(React) setState() callback not firing after state change - javascript

I am using React for this project.
I need to read the state directly after I set it (using the callback), but when I print the state to the screen using the callback I get the values of the previous state. Using devtools I can see that the state does change, so that is not the problem. I looked at the docs and it said the callback is supposed to fire after the state is changed and the component updates, but I am not seeing that behaviour in my component. I am also not getting any error messages.
Here is the code, thanks for your help!
onAnswerSelect = (e) => {
const selectedAnswer = e.target.value;
//Set the state, then simulate a click on the submit button
this.setState(() => ({selectedAnswer}), this.simulateClick(e))
}
simulateClick = (e) => {
console.log(this.state.selectedAnswer)
e.target.parentNode.parentNode.firstChild.click()
}

You are not passing function to setState callback but calling it. You can pass a function and parameter using bind function like this
this.setState(() => ({selectedAnswer}), this.simulateClick.bind(this,e))
The other way is to use a closure. You can make simulateClick function to return a function like this
simulateClick = (e) => () => {
console.log(this.state.selectedAnswer)
e.target.parentNode.parentNode.firstChild.click()
}
this.setState(() => ({selectedAnswer}), this.simulateClick(e))

Related

How to works setState function?

I want to create a basic crud app. So i want to create a basic live search. For this reason, i have written a simple code. Like this
searchProduct = (e) => {
const query = e.target.value.toLowerCase();
const data = [...this.state.exampleProducts];
const filteredData = data.filter(p => {
return p.name.toLowerCase().includes(query);
})
console.log(filteredData);
}
When I use the setState function, the console.log output gives different results.
guessing you are new in Reactjs, please check this one first https://css-tricks.com/understanding-react-setstate/
Do not depend on this.state immediately after calling setState() and make use of the updater function instead.
setState has a callback function that you can use, but DO NOT use setState inside of it.
When I need to log something inside the state, prefer to use console.log in the render function in this way you won't miss anything.
The setState dose doesn't apply the change immediately. You can pass callback function like this:
this.setState({value: 'updated-value'}, () => {
console.log(this.state.value); // updated-value
// You can use your updated state here
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

React state inside a function is not changing even after calling it with a delay of (5 seconds)

In react I am using functional component and I have two functions (getBooks) and (loadMore)
getBooks get data from an endPoint. But when I call loadMore function on button click inside the getBooks function (loadMoreClicked) is not changed it uses the previous state even after calling it with a delay of (5 seconds). But when I call loadMore again the state changes and everything works fine.
can someone explain why the (loadMoreClicked) on the initial call to (getBooks) didn't update
even calling it after 5 seconds delay.
function component() {
const [loadMoreClicked, setLoadMore] = useState(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`; //this is my end point
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(loadMoreClicked); //the (loadMoreClicked) value is still (false) after (5 sec)
})
.catch(err => {
console.log(err);
});
};
const loadMore = () => {
setLoadMore(true); //here i am changing (loadMoreClicked) value to (true)
setTimeout(() => {
getBooks(); // i am calling (getBooks()) after 5 seconds.
}, 5000);
};
return (
<div>
<button onClick={() => loadMore()}>loadMore</button> //calling (loadMore)
function
</div>
);
}
There's two things going on:
getBooks() is using const values that are defined in the surrounding function. When a function references const or let variables outside of its definition, it creates what's called a closure. Closures take the values from those outer variables, and gives the inner function copies of the values as they were when the function was built. In this case, the function was built right after the state was initially called, with loadMoreClicked set to false.
So why didn't setLoadMore(true) trigger a rerender and rewrite the function? When we set state, a rerender doesn't happen instantaneously. It is added to a queue that React manages. This means that, when loadMore() is executed, setLoadMore(true) says "update the state after I'm done running the rest of the code." The rerender happens after the end of the function, so the copy of getBooks() used is the one built and queued in this cycle, with the original values built in.
For what you're doing, you may want to have different functions called in your timeout, depending on whether or not the button was clicked. Or you can create another, more immediate closure, based on whether you want getBooks() to consider the button clicked or not, like so:
const getBooks = wasClicked => // Now calling getBooks(boolean) returns the following function, with wasClicked frozen
() => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(wasClicked); // This references the value copied when the inner function was created by calling getBooks()
})
.catch(err => {
console.log(err);
});
}
...
const loadMore = () => {
setLoadMore(true);
setTimeout(
getBooks(true), // Calling getBooks(true) returns the inner function, with wasClicked frozen to true for this instance of the function
5000
);
};
There is a third option, which is rewriting const [loadMoreClicked, setLoadMore] to var [loadMoreClicked, setLoadMore]. While referencing const variables freezes the value in that moment, var does not. var allows a function to reference the variable dynamically, so that the value is determined when the function executes, not when the function was defined.
This sounds like a quick and easy fix, but it can cause confusion when used in a closure such as the second solution above. In that situation, the value is fixed again, because of how closures work. So your code would have values frozen in closures but not in regular functions, which could cause more confusion down the road.
My personal recommendation is to keep the const definitions. var is being used less frequently by the development community because of the confusion of how it works in closures versus standard functions. Most if not all hooks populate consts in practice. Having this as a lone var reference will confuse future developers, who will likely think it's a mistake and change it to fit the pattern, breaking your code.
If you do want to dynamically reference the state of loadMoreClicked, and you don't necessarily need the component to rerender, I'd actually recommend using useRef() instead of useState().
useRef creats an object with a single property, current, which holds whatever value you put in it. When you change current, you are updating a value on a mutable object. So even though the reference to the object is frozen in time, it refers to an object that is available with the most current value.
This would look like:
function component() {
const loadMoreClicked = useRef(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
console.log(loadMoreClicked.current); // This references the property as it is currently defined
})
.catch(err => {
console.log(err);
});
}
const loadMore = () => {
loadMoreClicked.current = true; // property is uodated immediately
setTimeout(getBooks(), 5000);
};
}
This works because, while loadMoreClicked is defined as a const at the top, it is a constant reference to an object, not a constant value. The object being referenced can be mutated however you like.
This is one of the more confusing things in Javascript, and it's usually glossed over in tutorials, so unless you're coming in with some back-end experience with pointers such as in C or C++, it will be weird.
So, for what you are doing, I'd recommend using useRef() instead of useState(). If you really do want to rerender the component, say, if you want to disable a button while loading the content, then reenable it when the content is loaded, I'd probably use both, and rename them to be clearer as to their purpose:
function component() {
const isLoadPending = useRef(false);
const [isLoadButtonDisabled, setLoadButtonDisabled] = useState(false);
const getBooks = () => {
const endPoint = `http://localhost/getBooks`;
axios
.get(endPoint, {
params: newFilters
})
.then(res => {
if (isLoadPending.current) {
isLoadPending.current = false:
setLoadButtonDisabled(false);
}
})
.catch(err => {
console.log(err);
});
};
const loadMore = () => {
isLoadPending.current = true;
setLoadButtonDisabled(true);
setTimeout(getBooks(), 5000);
};
}
It's a little more verbose, but it works, and it separates your concerns. The ref is your flag to tell your component what it's doing right now. The state is indicating how the component should render to reflect the button.
Setting state is a fire-and-forget operation. You won't actually see a change in it until your component's entire function has executed. Keep in mind that you get your value before you can use the setter function. So when you set state, you aren't changing anything in this cycle, you're telling React to run another cycle. It's smart enough not to render anything before that second cycle completes, so it's fast, but it still runs two complete cycles, top to bottom.
you can use the useEffect method to watch for loadMoreClicked updates like componentDidUpdate lifecycle method and call the setTimeout inside that,
useEffect(() => {
if(loadMoreClicked){
setTimeout(() => {
getBooks();
}, 5000);
}
}, [loadMoreClicked])
this way only after the loadMoreClicked is changed to true we are calling the setTimeout.
This boils down to how closures work in JavaScript. The function given to setTimeout will get the loadMoreClicked variable from the initial render, since loadMoreClicked is not mutated.

Best practise to handle with responses and incoming props

with redux, we uses actions to handle with crud operations. But I stuck at some points. If we send async requests inside of component. We can easly handle with response. But when we send request through actions, we dont know what happened. Is request send successfully ? it took how much amount of time ? What kind of response is returned ? we don't know that
I will clarify question with samples..
lets update a post.
onClick () {
postsApi.post(this.state.post) | we know how much time
.then(res => res.data) | has took to execute
.then(res => { | request
console.log(res) // we have the response
})
.catch(err => console.log(error))
}
But if we use actions
onClick () {
this.props.updatePost(this.state.post) // we know nothing what will happen
}
or handling with incoming props. lets say I have fetchPost() action to retrieve post
componentDidMount(){
this.props.fetchPost()
}
render method and componentDidUpdate will run as well. It's cool. But what if I want to update my state by incoming props ? I can't do this operation inside of componentDidUpdate method. it causes infinity loop.
If I use componentWillUpdate method, well, things works fine but I'm getting this warning.
Warning: componentWillReceiveProps has been renamed, and is not
recommended for use. Move data fetching code or side effects to
componentDidUpdate. If you're updating state whenever props change,
refactor your code to use memoization techniques or move it to static
getDerivedStateFromProps
I can't use componentDidUpdate method for infinty loop. Neither getDerivedStateFromProps method because it's run everytime when state change.
Should I continue to use componentWillMethod ? Otherwise what should I use and why (why componentWillMethod is unsafe ?)
If I understand correcty, what you would like to do is to safely change your local state only when your e.g. updatePost was successful.
If indeed that is your case, you can pass a callback function on your updatePost and call this as long as your update was succefull.
successfulUpdate() {
// do your thing
this.setState( ... );
}
onClick () {
this.props.updatePost(this.state.post, this.successfulUpdate) // we know nothing what will happen
}
UPDATE:
You can also keep in mind that if your action returns a promise, then you can just use the then method:
onClick () {
this.props.updatePost(this.state.post).then(this.onFulfilled, this.onRejected)
}
I think we can use redux-thunk in this cases. What if we dispatch an async function instead of dispatch an action object?
"Neither getDerivedStateFromProps method because it's run everytime when state change." - does it matter? You can avoid setting state with every getDerivedStateFromProps call by using a simple condition inside.
Example:
static getDerivedStateFromProps(props, state) {
if (props.post !== state.post) { // or anything else
return {
post: props.post,
};
}
return null;
};
An infinite loop will not occur.
Here is my way for such cases. We can redux-thunk for asynchronous calls such as api call. What if we define the action that returns promise? Please check the code below.
actions.js
export const GetTodoList = dispatch => {
return Axios.get('SOME_URL').then(res => {
// dispatch some actions
// return result
return res.data;
});
}
TodoList.js
onClick = async () => {
const { GetTodoList } = this.props;
try {
const data = await GetTodoList();
// handler for success
this.setState({
success: true,
data
});
} catch {
// handler for failure
this.setState({
success: fail,
data: null
});
}
}
const mapStateToProps = state => ({
GetTodoList
});
So we can use actions like API(which returns promise) thanks to redux-thunk.
Let me know your opinion.

API call on event in react, componentDidUpdate or eventHandler?

Where should we ideally place an api call to be made on occurrence of an event in React
Inside the eventHandler or componentDidUpdate ?
example:
handleItemClick = (item) => (event) => {
this.setState({selectedItem: item});
this.props.requestDataActionDispatch(item);
}
OR
componentDidUpdate(prevProps, prevState, snapshot) {
if(prevState.item !== this.state.item) {
this.props.requestDataActionDispatch(item);
}
}
Depends
But a simple solution is, if you want to call some API after change of state value then you must go for eventHandler. Also check for callback in setState.
handleItemClick = (item) => (event) => {
this.setState({selectedItem: item}, () => this.props.requestData(item));
}
I don't see any reason to wait for component update, I'd just put it in the event handler.
Naturally, in either case your component needs to know how to render appropriately when it has a selected item but doesn't have the data from the API yet...
(Side note: If requestDataActionDispatch results in a state change in your component, you probably want to clear that state when setting the selected item prior to the request, so you don't have one item selected but still have the state related to the previous item. But I'm guessing from the fact it's on props that it doesn't...)
It depends
I would prefer to call the api inside componentDidUpdate. Why ? Because it's cleaner. Whenever there is a change in state or props, componentDidUpdate will be called. So definitely, there has to be a condition inside componentDidUpdate like you have mentioned
if(prevState.item !== this.state.item)

Is calling componentDidMount within a function bad practice?

I have a function in my ReactJS app that sends an axios POST request to the server to delete a certain element from my database.
Basically, I have a list and I can delete certain items from it, however, React only displays the changes made by deleting an element after refreshing the page.
Here is the delete function that I use:
handleDelete (event) {
var id = event.target.id;
axios.get('todos/delete?id='+id)
.then(function(response) {
});
this.componentDidMount();
}
componentDidMount() fetches data from my database and stores it in the state.
I found that, if I call componentDidMount within the function, that it displays the changes right away, however, I kind of feel like that's a rather unprofessional way of doing what I'm trying to achieve.
Therefore my questions are:
Is it considered bad practice to call a lifecycle method within another function?
Is there a better way to get the page to display the changes right away?
Well you shouldn't really do that.
componentDidMount is just a lifecycle method of your component. What you want is this structure:
fetchData () { ... };
handleDelete (event) {
var id = event.target.id;
axios.get('todos/delete?id='+id)
.then(function(response) {
});
this.fetchData();
}
componentDidMount() {
this.fetchData();
}
This is a simplified example but you get the point.
NOTE: in the handleDelete function if you want the fetchData call to happen after the axios call your should but the code inside the then
axios.get('todos/delete?id='+id)
.then(() => {
this.fetchData();
});
Answer 1- No it is recommended to call lifecycle method in any function.
Answer 2- better recommednded way , call all server function in ComponentDidMount
for More refrence check here
You can do your operation in this way :-
componentDidMount(){
// do some get opration to get data from server
getFunction();
}
handleDelete (event) {
var id = event.target.id;
axios.get('todos/delete?id='+id)
.then(function(response) {
// get operation here in response
});
}
Indeed it's a bad practice. You can either use React lifecycle to update your state or directly within the handleDelete function.
handleDelete = ({ target }) => {
const { id } = target;
axios.get('todos/delete?id=='+id')
.then(response => {
const todos = this.state.todos;
todos.splice(indexOfTheItem,1);
this.setState({ todos });
})

Categories

Resources