0
votes

I have a React context that is working, but I want to bulk load items into the state.

The context has a reducer to add a single object to the array of objects that the Context stores. In the code (attached) the problem area is at the bottom in the Provider initialisation. In a useEffect, this does an api fetch call. The data comes back and is currently loaded one element at a time using the reducer. This is inefficient as it results in multiple render calls to the components listening to the context.

I am relatively new to React and Typescript and having difficulties getting the right coding and syntax to stop Typescript complaining.

I want to change the reducer to accept a 'load' action with a payload of an array of items that replace all current elements in the states array. I have a special load action and have beenn trying to get the payload for that action to be an array without changing the other actions which accept a single item. Either that or provide a new function that loads an array of objects into the state.

I have tried defining the payload in IAction to be payload: NewsItemType|Array, item NewsItemType }

Whatever I try, I get syntax errors in reducer: React.Reducer - mainly about the return type.

Can anyone help with advice on the best way to do this?

    import * as React from "react";

/** Custom types */
import { ActionType } from "../custom-types";
import { NewsItemType } from "../custom-types";
import { apiRequest } from "../utils/Helpers";

const MAX_ITEMS = 30;

interface IState {
    newsList: Array<NewsItemType>;
}

interface IAction {
    type: ActionType;
    payload: NewsItemType;
}

interface InewsContextInterface {
    state: {
        newsList: Array<NewsItemType>;
    };
    updateNewsList: React.Dispatch<IAction>;
}

const initialState: IState = { newsList: [] };

const reducer: React.Reducer<IState, IAction> = (state, action) => {
    switch (action.type) {
        case ActionType.add:
            return {
                newsList: [
                    action.payload,
                    ...state.newsList.filter(
                        newsitem => newsitem._id !== action.payload._id
                    )
                ]
            };
        case ActionType.load:
            return {
                newsList: [...state.newsList, action.payload]
            };
        case ActionType.update:
            return {
                newsList: state.newsList.map(item => {
                    return item._id === action.payload._id ? action.payload : item;
                })
            };
        case ActionType.delete:
            return {
                newsList: state.newsList.filter(
                    newsitem => newsitem._id !== action.payload._id
                )
            };
        default:
            throw new Error();
    }
};

export const newsContext = React.createContext<InewsContextInterface>({
    state: {
        newsList: []
    },
    updateNewsList: () => { }
});

const { Provider } = newsContext;

const NewsProvider: React.FC<{ children: React.ReactNode }> = ({
    children
}) => {
    const [newsList, updateNewsList] = React.useReducer(reducer, initialState);

    React.useEffect(() => {
        apiRequest("http://localhost:5000/news/?limit=" + MAX_ITEMS, "get", true)
            .then(news => {
                if (news) {
                    Array.from(news).forEach((article: any) =>
                        updateNewsList({ type: ActionType.load, payload: article })
                    );
                }
            })
            .catch(alert => {
                console.error(alert);
            });
    }, []);

    return (
        <Provider value={{ state: newsList, updateNewsList }}>{children}</Provider>
    );
};

export default NewsProvider;
1
Can you post the errors you're seeing?Ross Allen
After changing payload to: interface IAction { type: ActionType; payload: NewsItemType | Array<NewsItemType>; } I get typescript errors on the reducer declaration which start with: Type '(state: IState, action: IAction) => { newsList: (NewsItemType | NewsItemType[])[]; }' is not assignable to type 'Reducer<IState, IAction>'. Call signature return types '{ newsList: (NewsItemType | NewsItemType[])[]; }' and 'IState' are incompatible.John Barrett

1 Answers

0
votes

First of all, you will not have multiple calls to the API request, because you set up an empty dependency array as second parameters of useEffect. So it will only be called once, after the component was mounted.

Regarding the errors in your reducer, it will be hard to answer without knowing precisely the error message.

If you want to see an example of data fetching with React hooks, you can look at the fetcher pattern, as used in this file.

If you want your reducer to accept both a single element or an array, you can write the load part like this:

case ActionType.load:
  return {
    newsList: [...state.newsList, ...Array<NewsItemType>().concat(action.payload)]
  };