ReactJS can not access response object inside setState - reactjs

I am trying to update the setSet as part of output from my RestAPI. However I am getting an error that response object is undefined. I am able to log it outside setState method.
Code
addNewTodo = () => {
axios.post('http://localhost:5001/todos', "task="+this.state.newTodoList.task)
.then(response=>console.log(response.data))
.then(response=>{
this.setState(prevState=>({
TodoList: prevState.TodoList.push(response.data),
}))
});
{this.toggleNewTodoModal()}
}
I get following log in console before error
{task: "ddd", id: "todo10"}
Error:
TypeError: Cannot read property 'data' of undefined
at following line
TodoList: prevState.TodoList.push(response.data),

So your first .then returns a console log, meaning your second .then will no longer have any values. If you change your code to this:
Regarding pushing new Data to react state array, The recommended approach in later React versions is to use an updater function when modifying states to prevent race conditions. So pushing new Data to state array should be something like below
axios
.post('http://localhost:5001/todos', 'task=' + this.state.newTodoList.task)
.then(response => {
console.log(response.data);
this.setState(prevState => ({
TodoList: [...prevState.TodoList, response.data],
}));
});
It should work just fine. You can chain .then as much as you like, as long as you return some values, and not a console log, for example, in the fetch:
fetch('some_url', {
method: 'GET',
})
.then(res => res.json()) // this returns the data
.then(data => console.log(data)) // this has access to the data

My state object was a map, and so following worked for me.
State
state = {
TodoList: {},
}
Updating State
axios
.post('http://localhost:5001/todos', 'task=' + this.state.newTodoList.task)
.then(response => {
const {id, task} = response.data
this.setState(prevState => ({
TodoList: {...prevState.TodoList,
[id]: task},
}));
});

Related

React + Fetch API. Can't set response in setState

I know this has been asked many times on this site but after going through SO questions related to this for the past 5 hours I have to throw in the towel and see if there's someone that can identify what I'm doing wrong here.
I have a fetch request in my react application that I am successfully receiving a response from but I am unable to store the response in my state. It seems to me that everything looks correct but when I attempt to store the response it simply does nothing. There are no console errors in the browser nor in my console that is running the react app. Currently the related code looks like this (Some things are slightly modified for privacy).
loginSubmission = () => {
fetch('https://genericsite.com/auth', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({"username": this.state.username, "password": this.state.password})
})
.then(res => res.json())
.then(res => {this.setState({response: res}, () => this.sendResponse())})
.catch(error => {
console.log(error);
});
}
sendResponse(){
console.log(this.state.response)
let data = {response: this.state.response};
this.props.receiveResponse(data);
}
If I do it like how I have it below though I'm able to console.log the response with no issues but from what I was reading in a similar question there's something about console.log that forces it to complete the request so it can log the result.
loginSubmission = () => {
fetch('https://genericsite.com/auth', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({"username": this.state.username, "password": this.state.password})
})
.then(res => res.json())
.then(res => {console.log('res.response'})
.catch(error => {
console.log(error);
});
}
That returns the following object:
{token: 'bigLongJumbledToken', idtoken: '', exp: 1655106045, username: 'myusername'}
exp: 1655106045
idtoken: ""
token: "bigLongJumbledToken"
username: "myusername"
[[Prototype]]: Object
And my state in this component looks like so:
this.state = {
username: '',
password: '',
response: {}
}
this.userOnChange = this.userOnChange.bind(this);
this.passOnChange = this.passOnChange.bind(this);
this.loginSubmission = this.loginSubmission.bind(this);
}
Thanks in advance for any help with this.
In this line: .then(res => {this.setState({response: res}, () => this.sendResponse())}) you are calling the setState with two arguments, it should be only one. I think that you want to store the response in the state and also execute sendResponse function with the response data but, even after you fix the call of setState the function sendResponse will not receive the updated state because react will wait to finish the current executing function that is .then() before actually update the state.
You have two ways of do what (i guess) you are trying to do:
First: use the response directly to call sendResponse
Second: use componentWillUpdate to call sendResponse after state updates
I'll give an example of the first approach cause I think is the cleanest:
.then(res => {
this.setState({response: res})
this.sendResponse(response)
})
sendResponse(res){ // expects response as a parameter
console.log(this.state.response)
// let data = {response: this.state.response}; // avoid this
this.props.receiveResponse(res);
}

Dispatching function multiple times in order to execute it with different parameters

Background
I'm building a React Native 0.64.1 app using Redux 4.1.0. This app fetches data from an API endpoint via POST which can take multiple "category" params. Only one value can be passed as category at a time, so in order to display data from multiple categories one would have to execute the function one time per category.
This is how the axios request is handled:
export const getData = (tk, value) =>
apiInstance
.request({
url: ENDPOINTS.CATEGORIES,
method: 'POST',
data: qs.stringify({
token: tk,
category: value,
}),
})
.then(response => {
return response.data;
})
.catch(error => {
return Promise.reject(error.message);
});
This function is then executed via a redux action/reducer, etc.
The tricky part is that "value" is set by the user and can be changed at any point in time.
The front end meets this function in a certain screen where this happens:
useEffect(() => {
dispatch(retrieveData(tk, value));
}, [dispatch, value]);
Problem & Question
I've tried doing a for loop that would iterate through an array that contains the possible strings of text value could be, that would look something like this:
const arrayOfValues = ['A','B','C','D']
let value = null;
useEffect(() => {
for (let i = 0; i < arrayOfValues.length; i++) {
value = arrayOfValues[i];
dispatch(retrieveData(tk, value));
}
}, [dispatch, value]);
I know this is horrible and I'm just showing it because it's the only thing I could think about (and it doesn't even work).
An ideal solution would:
Execute the first request on load
Run a request once per item in an array WITHOUT deleting the previously called for data
Each time it runs it needs to update the "value" parameter.
As a note about "retrieveData()", that is just the redux action.
Any help would be very much appreciated.
Solution by #rigojr
This seems like it should work, but either I haven't expressed myself properly or there's something wrong with the answer. I'm guessing it's the former.
#rigojr proposed the following:
export const getData = (tk, values) => values.map((value) => apiInstance
.request({
url: ENDPOINTS.CATEGORIES,
method: 'POST',
data: qs.stringify({
token: tk,
category: value,
}),
}))
Promise.all(getData(tk,values)) *****
.then(responseValues => {
// Dispatch the response, it will come an array of values response.
})
.catch(eer => {
// Error handling
})
Howeve, values in the line marked with many asterisks is inaccessible. I believe this is because previosuly I failed to mention that the whole Redux data flow happens in three separate files.
Dispatching the action: UI dispatches an action onLoad in App.js:
useEffect(() => {
dispatch(retrieveData(tk, values));
}, [dispatch, value]);
The action is ran in action.js file. It looks something like this:
Note that I have added the Promise.all() in this screen, as it seems like the place where it should actually go, instead of the other one.
export const actionTypes = keyMirror({
RETRIEVE_REQUEST: null,
RETRIEVE_SUCCESS: null,
RETRIEVE_FAILURE: null,
});
const actionCreators = {
request: createAction(actionTypes.RETRIEVE_REQUEST),
success: createAction(actionTypes.RETRIEVE_SUCCESS),
failure: createAction(actionTypes.RETRIEVE_FAILURE),
};
export const retrieveData = (tk, values) => dispatch => {
dispatch(actionCreators.request());
Promise.all(getData(tk, values))
.then(data => dispatch(actionCreators.success(data)))
.catch(error => dispatch(actionCreators.failure(error)));
};
Then there's the reducer, of course in reducer.js:
export const initialState = {
loadingData: false,
data: [],
error: null,
};
const actionsMap = {
[actionTypes.RETRIEVE_REQUEST]: state => ({
...state,
loadingData: true,
}),
[actionTypes.RETRIEVE_SUCCESS]: (state, action) => ({
...state,
loadingData: false,
data: action.payload,
}),
[actionTypes.RETRIEVE_FAILURE]: (state, action) => ({
...state,
loadingData: false,
error: action.payload,
}),
};
export default (state = initialState, action) => {
const actionHandler = actionsMap[action.type];
if (!actionHandler) {
return state;
}
return actionHandler(state, action);
};
Data is then accessed via a selector:
const data = useSelector(state => state.data.data);
When running the code above, I am greeted with the following lovely error message:
TypeError: undefined is not a function (near '...}).then(function (response)...')
And in the emulator, I get pointed in the direction of these lines of code:
export const getData = (tk, values) => values.map((value) => apiInstance
.request({
url: ENDPOINTS.CATEGORIES,
method: 'POST',
data: qs.stringify({
token: tk,
category: value,
}),
}))
More specifically, the emulator seems to think that the error has to do with value.map, as it points a little red arrow at "values" just before the method.
Any idea on what went wrong?
Note
Upon refresh the error might change, for example just now it has shown the same error message but it points in the direction of
export const retrieveData = (tk, values) => dispatch => {
dispatch(actionCreators.request());
Promise.all(getData(tk, values))
.then(data => dispatch(actionCreators.success(data)))
.catch(error => dispatch(actionCreators.failure(error)));
};
More specifically, the little red arrow points at getData.
Refreshing again, and the error points at
useEffect(() => {
dispatch(retrieveData(tk, values));
}, [dispatch, value]);
Refrsh once more and it just loses it and goes for a module, as shown in the image:
It doesn't go further from there. Just mind that every single time, the error message is TypeError: undefined is not a function (near '...}).then(function (response)...'), it just points in a new direction.
Solved in
Unable to perform .map whithin function
Try to use a Promise.all():
export const getData = (tk, values) => values.map((value) => apiInstance
.request({
url: ENDPOINTS.CATEGORIES,
method: 'POST',
data: qs.stringify({
token: tk,
category: value,
}),
}))
Promise.all(getData(tk,values))
.then(responseValues => {
// Dispatch the response, it will come an array of values response.
})
.catch(eer => {
// Error handling
})
Read more about Promise.all() here

How to send updated state in axios in React?

I am trying to send post request using axios in Reactjs.
I have two component a timer component and App component and in App component i am trying to submit a form and send an axios call when i fetch the time from Timer component and save itinto counter state
I have written a condition if counter is true then update my state and then further send the post request
Working Demo
here is a handle submit code:
const handleSubmit = e => {
console.log("handleSubmit");
e.preventDefault();
if (counter) {
console.log(counter);
const url = `url string`;
setState({
...state,
lastn: {
attestedTime: myDateFunc(),
time: counter
}
});
console.log(state);
axios
.post(url, state)
.then(response => {
console.log(response);
console.log(response.data);
})
.catch(error => {
console.log(error);
});
}
};
The problem is when counter is true its not update the state which causes error while send axios request.
I have consoled each and every thing but still it fails.
It seems there is lot of rendering.
If you are using class components, you can make the reuqest after the state has been set. Something like this:
this.setState({
...state,
lastn: {
attestedTime: myDateFunc(),
time: counter
}
}, () => {
axios
.post(url, state)
.then(response => {
console.log(response);
console.log(response.data);
})
.catch(error => {
console.log(error);
});
});
Since you did set the react-hooks tag, I guess that approach is not what you need. In your case, I suggest saving new state in some temporary variable and than passing that variable to axios. Like this:
const newState = {
...state,
lastn: {
attestedTime: myDateFunc(),
time: counter
}
};
setState(newState);
axios
.post(url, newState)
.then(response => {
console.log(response);
console.log(response.data);
})
.catch(error => {
console.log(error);
});
setState can be executed asynchronously by React, to optimize the rendering process. For cases like this one, it can also take a callback function that is guaranteed to be executed after updating the state.
For example:
this.setState({
name:'value'
},() => {
console.log(this.state.name);
});
in this case console.log will be executed after setting the name variable.
see the docs: https://reactjs.org/docs/react-component.html#setstate

Cannot fetch api due to array react native

I bulid an api using laravel which can run in postman (http://lkcfesnotification.000webhostapp.com/api/notifications). The problem is when i fetch using an example from this (https://www.youtube.com/watch?v=IuYo009yc8w&t=430s) where there is a array in the api then i have to setstate the array which is working well but when i try using the below code it does not render due to it is not using array in the api for example the random user api have "results" :[item], and mine one is "data":[my item]
fetchData = async () => {
const response = await fetch("https://randomuser.me/api?results=500");
const json = await response.json();
this.setState({ data: json.results });
};
if i use this will work but i want to use below code due to some homework i am doing
type Props = {};
export default class IndexScreen extends Component<Props> {
...
this.state = {
data: [],
isFetching: false,
};
_load() {
let url = "http://lkcfesnotification.000webhostapp.com/api/notifications";
this.setState({isFetching: true});
fetch(url)
.then((response) => {
if(!response.ok) {
Alert.alert('Error', response.status.toString());
throw Error('Error ' + response.status);
}
return response.json()
})
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
.catch((error) => {
console.log(error)
});
}
https://imgur.com/a/he5mNXv this is my render
the result i get the code i run is blank is loading
The fetch request is working but you are not saving the right data in the right state property.
The issues is located in the following part:
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
You are assigning the response to a variable members but saving another variable data, which does not exist.
In addition, the response is an object with more information than just the data, so what you are looking for is just the data property of the response.
This should work:
.then(({ data }) => {
this.setState({data});
this.setState({isFetching: false});
})
Here we destructure the response into the variable { data }, solving your issue.
Based on the snippets you don't use the fetched data to set it to your state:
.then((members) => {
this.setState({data});
this.setState({isFetching: false});
})
membersis the result of your fetched json. So either rename members to data or use data: members. If the code should work like your first function it's probably data: members.result. You can also combine the two setState calls to one single call:
this.setState({
data: members.result,
isFetching: false,
});

How do I return a promise in a redux action dispatch call so I can chain .then blocks?

I am creating an app using react-native and redux. My app should send a request to a firebase rest api to get back a user's todo's. In my react-native app, I get this response through a redux function that is passed in the connect part of the react-native components. I would like to chain a .then block right after I call my function.
I have tried creating a promise and returning that in my action but it automatically resolves to an error when chaining the .then / .catch
I have also tried doing the same without creating a promise.
Here is my action:
export const fetchHomework = () => {
return (dispatch, getState) => {
dispatch(uiStartLoading());
dispatch(uiStartFetchingHomework());
dispatch(authGetToken())
.catch(err => {
dispatch(errHandler(err))
})
.then(token => {
const uid = getState().userid;
fetch(restAPI)
.catch(err => {
dispatch(errHandler(err));
})
.then(res => res.json())
.then(response => {
dispatch({
type : 'SET_HOMEWORK_FOR_AGENDA',
homework : response
})
dispatch(uiStopLoading());
dispatch(uiStopFetchingHomework());
})
.catch(err => {
dispatch(errHandler(err));
dispatch(uiStopLoading());
dispatch(uiStopFetchingHomework());
})
})
dispatch(uiStopLoading());
dispatch(uiStopFetchingHomework());
}
}
Note: rest api is replaced with the url of the rest api
And here is where I fetch this data:
this.setState({refreshing: true});
this.props.retrieveHomework();
this.setState({refreshing: false, firebaseItems : this.props.homework});
this.loadItems(this.state.selectedDay);
(This is called in a function when refreshing)
I expected that when I chain a .then block after retrieveHomework, the then block would wait for the function to finish and then run the code inside, but this is not what is happening. What is happening is it either skips the then blocks or throws an error that the catch block catches.
Edit:
this.props.retrieveHomework is a function that points to the async action since I am using redux thunk.
You need to move all of the code you want to wait to run into the .then:
this.props.retrieveHomework()
.then(() => {
this.setState({refreshing: false, firebaseItems : this.props.homework});
this.loadItems(this.state.selectedDay); // If this is async, you need to `return` it here as well
});

Resources