In Redux I created another action just copying a previous working one but it doesn't work.
The one that works:
export const addEntry = entry => {
const config = {
headers: {
"Content-Type": "application/json",
},
}
return async dispatch => {
const response = await axios
.post("http://localhost:5000/api/db/addentry", entry, config)
.then(results => results.data)
try {
await dispatch({ type: ADD_ENTRY, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
The one that doesn't:
export const deleteEntry = itemId => {
console.log("action delete 1") // step 1
return async dispatch => {
console.log("action delete 2") // step 2
const response = await axios.delete(
`http://localhost:5000/api/db/deleteitem/${itemId}`,
itemId
)
try {
console.log("action delete 3") // step 3
await dispatch({ type: DELETE_ENTRY, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
If I log it step by step it stops after the first log
It doens't do anything. The same function addEntry works perfectly. Any idea?
You have to delete the ItemId, using axios.delete instead of axios.post
You also have to put your DELETE_ENTRY like 'DELETE_ENTRY' or else your reducer won't understand it
The reason why the first log comes through, is because that is part of the action creator. Normally, an action creator returns an object in the shape of { type: <type>, payload: <payload> }, but in this case, it returns a new function, which is called a "thunk".
How does this action get dispatched?
In order for this to work, you have to include the redux-thunk middleware, which if it receives a function through dispatch(...), executes that function.
Read up about thunks here
thanks to #HMR I just forgot to dispatch(deleteEntry(item.itemId)) when I call the action
Related
i want to know if there is some clean code or update to make it on my code, because i think i repeat the same code on every actions on my redux, my question is how can I avoid calling axios on my actions files ?
Please take a look on my code here :
export const SignInType = (host, lang) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_SIGNINTYPE_REQUEST,
});
const { data } = await axios.get(
`/${lang}/data?host=${host}`
);
console.log({ data });
dispatch({
type: USER_LOGIN_SIGNINTYPE_SUCCESS,
payload: data,
});
dispatch({
type: USER_LOGIN_CLEAR_ERROR,
});
} catch (err) {
dispatch({
type: USER_LOGIN_SIGNINTYPE_FAIL,
payload: err,
});
}
};
I Really want to delete the Axios name from my actions file and make it on a separate file, but how can i do this ?
Thank you
We can suggest but there's no correct answer to this, initially any redundant lines of code can be abstracted, so in order to make things a little bit easier, we need to abstract the obvious and add the meaningful, e.g:
abstract the way you write action creators:
const actionComposer = (options) => (...args) => async dispatch => {
const modifiedDispatch = (type, payload) => dispatch({ type, payload });
const { action, onSuccess, onFailed } = options(modifiedDispatch);
try {
if (action) {
const res = await action(...args)
onSuccess(res);
}
} catch (err) {
onFailed(err)
}
}
then your code can look like this:
export const SignInType = actionComposer((dispatch)=> {
return {
action: async (host, lang) => {
dispatch(USER_LOGIN_SIGNINTYPE_REQUEST);
const { data } = await axios.get(`/${lang}/data?host=${host}`);
return data;
},
onSuccess: (res) => {
dispatch(USER_LOGIN_SIGNINTYPE_SUCCESS, data);
dispatch(USER_LOGIN_CLEAR_ERROR);
},
onFailed: (err) => {
dispatch(USER_LOGIN_CLEAR_ERROR, err.message)
}
}
})
Redux Toolkit already has a createAsyncThunk API that does all the work of defining the action types and dispatching them for you. You should use that.
Alternately, you can use our RTK Query data fetching and caching library, which will eliminate the need to write any data fetching logic yourself.
So I have here a form which which when submitted, calls this function:
const handleSubmit = (e) => {
e.preventDefault();
try {
dispatch(addCategory({ category, identifier, definition }));
} catch (err) {
console.log(err.message);
}
};
and this is the code to my addCategory action in a separate file.
export const addCategory =
({ category, identifier, definition }) =>
async (dispatch) => {
try {
const { data } = await axios.post(
"http://localhost:5000/Admin/addCategory",
{ category, identifier, definition }
);
dispatch({ type: "ADD_CATEG", payload: data });
} catch (error) {
dispatch({
type: "GET_ERROR",
payload: error.response.data.errorMessage,
});
}
};
As you can see, I have an error handling in my backend and it triggers when the user inputs a duplicate data. The way I am getting that error is through this:
const { error } = useSelector((state) => state.categories);
What I want is that after I dispatch my action in my handleSubmit, it checks whether the error is empty or not. I've tried to call a function after the try-catch block in handleSubmit which looks like this. I've tried to run it then I've entered correct inputs of data without error, it displays 'no err' but if I tried to enter a duplicate input, it doesn't give me an error but in my redux console, it is already there. When I submit the form again, then it will now display the 'with err'. I've been trying to figure it out but can't seem to find any solutions.
const try = () => {
if (error !== null) {
console.log("with err");
} else if (error === null) {
console.log("no err");
}
};
I've ran into this problem as well.
One is catching an error on the request being sent
The other is catching an error from data handling on the Redux side
This is a pattern I've used before, you just need to make sure you are handling errors at every level.
export const addCategory =
({ category, identifier, definition }) =>
async (dispatch) => {
let response
try {
response = await axios.post(
"http://localhost:5000/Admin/addCategory",
{ category, identifier, definition }
);
dispatch({ type: "ADD_CATEG", payload: data });
} catch (error) {
response =
dispatch({
type: "GET_ERROR",
payload: error.response.data.errorMessage,
});
}
if(response?.data){
// do stuff for success
}
// do stuff for errors
};
I have a call to an API inside an action in redux.
export const registerUser = registeredUserData => async dispatch => {
let messages;
try {
const response = await axios.post('/users/register', registeredUserData);
messages = response.data
} catch (error) {
if (error.response) {
messages = error.response.data
}
}
dispatch({
type: REGISTER_USER,
messages,
});
};
This action is called when a form is sumbitted.
const onRegisterUser = (e) => {
e.preventDefault();
registerUser(registeredUserData);
};
When, if a call was successfull I want to redirect to another page.
The problem I'm facing is that I don't know how to implement history.push() in this case.
If I put it inside method of my component right after registerUser(registeredUserData); then it gets called right away no matter the response of the call. And I'm not sure if it is a good idea to redirect from the action itself.
All the help will be much appreciated.
In your example your action registerUser is a promise since it's an async function. So you could rewrite your onRegisterUser to look like this:
const onRegisterUser = (e) => {
e.preventDefault();
registerUser(registeredUserData)
.then(() => /* success */)
.catch(error => /* handle my failure */)
};
That being said you might want to consider creating SUCCESS and FAILURE actions for your network call. This allows you to potentially update the state of redux based on your register user api call.
You could modify your thunk to look like this:
export const registerUser = registeredUserData => async dispatch => {
try {
const response = await axios.post('/users/register', registeredUserData);
dispatch({
type: REGISTER_USER_SUCCESS,
messages: response.data,
});
} catch (error) {
if (error.response) {
dispatch({
type: REGISTER_USER_FAILURE,
messages: error.response.data,
});
}
}
};
You can then use one of React lifecycle methods to check for the state in redux change. Assuming the snippet is using react-redux and connect.
You might also want to consider looking into action creators.
An alternative to using React lifecycle methods is to use something like redux-saga which can signal on the SUCCESS and FAILURE actions and do the history.push on your behalf.
You might also want to look at react router if you haven't done so yet. It provides ways to access history and manage routing.
The point of async / await is to not have to use a nested promise chain in the case of your example.
Your try / catch block is equivalent to your then / catch. So if you want to use the above and have it catch when the response is a 400 you will either need to remove the try catch and handle the error in onRegisterUser, not recommended, or you will need to re-throw so that the catch is called when you call registerUser.
Here's an example on registerUser that should return a catch when failed response.
export const registerUser = registeredUserData => async dispatch => {
try {
const response = await axios.post('/users/register', registeredUserData);
await dispatch({
type: REGISTER_USER,
messages: response.data,
});
} catch (error) {
if (error.response) {
await dispatch({
type: REGISTER_USER,
messages: error.response.data,
isError: true,
});
throw new Error(error.response.data);
}
}
};
You might want to replace throw new Error(error.response.data) with something more specific by decorating the error object.
You are almost there. In your component, pass this.props.history as a parameter to the redux action. And from there, after the action is dispatched you can redirect to some other page.
P.S: It's not a bad idea to redirect from the action itself.
Inside your component:
const onRegisterUser = (e) => {
e.preventDefault();
registerUser(registeredUserData, this.props.history);
};
Inside your action:
export const registerUser = (registeredUserData, history) => async dispatch => {
let messages;
try {
const response = await axios.post('/users/register', registeredUserData);
messages = response.data
} catch (error) {
if (error.response) {
messages = error.response.data
}
}
dispatch({
type: REGISTER_USER,
messages,
});
history.push('/redirect-route);
};
Hi Allan,
You'll basically have this.props.history.push available from your Router which is passing it to all the Route components children in your app.
You can confirm this via console.log('__props__', this.props) in your render method for that component.
In order to implement this, I would suggest sending it as a callback to your action registerUser, in order to do this:
Add the cb to your action:
export const registerUser = registeredUserData => async dispatch => {
let messages;
try {
const response = await axios.post('/users/register', registeredUserData);
messages = response.data
} catch (error) {
if (error.response) {
messages = error.response.data
}
}
dispatch({
type: REGISTER_USER,
messages,
});
// maybe you want a conditional redirect?
if(/*somecondition to check data or error?*/){
cb && cb(); //if callback exists, invoke it
}
};
And for: onRegisterUser:
const onRegisterUser = (e) => {
e.preventDefault();
registerUser(registeredUserData, () => {
this.props.history.push(//route);
});
};
Hope that works, if it doesn't please describe the behavior.
I'm using v2.2.2 of redux-loop to handle my side-effects from a server call.
Having dispactched an action from my component like so:
checkStatus() {
const user = Utils.toJS(this.props.user);
this.props.dispatch(UserActions.getUserData(user._id))
.then((res) => {
console.log(res)
}
}
I expect my promise to come back from the dispatch, but it always returns
[]
My action looks like so...
export async function getUserData(data) {
return await getUser(data)
.then((res) => ({type: USER_GET_SUCCESS, payload: res}))
.catch((err) => ({type: USER_GET_FAILURE, payload: err}));
}
where getUser looks like:
export async function getUser(data) {
return await get(`/users/${data}`)
}
and gets caught in the reducer and saved to the state like so:
case USER_GET_SUCCESS:
return state
.set('user', fromJS(action.payload.data));
The data always comes back properly but for some reason never gets returned back as a promise to the original dispatch.
Any suggestions would be amazing!
I think part of the issue is mixing your promise .then code in with the async/await calls within a single function. Try this instead:
export async function getUserData(data) {
try {
const result = await getUser(data);
return { type: USER_GET_SUCCESS, payload: result };
} catch (err) {
return {type: USER_GET_FAILURE, payload: err};
}
}
I am building an react / redux webapp where I am using a service to make all my API calls. Whenever the API returns 401 - Unauthorized I want to dispatch a logout action to my redux store.
The problem is now that my api-service is no react component, so I cannot get a reference to dispatch or actions.
What I did first was exporting the store and calling dispatch manually, but as I read here How to dispatch a Redux action with a timeout? that seems to be a bad practice because it requires the store to be a singleton, which makes testing hard and rendering on the server impossible because we need different stores for each user.
I am already using react-thunk (https://github.com/gaearon/redux-thunk) but I dont see how I can injectdispatch` into non-react components.
What do I need to do? Or is it generally a bad practice to dispatch actions outside from react components?
This is what my api.services.ts looks like right now:
... other imports
// !!!!!-> I want to get rid of this import
import {store} from '../';
export const fetchWithAuth = (url: string, method: TMethod = 'GET', data: any = null): Promise<TResponseData> => {
let promise = new Promise((resolve, reject) => {
const headers = {
"Content-Type": "application/json",
"Authorization": getFromStorage('auth_token')
};
const options = {
body: data ? JSON.stringify(data) : null,
method,
headers
};
fetch(url, options).then((response) => {
const statusAsString = response.status.toString();
if (statusAsString.substr(0, 1) !== '2') {
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
store.dispatch(UserActions.logout());
}
reject();
} else {
saveToStorage('auth_token', response.headers.get('X-TOKEN'));
resolve({
data: response.body,
headers: response.headers
});
}
})
});
return promise;
};
Thanks!
If you are using redux-thunk, you can return a function from an action creator, which has dispatch has argument:
const doSomeStuff = dispatch => {
fetch(…)
.then(res => res.json())
.then(json => dispatch({
type: 'dostuffsuccess',
payload: { json }
}))
.catch(err => dispatch({
type: 'dostufferr',
payload: { err }
}))
}
Another option is to use middleware for remote stuff. This works the way, that middle can test the type of an action and then transform it into on or multiple others. have a look here, it is similar, even if is basically about animations, the answer ends with some explanation about how to use middleware for remote requests.
maybe you can try to use middleware to catch the error and dispatch the logout action,
but in that case, the problem is you have to dispatch error in action creator which need to check the log status
api: throw the error
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
throw new Error('401')
}
action creator: catch error from api, and dispatch error action
fetchSometing(ur)
.then(...)
.catch(err => dispatch({
type: fetchSometingError,
err: err
})
middleware: catch the error with 401 message, and dispatch logout action
const authMiddleware = (store) => (next) => (action) => {
if (action.error.message === '401') {
store.dispatch(UserActions.logout())
}
}
You should have your api call be completely independent from redux. It should return a promise (like it currently does), resolve in the happy case and reject with a parameter that tells the status. Something like
if (statusAsString === '401') {
reject({ logout: true })
}
reject({ logout: false });
Then in your action creator code you would do:
function fetchWithAuthAction(url, method, data) {
return function (dispatch) {
return fetchWithAuth(url, method, data).then(
({ data, headers }) => dispatch(fetchedData(data, headers)),
({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
} else {
dispatch(fetchedDataFailed());
}
);
};
}
Edit:
If you don't want to write the error handling code everywhere, you could create a helper:
function logoutOnError(promise, dispatch) {
return promise.catch(({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
}
})
}
Then you could just use it in your action creators:
function fetchUsers() {
return function (dispatch) {
return logoutOnError(fetchWithAuth("/users", "GET"), dispatch).then(...)
}
}
You can also use axios (interceptors) or apisauce (monitors) and intercept all calls before they goes to their handlers and at that point use the
// this conditional depends on how the interceptor works on each api.
// In apisauce you use response.status
if (response.status === '401') {
store.dispatch(UserActions.logout())
}