Should I handle errors in my action creators

Viewed 161

In the following context how should I handle possible errors:

export async function testAction(data) {

    try {
        let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
        return {
            type: 'TEST_ACTION',
            payload: response
        }
    } catch(err) {
        // ???
    }

}

// Somewhere in a component:

<Button onClick={ () => dispatch( testAction() ) }>
    Test Stuff
</Button>

Or is better to actually dispatch from the component, eg:

refactor action creator:

export async function testAction(data) {

        try {
            let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
            return response
        } catch(err) {
            return err
        }

}

Somewhere in a component:

const handleTestAction = () => {
   testAction().then(r => dispatch( { type: 'TEST_ACTION', payload: r } ) ).catch( // hadnle errors)
}

<Button onClick={ handleTestAction }>
   Test Stuff
</Button>

I know the redux style guide recommends using Action Creators for dispatching actions but in this particular case I am calling the action first and then use dispatch. How should I approach it?

2 Answers

You can create another reducer to handle errors.

   export async function testAction(data) {

        try {
            let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
            return {
                type: 'TEST_ACTION',
                payload: response
            }
        } catch(err) {
            return {
                type: 'ERROR',
                payload: err
            }
        }

    }

But you cannot do it like above. because the process is asynchronous

You have to use a 'redux-thunk' for that. Once you add it as a middle-ware to your store, you can get the dispatcher in to your action creater, so you can dispatch anything in the test action after you complete.

So your reducer should change to the below one,

   export async function testAction(data) {

     return (dispatch) => {
        try {
            let response = await 
            axios.get(`https://jsonplaceholder.typicode.com/todos/1`);

            dispatch({
               type: 'TEST_ACTION',
                payload: response
           })
        } catch(err) {
            dispatch({
               type: 'ERR',
                payload: response
           })
        }           
     }

    }

UPDATE

Once you connect the middleware, you can use dispatch in the action creater,

const store = createStore(
    reducers,
    {},
    applyMiddleware(thunk)
);

You need to only add the thunk to the store just like above.

You can make it more clear by refactor your code like below

export const testAction = () => async (dispatch) => {
     try {
            let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
            dispatch({
               type: 'TEST_ACTION',
                payload: response
           })
        } catch(err) {
            dispatch({
               type: 'ERR',
                payload: response
           })
     }
}

If your API is going to change in dev and prod modes, You can use below way,

Somewhere in your application,

 const axiosInstatnce = axios.create({
    baseURL: "https://jsonplaceholder.typicode.com",
    headers: {/* you can set any header here */}
  });

Now when you create store,

const store = createStore(
 reducers,
 {},
 applyMiddleware(thunk.withExtraArgument(axiosInstatnce))
);

Now you can get the axiosInstance as the third argument of the function you return from the testAction. 2nd argument gives the current state.

 export const testAction = () => async (dispatch, state, api) => {
     try {
            let response = await api.get(`/todos/1`);
            dispatch({
               type: 'TEST_ACTION',
                payload: response
           })
        } catch(err) {
             dispatch({
               type: 'ERR',
                payload: response
           })
     }
}

Now in your component,

import {testAction} from '../path/to/actions'

    const dispatch = useDispatch()

    dispatch(testAction())

If you want to write async code in an action creator, you need to write an async action creator. Regular action creators return an object whereas async action creators return a function instead of an object.

export function testAction(data) {
   return async function(dispatch) {
      // async code
   }
}

Inside the function returned by an async action creator, you have access to dispatch which can be used to dispatch any success action in case of successful response from server and in case of error, you can dispatch an action indicating that an error has occurred.

export function testAction(data) {
    return async function (dispatch) {
        try {
            let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);

            dispatch({
              type: 'TEST_ACTION',
              payload: response
            });

       } catch(err) {
           dispatch({type: 'TEST_ACTION_ERROR', message: 'error occurred'});
       }
   }
}

You also need to use redux-thunk middleware if you have async action creators in your code. This middleware allows action creators to return a function.

For complete details about how to create async action creators and how to setup redux-thunk middleware to make async creators work, take a look at Async Actions

Related