Call a function after react-redux dispatch has finished - reactjs

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
};

Related

Axios calls with React : best practises

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.

Axios get in stock items

I am trying to filter products whether it is available or not.
Not sure how to pass an axios request with ">" logic.
I've started to create an Action
export const listProductAvailable = () => async (dispatch) => {
dispatch({
type: PRODUCT_AVAILABLE_LIST_REQUEST,
});
try {
const { data } = await Axios.get(`/api/products?countInStock>0`);
dispatch({ type: PRODUCT_AVAILABLE_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_AVAILABLE_LIST_FAIL, payload: error.message });
}
};
But I don't think that such a request is possible.
const { data } = await Axios.get(/api/products?countInStock>0);
Also, I don't see myself changing my Product Model creating an isAvailable boolean field as it would be redundant with countInStock =0 or not.

What's causing this async function to work only after a page refresh?

I've been attempting to find discussions about this for over a week now, but most issues seem related to trouble persisting through a refresh, while I'm having state troubles without refreshing, so I'm not getting much of anywhere with it.
I'm attempting to load a gallery of images after a user logs in. The login is functioning properly--updates the state with a reducer and pushes from /login to /gallery and I can see in the inspector that the user ID updates from null to a value.
At /gallery I attempt to retrieve some data through axios asynchronously. It's a POST request so that I can send the user's ID in the body rather than the url/using params.
On initial login, state.images doesn't update and throws this error:
"data: "Cast to string failed for value "{ user: '60fc726d827a4e3daff47619' }" (type Object) at path "user" for model "Upload"" " Relative to my database/models: I've tried adjusting type on the model, both the $type approach and adjusting for a String that is an array, the former caused errors large enough for the page not to load, the latter affected no discernible changes.
If I reload the page, everything works and the images load. If I click from gallery to home and then back to gallery again, nothing changes in the state. I have no idea if this is an issue with my amateur async function structure, my mongodb setup, my reducer, the axios post itself, or something else entirely.
I've read that pretty much everything needs to be lined up inside of useEffect() but I've had absolutely no luck getting that to function either.
The whole of the code is at https://github.com/polysnacktyl/react-foraging, but here's the (seemingly) most relevant:
Login
function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { getLoggedIn } = useContext(AuthContext);
const { dispatch } = useContext(Context);
const history = useHistory();
async function login(e) {
e.preventDefault();
try {
const { data } = await axios.post('http://localhost:3000/auth/login', {
email,
password
});
await getLoggedIn();
dispatch({
type: 'login',
payload: { user: data._id }
})
window.localStorage.setItem('user', JSON.stringify(data._id))
history.push('/gallery');
} catch (err) {
console.error(err);
}
}
return (... and so forth
Image Gallery
const { state, dispatch } = useContext(Context);
const [isLoading, setLoading] = useState(true);
const [images, setImages] = useState({ images: [] });
const user = state.user;
const success = async () => {
try {
const res = await axios.post('http://localhost:3000/auth/mine', { user });
dispatch({
type: 'fetchSuccess',
payload: { images: res.data }
})
setImages(res.data);
} catch (err) { console.log(err.response) }
}
const fail = (error) =>
dispatch({
type: 'fetchFail',
payload: { error: error.message }
});
function loadImages() {
dispatch({ type: 'fetchImages' });
setTimeout(async () => {
try {
await success();
setLoading(false)
} catch (error) {
await fail(error);
}
}, 1000);
}
useEffect(() => {
loadImages()
//eslint-disable-next-line
}, [])
if (isLoading) {
return (<div className='loading'>...loading</div>)
} else {
return (...and so on
Let's have a look at your loadImages. This function is called during componentDidMount. This seems OK on first glance but it is a bug actually.
loadImages internally calls success that actually depends on user variable.
Having this in mind what we can do is following:
useEffect(() => {
if(!user) { // if no user present somehow, let's return
return;
}
loadImages() // load the images
//eslint-disable-next-line
}, [user]) // add a dependency to the user, since we load user images actually
I think with this approach the issue will be fixed and also you can remove the setTimeout in loadImages in my opinion.
As it turns out, my issue was in my axios-post-with-req-body strategy. I switched it to a GET request and sent the user ID in the params and now everything is loading on initial login and persisting through page relaods. I'm still not sure why it was causing the behavior it was, but at least I know how to avoid it.
the setup that ultimately worked out for me:
function Gallery() {
const { state, dispatch } = useContext(Context);
const [isLoading, setLoading] = useState(true);
const [images, setImages] = useState([]);
const user = state.user;
const success = async () => {
const res = await axios.get('http://localhost:3000/auth/mine', {
params: { user }
})
dispatch({
type: 'fetchSuccess',
payload: res.data
})
setImages(res.data);
}
const fail = (error) =>
dispatch({
type: 'fetchFail',
payload: { error: error.message }
});
function loadImages() {
dispatch({ type: 'fetchImages' });
setTimeout(async () => {
try {
await success();
setLoading(false)
} catch (error) {
await fail(error);
}
}, 0);
}
useEffect(() => {
loadImages()
//eslint-disable-next-line
}, [])
if (isLoading) {
return (<div className='loading'>...loading</div>)
} else {
return (...images that actually load. wonderful.)
Additionally, I had to adjust the router to accept req.params.user instead of req.body.user.

Custom error message for firebase for react and redux

I am trying to create a Login feature using firebase and redux. I try to implement a custom error message when the user input the wrong credentials like so:
// Log in
export const signin = (data, onError) => {
console.log(data.email, data.password)
return async dispatch => {
try {
const res = await firebase.auth().signInWithEmailAndPassword(data.email, data.password)
.then(async () => {
if(res.user){
const user = await firebase.firestore().collection('users').doc(res.user.uid).get();
if(user.exists) {
const userData = user.data() as User;
dispatch({
type: SET_USER,
payload: userData
})
}
return res
}
}).catch((error) => {
switch(error.code) {
case 'auth/user-not-found ':
error.message = "User not found"
break;
}
})
}
catch (err) {
console.log(err);
onError();
dispatch(setError(err.message));
}
}
}
However, after I run my application, the application stuck in a loading loop forever. Can someone help me out? Thank you in advance. Cheers

How to redirect from component after action was called successfully in React?

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.

Resources