1

I am trying to figure out how to use useReducer with asynchronous CRUD operations (using the fetch API).

In my mind the reducer function would look like this:

async function reducer(action, state) {
    const newState = {...state};

    switch (action.type) {
        case types.ADD_ITEM:
            try {
                const {item} = await addItem(action.payload.item);
                newState.items.push(item);
            }
            catch (e) {
                newState.error = e.message;
            }
            break;
        case types.REMOVE_ITEM:
            try {
                await removeItem(action.payload.itemId);
                newState.items = newState.items.filter(value.id !== action.payload);
            }
            catch (e) {
                newState.error = e.message;
            }
            break;
        case types.EDIT_ITEM:
            try {
                const {item} = await editItem(action.payload.itemId, action.payload.item);
                newState.items[newState.items.findIndex(value => value.id === action.payload.itemId)] = item;
            }
            catch (e) {
                newState.error = e.message;
            }
            break;
    }

    return newState;
}

These would be the fetch functions:

async function addItem(item) {
    const response = await fetch('addItemRoute', {
        method: "POST",
        body: JSON.stringify({
            item
        })
    });
    return response.json();
}

async function removeItem(itemId) {
    const response = await fetch('removeItemRoute/' + itemId, {
        method: "DELETE"
    });
    return response.json();
}

async function editItem(itemId, item) {
    const response = await fetch('editItemRoute/'+ itemId, {
        method: "PUT",
        body: JSON.stringify({
            item
        })
    });
    return response.json();
}

But the reducer function cannot be an async function.

What would be the standard way to handle concepts like this?

Any help/reference is truly appreciated.

VLAZ
  • 26,331
  • 9
  • 49
  • 67
Laurent Dhont
  • 1,012
  • 1
  • 9
  • 22

1 Answers1

2

I think you misunderstood the role of reducer. In React world, there is a thing call global state (a way to pass values down to children without having to pass as props), which traditionally being handled by another package called Redux. The reducer only handle taking whatever you dispatch, decide what action to take to update the global state based on the type of action which is not asynchronous. The action is what you use to decide what to dispatch and also the way for you to get the data to dispatch so usually all the HTTP calls occurs here. Since useReducer will returns for you the current state and the dispatch function as well, you can basically pass this dispatch to your action. You can take a look at my example below based on your example for clearer image of what you might want to do:

You may want to put all your action in a action file called action.js like this:

async function addItem(item, dispatch) {
    const response = await fetch('addItemRoute', {
        method: "POST",
        body: JSON.stringify({
            item
        })
    });
    return dispatch({
      type: "ADD_ITEM",
      payload: response.json()});
}

async function removeItem(itemId, dispatch) {
    const response = await fetch('removeItemRoute/' + itemId, {
        method: "DELETE"
    });
    return dispatch({
      type: "ADD_ITEM",
      payload: response.json()
     });
}

async function editItem(itemId, item, dispatch) {
    const response = await fetch('editItemRoute/'+ itemId, {
        method: "PUT",
        body: JSON.stringify({
            item
        })
    });
    return dispatch({
      type: "ADD_ITEM",
      payload: response.json()
     });
}

Then in your reducer, you can do the regular without having to call the fetch or async calls like this:

async function reducer(action, state) {
    const newState = {...state};

    switch (action.type) {
        case types.ADD_ITEM:
            try {
                const {item} = action.payload;
                newState.items.push(item);
            }
            catch (e) {
                newState.error = e.message;
            }
            break;
        case types.REMOVE_ITEM:
            try {
                newState.items = newState.items.filter(value.id !== action.payload);
            }
            catch (e) {
                newState.error = e.message;
            }
            break;
        case types.EDIT_ITEM:
            try {
                const {item} = action.payload;
                newState.items[newState.items.findIndex(value => value.id === action.payload.itemId)] = item;
            }
            catch (e) {
                newState.error = e.message;
            }
            break;
    }

    return newState;
}

Then in your component with the button that you want to execute this, you can do something like this:

const MyComponent = ()=> {
  ...
  const [state, dispatch] = useReducer(reducer, initialState);
  ...
  return (
    ...
    <button onClick={addItem(item, dispatch)}/>
    ...
}

You can reference the core concept of redux here which IMO explains very clearly the functionality of reducer, dispatch, actions and global state. If you want you can also tried out Redux as well, here's their tutorial.

Tung Pham
  • 537
  • 4
  • 10