How to handle async data in ComponentDidUpdate? - reactjs

I'm new to React and got some questions now.
I use componentDidUpdate to fetch data when the button is clicked. I manage the results data with redux. It's seem like componentDidUpdate is async function, because "console.log" always execute first before the fetch function is done. So I always got [] in the console the first time I clicked the button.
Suppose I have 2 actions I want to do when I clicked the button. First fetching the data, after that use that data and pass to another actions. How can I wait until the fetch data done after that the console.log is execute? Please help me.
state = {
searchClick: false,
keyword: "pizza",
};
componentDidUpdate(prevProps, prevState) {
if (
this.state.searchClick &&
prevState.searchClick !== this.state.searchClick
) {
// send get request to fetch data
this.props.searchResults(this.state.keyword);
//Update searchClick state
this.setState({ ...this.state, searchClick: false });
// console log the result data
console.log(this.props.results);
}
}

The console log always executes first because searchResults is async. What you need to do is returning a promise inside searchResults:
const searchResults = (keywords) => {
// fetch with the keywords and return the promise
// or any promise that resolves after your search is completed
return fetch(...);
};
componentDidUpdate(prevProps, prevState) {
if (
this.state.searchClick &&
prevState.searchClick !== this.state.searchClick
) {
// send get request to fetch data
this.props.searchResults(this.state.keyword).then(() => {
//Update searchClick state
this.setState({ ...this.state, searchClick: false });
// console log the result data
console.log(this.props.results);
});
}
}

Related

response is shown in network but returns empty array when logged in console (React)

I'm fetching data from api to render it on a component. The network tab on inspect shows the response object.
My code of the component here:
`
const NormalDetails = (props) => {
const [allCustomers, setAllCustomers] = useState([]);
useEffect(() => {
const getAllCustomers = () => {
try {
const response = salesService.getAllCustomers();
if (response.status == 200) {
setAllCustomers(response.data.object);
}
} catch (error) {
console.log("error fetching data");
}
};
getAllCustomers();
console.log(allCustomers);
}, []);
`
But while logging the response in console, it returns an empty array:
According to the React official docs, state updates may be asynchronous, meaning you may not see the updated state instantly.
You are logging allCustomers just after you update the state with setAllCustomers to response.data.object. You might be actually logging the initial state ([]) to the console instead of the updated one. try logging the response data itself instead of the state to assure correct response, or add some checking mechanism to useEffect to log the state only if its not [].

How to access redux state after dispatch is finished fully?

I am making a Create Operation. When the user clicks on onSubmit I am dispatching an action that creates a resource in the database and updates the store with the information that is returned from API call which contains information like status(indicating the status of operation) and error or message.
const initialState = {
loading: true,
status: false,
error: false
}
Above is my initial state of redux store
const {loading,status} = useSelector(state=>state.newUser)
I am consuming the state using useSelector
const addUser = (values) => async (dispatch) => {
const response = // Makes an API call return information
dispatch({type:"ADD_USER",payload:response.data})
}
onSubmit = (values)=>{
dispatch(addUser(values))
.then(data=>{
console.log(data)
})
if(!status){
}
}
In the above onSubmit function when the user submits the form it dispatches an action and updates the store. Now based on the new state I want to update the UI. After dispatching I am checking whether the status of the operation is true or false. If false I want to display the error. The problem is after dispatching If I try to access status it gives me the status value from initialState. I want to execute the if(!status) after the states get updated.
In Redux everything is declarative, you can't wait for the async action like you do on promise calls. Instead you have to dispatch an action for failure and store the error in the reducer.
Your thunk should look like something below
const addUser = (values) => async (dispatch) => {
dispatch({type:"ADDING_USER"}) // You can use this for showing some loading state
const response = await getDataFromAPI(values);
if(response.status === 200) {
dispatch({type:"ADD_USER_SUCCEEDED", data: response.data});
} else {
dispatch({type:"ADD_USER_FAILED", error: "API Failed" });
}
}
And then in the component you have to listen for all the values that you need and write rendering logic declaratively based on the values.
const {loading,status,error} = useSelector(state=>state.newUser)
if(error)
return <p>API Call failed</p>;
else if(loading)
return <p> Loading </p>;
else
return <p> Data saved </p>
If you need to trigger some logic based on it then you should be doing that in the thunk itself. By any chance if you need to trigger some function from the component when the value in the redux state changes, then you have to use useEffect and pass the value as dependency.
useEffect(() => triggerFunction, [status]);
In Redux:
First of all, it's preferred to update your state to include only status and its value should be changed depending on the status of the request.
status = 'idle' or
status = 'loading' or
status = 'succeded' or
status = 'failed'
Second, in your addUser function you should use the await keyword before the request.
Third, you should check the status of the request if it failed or succeeded, this can be done inside the addUser function and dispatch one of the two actions one for failure and one for success to update the status, or you can do this after dispatching your action `({type:"ADD_USER",payload:response.data}) and check the response data in the reducer to update the status.
In the UI:
You need to access the data through useSelector as you did, but to take an action after a form submission which in turn will update the status in the store. You should do this in useEffect hook.
useEffect(() =>
if (status === 'failed') {
// somecode
} else if (status === 'succeeded') {
// somecode
}
}, [status]);

Why is my if statement always giving the same result in React?

I have a button that changes the state of sort to either true or false. This is being passed to the App component from a child using callbacks. The first console log in the refinedApiRequest gives the value of true and then false and then true and so on as the button is click. However, the if statement keeps resulting in the else result regardless of the state of sort (true or false). What is causing this?
I want to assign a variable a value depending on the state of sort so that I can feed this value into the params of a async api get request. Thanks in advance.
class App extends React.Component {
state = { pictures: '', sort: '' };
onSortSelect = sort => {
this.setState({ sort: sort }, () => this.refinedApiRequest(sort));
}
async refinedApiRequest(sort) {
console.log(sort);
if(sort == 'true') {
console.log('its true');
} else {
console.log('its false');
}
const res = await axios.get('url', {
params: {
order: {a variable}
}
});
this.setState({ pictures: res.data });
console.log(this.state.pictures);
}
While the optional callback parameter to this.setState is guaranteed to only be executed when the component is re-rendered, in your case it still closes over the sort value of the previous render (ref: How do JavaScript closures work?).
I'd suggest following reacts advice:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
from: https://reactjs.org/docs/react-component.html#setstate
(emphasis by me)

setState is not updating in my other method

I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )

Can't set state with axios get request?

So I want to set my state for my bookings to be my result.data, the request is working and I can console.log(result.data). However when I try to set the state and then check that state, it's empty.
What is going on? Does it have something to do with my params I'm passing to axios?
My initial state looks like: this.state = {bookingArrayByDate: []}
Tried setting the state outside "then" but that didn't work either
componentDidMount() {
let today = moment(new Date())
let dateToSend = today.format('YYYY-MM-DD')
axios
.get(
`http://localhost:8888/booking-backend/fetch-reservation.php/`,
{ params: { res_date: dateToSend } }
)
.then((result: any) => {
console.log(result.data)
this.setState({
bookingArrayByDate: result.data
})
})
console.log('this is outside', this.state.bookingArrayByDate)
}
The array at the bottom will be empty when i console log it.
Your code is fine. What you are experiencing is the standard asynchronous behavior ofthis.setState(). When you console.log() your state, there is no guarantee that it was updated in the time between your setState() and the console.log() execution.
In fact, its almost 100% unlikely that it was changed within that time, because React actually batches setState() requests by putting them in a queue until the entire event is completed. In your case, the setState() wouldn't actually occur until after the componentDidMount() logic finishes.
What you can do is pass a call-back as the 2nd argument to this.setState(), that call-back is executed directly after the state has finished updating, thus ensuring you are printing the updated state.
componentDidMount() {
let today = moment(new Date())
let dateToSend = today.format('YYYY-MM-DD')
axios
.get(
`http://localhost:8888/booking-backend/fetch-reservation.php/`,
{ params: { res_date: dateToSend } }
)
.then((result: any) => {
this.setState({
bookingArrayByDate: result.data
}, () => console.log(this.state.bookingArrayByDate)) <-- you can swap this whatever function you need.
})
}

Resources