Should I handle errors in my action creators - reactjs

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?

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

How to return the desired type in action creator React Typescript Redux

I have a problem. Look at my code first:
export const login = (email: string, password: string) => {
return async (dispatch: Dispatch<UserAction>) => {
try {
dispatch({type: UserActionTypes.FETCH_USER})
const response = await axios.post<IUser>('http://localhost:8080/auth/signIn', {email, password})
dispatch({type: UserActionTypes.FETCH_USER_SUCCESS, payload: response.data})
return response.data
}catch (e) {
dispatch({
type: UserActionTypes.FETCH_USER_ERROR,
payload: 'Произошла ошибка при загрузке пользователя'
})
}
}
}
It's my action creator, where i authentificate user.
let principal = await login(email.value, password.value)
console.log(principal)
Here I try to get the response, i return in action creator, i get not what i need and i can't get fields i need.
But what i see in console console.log(principal): this is object, i needed, but i can't get his fields:
You need to use useDispatch hook. https://react-redux.js.org/api/hooks#usedispatch
const dispatch = useDispatch();
And then dispatch the login action.
dispatch(login(email.value, password.value));
also you do not need to await when you dispatch an action.

The function call inside the redux action doesnot get executed

I'm trying my first react application with redux, along with Thunk as middle ware. When calling the action from one of the components, the action is hit but the code inside the action return is not executed. I have no clue what I'm missing here. I'm also using Firestore to get the data.
export const getBikeTypes = () => {
console.log('outside return')
return (dispatch, getState, { getFireBase, getFireStore }) => {
console.log('inside return')
const firestore = getFireStore();
firestore.collection('BikeTypes').get()
.then((response) => {
console.log(response)
return response
}).then(() => {
dispatch({ type: 'GET_BIKETYPES' });
}).catch((err) => {
dispatch({ type: 'GET_BIKETYPES_FAIL', err });
})
}
};
I think you should dispatch action with the payload once you get the response.
const firestore = getFireStore();
firestore.collection('BikeTypes').get()
.then((response) => {
dispatch({ type: 'GET_BIKETYPES', payload: response })
})

How to pass parameters in an async api thunk call

Hi I am trying to pass a parameter for the post method in an api call in a redux thunk middleware. But this is not invoking an action. Could someone have a look at the code and tell what mistake i am doing:
import API from "../../_metronic/utils/api";
let FetchActions = async (id,dispatch) => {
await API.post("companies/",id)
.then(res => dispatch({ type: "FETCH_COMPANIES", payload: res.data }))
.catch(err => console.log(err));
};
export default FetchActions;
I am getting the following error:
TypeError: dispatch is not a function
at fetchAction.js:6
import API from "../../_metronic/utils/api";
let FetchActions = id => async (dispatch, getState) => {
await API.post("companies/",id)
.then((res) => {
dispatch({ type: "FETCH_COMPANIES", payload: res.data })
})
.catch((err) => {
console.log(err)
});
};
export default FetchActions;
You are doing wrong with the syntax as redux-thunk needs a callback function to return from the method.
Another thing is if you are using await you don't need to have .then and .catch, instead, you should wrap this code in the try-catch block.
The below code should work for you.
import API from "../../_metronic/utils/api";
let FetchActions = async (id) => {
return async (dispatch, getState) => {
try {
const res = await API.post("companies/",id);
dispatch({ type: "FETCH_COMPANIES", payload: res.data })
} catch(err) {
console.log(err)
}
}
};
export default FetchActions;
Read more about the action creators in redux-thunk here.

Why are action function called wrapped inside dispatch in react?

I was just going through some react-redux code online and basically i came across the following set of code , which is basically a js file full of ACTIONS , just to give some context a combination of redux and redux-thunk is being used here:
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
await dispatch(getConfig());
await dispatch(getGenres());
dispatch({ type: TYPES.REMOVE_LOADING });
};
// Action Creator to get the config object from the API
export const getConfig = () => async dispatch => {
const res = await tmdbAPI.get('/configuration');
dispatch({
type: TYPES.GET_CONFIG,
payload: res.data,
});
};
I am a bit confused as to why is getConfig function is being wrapped inside a dispatch ?
if you don't want, don't wrap that
like this
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
dispatch(await getConfig());
dispatch({ type: TYPES.REMOVE_LOADING });
};
//this is not using redux-thunk
// Action Creator to get the config object from the API
export const getConfig = async () => {
const res = await tmdbAPI.get('/configuration');
return {
type: TYPES.GET_CONFIG,
payload: res.data,
};
};
Important
but there are so many reasons why wrap the actions by dispatch.
Here is one example.
when you want get multiple data in one action step by step
export const getData = () => async dispatch => {
dispatch({
type: DATA_LOADING_START
});
try {
const res = await tmdbAPI.get('/url1');
dispatch({
type: DATA1_LOADED,
payload: res.data,
});
const res = await tmdbAPI.get('/url2');
dispatch({
type: DATA2_LOADED,
payload: res.data,
});
} catch (err) {
// handle error
}
dispatch({
type: DATA_LOADING_END
});
};
//this is using redux-thunk
#AlexanderSolonik
Question: Why wrap actions by dispatch?
because dispatch() sends the action result to the reducer.
Redux thunks are just redux actions that can perform side effects. So
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
await dispatch(getConfig());
await dispatch(getGenres());
dispatch({ type: TYPES.REMOVE_LOADING });
};
Is just an async init function which when called performs the steps in a synchronous manner.
The key is that thunks can dispatch other thunks/actions/etc so the init thunk is just dispatching the getConfig() action which is itself async so the next step of the init function won't be performed until the config API call finishes (possibly because some other code depends on it)

How to make await work with redux Saga in React?

The await does not seem to work with Redux saga. I need to wait for my API call to finish and then execute the remaining code. What happens now is that AFTER CALL gets printed before the RESPONSE which means await does not seem to work at all. I'm using async calls but not sure what needs to be done extra from the redux saga side?
async componentWillMount() {
console.log("BEFORE CALL")
await this.props.getUserCredit()
console.log("AFTER CALL")
}
mapDispatchToProps = (dispatch) => {
return {
getUserCredit: () => dispatch(getUserCredit()),
}
};
connect(null, mapDispatchToProps)(MyComponent);
Action
export const getUserCredit = () => {
return {
type: GET_USER_CREDIT,
};
};
Redux Saga
const getUserCreditRequest = async () => {
const response = await Api.get(getCreditUrl)
console.log("REPONSE!!!")
console.log(response)
return response
}
function* getUserCredits() {
try {
const response = yield call(getUserCreditRequest);
if (response.status === okStatus) {
yield put({
userCredit: response.data.userCredit
}
));
}
} catch (error) {}
}
export function* getUserCredit() {
yield takeLatest(GET_USER_CREDIT, getUserCredits);
}
export default function* rootSaga() {
yield all([fork(getUserCredit)]);
}
Normally, init / fetching takes place during componentDidMount and don't use async or await inside components. Let the saga middleware do its thing via yield.
// In your component
componentDidMount() { // not async
this.props.getUserCredit(); // dispatch `GET_USER_CREDIT` action
}
mapDispatchToProps = (dispatch) => {
return {
getUserCredit: () => dispatch(getUserCredit()),
}
};
connect(null, mapDispatchToProps)(YourComponent);
You shouldn't be using async/await pattern. As redux-saga handles it by the yield keyword. By the time call is resolved you will have the value available in response.
in actions.js, you should have an action that will carry your data to your reducer:
export function getUserCredits(userCredit) {
return {
type: types.GET_USER_CREDIT_SUCCESS,
payload: userCredit
};
}
Your saga should handle the API call like so:
function* getUserCredits() {
try {
const response = yield axios.get(getCreditUrl); <--- This should work
// No need for if here, the saga will catch an error if the previous call failed
yield put(actions.getUserCredits(response.data.userCredit));
} catch (error) {
console.log(error);
}
}
EDIT: example of using axios with redux-saga

Resources