I'm having trouble with redux/axios. I want to change the store after an axios request is successful, and after the store change, I want to change the application state. I expected the code to get to my function but that does not happen, and I'm not sure why! Is redux async? if so how can I make a sync call? (Left some code bellow)
axios.post(url,{
"system":system,
})
.then(res => {
this.props.setTest(res) #code does not follow this line, no state is set
this.setState({
test_data:res,
redirect:true
})
})
.catch((error)=>{
if(error.response){
if(error.response.data["Error"]){
console.log("There was an error fetching the test")
}
}
})
export function setTest(test){
return {type: "SET_TEST",payload: test}
}
case "SET_TEST":{
console.log("SET_TEST")
return {
...state,
(and other changed args)
};
}
Thanks for the help!
It is very likely that this.props.setTest(res) triggers some kind of error.
Since later you have a catch block where you only handle very specific error, that error makes it never out and you will never know what the error was in the first place until you console.log it.
So:
.catch((error)=>{
if(error.response && error.response.data["Error"]){
console.log("There was an error fetching the test")
} else {
// don't swallow your error, log it!
console.log("some unexpected error happened:", error);
}
I don't think you understood how Redux works. In your case, setTest returns an action. An action needs to be dispatched.
Try this:
import { useDispatch } from "react-redux";
// inside your component
const dispatch = useDispatch();
dispatch(setTest(res));
Related
I have two files. One is "actions.js" and the other one is "Profile.js". The first file has a function that calls an API that will get information about an user based on his/her id.
The code for "actions.js" is:
import axios from "axios";
export const getPerson = (id, history) => async dispatch => {
try {
const res = await axios.get(`http://localhost:6969/users/getUsers/${id}`);
const { email } = res.data;
console.log(email);
dispatch({
type: GET_PERSON,
payload: res.data,
});
} catch (error) {
history.push("/profile");
}
};
The code for my "Profile.js" page is:
import React, { Component } from 'react'
import { getPerson } from '../actions/personActions';
import * as PropTypes from 'prop-types'
import { connect } from "react-redux";
render() {
this.props.getPerson(id, this.props.history);
----------- Followed by render method
}
Profile.propTypes = {
getPerson: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
errors: state.errors
});
export default connect(
mapStateToProps,
{ getPerson }
)(Profile);
Problem is that I cannot show those responses in my Profile page by even localstorage if I decide to put the respective value in it. Tried with variety of ways but it shows up in the action.js page if I see from console using inspect log however it shows undefined in the Profile.js page. Please tell me where did I made it wrong. Thanks.
So, I think you're issue is that you're not dispatching anything in catch here:
} catch (error) {
dispatch({
// or something like this
type: GET_PERSON,
payload: error,
});
history.push("/profile");
}
Because of this, state is not updated with new error and state.errors is probably null or undefined.
Also, If you're trying to render anything other than the error from the state, don't forget to add it to mapStateToProps.
Other than that, I would recommend against calling api function in render. That's a really bad idea, that's because you might get a lot of useless api calls or even a loop of renders. Either call it in componentDidMount or use useEffect hook.
Generally I would advise that you go through couple of more react/redux tutorials.
I have many async Redux actions that all fetch different resources from an API. Now if the user is not authenticated all these API calls will return a 401.
Now I am looking for a way to catch all these and automatically navigate the user to the login page using history.push('/login'). Now I thought of a "smart" way of doing this by having all these async actions set a 401 status to the action object. Subsequently a reducer would catch all these actions by their status property being equal to 401. The user would then be navigated to the login page by a history.push() call inside this reducer:
import { history } from '../routers/AppRouter'
export default (state = {}, { status }) => {
if (!status) return state
switch (status) {
case 401: {
history.push('/login')
break
}
default: {
}
}
return state
}
However, as I already expected, but hoped to false, this is not possible, since a reducer is not allowed to have side-effects... I get the following error as a result:
Error: You may not unsubscribe from a store listener while the reducer is executing.
My question thus is, if there is a way to make this work inside the reducer? Or is there some other way of doing this. I'd really like to avoid having to put the following code in every async action:
if (response.status === 401) history.push('/login')
So I did find a solution to this. Since it is a "workaround" I didn't bother posting it at first, but since others are apparently facing the same issue, I decided to share how I solved the problem anyway:
First of all, it is not possible (or at least highly discouraged) for a reducer to have any side-effects, such as calling history.push(). The workaround I used, was to create a custom error handler function, which looks pretty similar to a reducer:
export default (status, dispatch) => {
switch (status) {
case 401: {
history.push('/login')
break
}
case 404: {
history.push('/404')
break
}
default: {
return
}
}
}
Inside of my async action (thunk) I then have something like this:
if (status === 200) {
dispatch( <some success action> )
} else {
dispatch( <some failure action> )
errorHandler(status, dispatch)
}
This way, the "side-effect" happens inside of the thunk, which is allowed as opposed to inside the reducer (which is called by dispatch()).
I know it is slightly annoying to always have to include the errorHandler(status, dispatch) line, but at least all of the error handling code is obfuscated this way.
I hope this helps someone! If you have any other suggestions on how to do this, I'd love to hear them too! :)
I'm working on my MERN app, and when I'm logging smth in NewsPage component, it logs infinitely.
NewsPage component:
const NewsPage = ({news, fetchNews}) => {
const postNews = (title, body) => {
axios.post("http://localhost:9000/news", { title, body });
}
useEffect(() => {
fetchNews();
}, [fetchNews, postNews])
return (
<>
<AddNewsForm postNews={postNews}/>
<h1>News:</h1>
<NewsItemPage news={news} />
</>
);
};
const mapStateToProps = state => ({
news: state.news
})
export default connect(mapStateToProps, {fetchNews})(NewsPage);
Fetch news action:
export const fetchNews = () => dispatch => {
fetchRequest();
try {
const fetch = async () => {
const res = await axios.get("http://localhost:9000/news");
dispatch({
type: a.FETCH_NEWS_SUCCESS,
payload: res.data
});
}
fetch()
} catch (e) {
dispatch({
type: a.FETCH_NEWS_FAILURE,
error: e
});
}
}
It works correctly, I can fetch news from and post news to my backend, but if I log anything in console, it would be logging infinitely, and I will not get any error.
is there a way to fix this, and is this a real problem?
Its likely because whatever function the console log is located in is being used in render, which itself is a loop. Otherwise, there is no other way that I can see why it would repeat. It probably won't end up being a problem, unless the code you are executing slows down, which might cause performance issues in the future.
You're tracking fetchNews and postNews in your useEffect array, which will re-rerun fetchNews(); on every render.
Unless the values in the useEffect second argument are primitives, you need to use some deep compare methods for those: https://stackoverflow.com/a/54096391/4468021
Actually, you have wrong useEffect usage.
Effect would be called each time when component receive new props, so, it looks like this:
1) Component mounts, call function from useEffect
2) It makes API call, update data in store
3) Data passed to container, updates "dumb" component
4) Dumb component makes re-rendering, calling func from useEffect, and that's infinity loop.
In fact, It is pretty weird that you don't have memory leak.
What you can do, is:
1) Add some conditional rendering. I pretty sure, you need to call it only on initial load.
2) Add something like ImmutableJS, it would not re-render component and would not mutate store if nothing has changed
When a button is clicked I call the following:
this.props.dispatch(addNote(this.state.noteContent))
This in turn calls the following action:
export default function addNote(note){
return dispatch => {
axios.post('http://localhost:2403/notes', {
Body: note
})
.then(function (response) {
dispatch({
type: ADD_NOTE,
payload: response.data
})
})
.catch(function (error) {
console.log(error)
})
}
}
My reducer then updates the state and a new note is added to the list, so it's all working correctly.
However I now want to show a message in the UI which says "Note has been added successfully". Ideally, this would be a part of a wider notifications system, but for now, I just need to figure out how to report success (or failure) on this one action.
How do I achieve this?
You can return a promise from your dispatch using redux-thunk, why not use that? Try this code in your thunk:
export default function addNote(note){
return dispatch => {
return axios.post('http://localhost:2403/notes', {
Body: note
})
... //function continues
Then you can do:
this.props.dispatch(addNote(this.state.noteContent)).then(successFunc, failureFunc);
successFunc = function() {
//Show message on UI
}
You can use a toast component (like this https://www.npmjs.com/package/react-toastify) that sits on top of your routes in your app that renders every time an action in redux is called.
In each of your action functions, you can have a call to a message reducer that updates the state inside message reducer. Each time the state in that reducer is changed, the toast component will re-render and last for a certain amount of time or until a user manually closes the toast component.
I want to make a put request with Axios and I got this inside of my action so far:
export function updateSettings(item) {
return dispatch => {
console.log(item)
return axios.put(`/locks`).then(response => {
console.log(response)
})
}
}
When I console.log item I can see all of things I've typed in my input boxes inside of that object, but later I get 404. I know that I have that URI. Does anyone know how to troubleshoot this problem?
a put response will need an object to send with. the correct axios for put is this:
export function updateSettings(item) {
return dispatch => {
console.log(item)
return axios.put(`/locks`, item).then(response => {
console.log(response)
})
}
}
this is most likely why you get the error because the object to PUT with is undefined.
You can watch this list on the link below, to how to make the correct requests with axios. Axios request methods
A PUT request requires an identifier(e.g id) for the resource and the payload to update with. You seem not to be identifying the resource you want to update, hence 404.
you'd need an id and item like this.
export function updateSettings(id, item) {
return dispatch => {
console.log(item)
return axios.put(`/locks/${id}`, item).then(response => {
console.log(response)
})
}
}