Am try to pass the parameters to the state callback function, which has click event listener.
the parameters are not getting updated with the latest value, i always get the value which is set on first time click.
this is what i tried:
this.setState({ idx: i0 === this.state.idx ? null : i0 }, () => {
document.addEventListener('click', this.closeMenu.bind(null, i0, event));
});
closeMenu(i0, event) {
event.stopPropagation();
this.setState({ idx: null }, () => {
document.removeEventListener('click', this.closeMenu);
});
}
Am expecting the closeMenu function parameter i0 value should get always the latest value which is getting passed from parent function.
Some points to consider. Why do you setting up listeners in callbacks to setState, setState is async, who can know what can happens in this scenario ? If you not denouncing you button click (or how you trigger this functionality) what if listener will be initialized up multiple times ? Another point, you doing this in callback to setState, what if component will be unmounted in this point ? This is clearly a possibility. I think the right choice will be to setup listener in componentDidMount and remove it in componentWillUnmount. So it should be something like this:
componentDidMount(){
document.addEventListener('click', this.closeMenu);
}
componentWillUnmount(){
document.removeEventListener('click', this.closeMenu);
}
One more point, to consume props in closeMenu or anywhere just you arrow functions like so :
closeMenu = event => {
let i0 = this.props.i0;
event.stopPropagation();
...
}
This will save you from passing additional parameter as it will bind function to component context and allow you to access props directly. If you elaborate a bit on what you trying to achieve, i can try to explain better.
Related
I have some function (Promise Resolve) when onChange handler
This function will fetch data array by PromiseValue, so I use .Then( result ) to get the array field and it work when I print result with console.log.
But the problem is on this.setState because always get error with error message : _this2.setState is not a function
getList(checkboxes['myCheckbox']).then(
result => {
this.setState({ CheckBox: result })
}
).catch( err => {
console.log(err)
});
thanks before
Without more of the component, I'd guess this is probably occurring because your onChange handler in your component isn't bound to the class instance. So when the onChange fires and the promise code is executed, this.setState will not be defined.
You could make it work if you:
bound the functions in the constructor of the class component, i.e. this.handleChange = this.handleChange.bind(this)
use the class fields syntax, i.e. const handleChange = () => { // your code };
converted the component to a functional component and used hooks, i.e. useState;
I could be wrong though as I don't have the full text of the code! Hope this helps :)
Inside Promise.then(). this always refers to the callback function itself. so there would be no setState function.
Try defining a new function to save the result into the state.
First let me put my code here and I'll explain what's happening.
Parent.js
callback = (id) => {
this.setState({des: id});
console.log(this.state.des);
}
//on my render i have this when i call my child component
<Child callback={this.callback}/>
Child.js
handleChange = (event) => {
let des = event.target.value;
console.log(des);
this.props.callback(des);
};
When i console.logon my Child component, it returns the data that i want to pass, but when I do it in calbackon my Parent component, it returns <empty string> and i don't know why that's happening.
The reason this is happening is because setState is an async function. When you are trying to log this.state.des, the state would not have been set yet. If you want to console log your state to see if it has been set as expected, what you want to do is log it in the callback of this.setState (so it logs once we know state is set). Try something like the following in your parent.js :
callback = (id) => {
this.setState({des: id}, () => {
console.log(this.state.des);
});
}
see the React Docs for setState for more details
The call to setState is asynchronous and therefore you might not read the updated state if you are accessing it directly after calling setState. Because of this setState(updater[, callback]) actually exposes a callback which can be used for operations which depend on the state update being done. (This is explained in the react docs for setState.)
In your case, adjusting the callback function like this
callback = (id) => {
this.setState({des: id}, () => {
console.log(this.state.des);
});
}
should do the trick.
If you want to know more about the reasoning behind setState being asynchronous (even if it might be a bit confusing in the beginning, like in your case) you should check out this github issue and especially this comment.
React 16 deprecates componentWillReceiveProps() lifecycle method. The preferred replacement is new getDerivedStateFromProps() or componentDidUpdate(). Now let's suppose I have some code like that:
componentWillReceiveProps(newProps) {
if (this.props.foo !== newProps.foo) {
this.setState({foo: newProps.foo}, () => this.handleNewFoo()});
}
}
I could try moving the code to getDerivedStateFromProps() like that:
getDerivedStateFromProps(newProps, prevState) {
if (this.props.foo !== newProps.foo) {
return {foo: newProps.foo};
} else {
return null;
}
}
but what do I do with the callback from setState()? Is moving it to componentDidUpdate the only option? I'd rather have this.handleNewFoo() invoked before render() to avoid visual and performance cost of two renders.
If you haven't read this blog post you should
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#when-to-use-derived-state
If you are simply assigning props.foo to state.foo without any calculation / processing then you need to ask yourself should you really need that field in state. Because you can always use this.props.foo instead of this.state.foo.
what is this.handleNewFoo method does?
are you doing another setState inside handleNewFoo? if you are then you are triggering multiple render calls.
eg.
this.setState({foo: this.props.foo} // one render call, () => this.handleNewFoo())
const handleNewFoo = () => {
this.setState({ }) // another render call
}
If you are not doing any setState inside handleNewFoo then ask yourself does handleNewFoo has to be called on prop change?
If you are using setState inside the handleFoo i would suggest something like this
getDerivedStateFromProps(newProps, prevState) {
if (this.props.foo !== newProps.foo) {
return this.handleNewFoo(this.props.foo, prevState);
} else {
return prevState;
}
}
handleNewFoo(foo, state) {
// do some calculation using foo
const derivedFoo = state.foo + foo;
// if your are using immutability_helper library.
const derivedState = update(state, { foo: {$set: derivedFoo}});
return derivedState
}
It all depends on what you are doing inside this.handleNewFoo
EDIT:
Above code won't work as #Darko mentioned getDerivedStateFromProps is static method and you cannot access this inside it. And you cannot get the previous props inside getDerivedStateFromPropslink. So this answer is completely wrong.
one solution is to use useEffect
useEffect(() => {
setState(...)
}, [props.foo])
this useEffect will call whenever the props.foo is changed. And also the component will be rendered twice one for prop change and another for setState inside useEffect. Just be careful with setState inside useEffect as it can cause infinite loop.
While I know, that this question has been asked numerous time, the usual answer is "you're passing a copy, not the original". Unless I'm understanding something wrong, this is not the problem in my case.
I made a Modal in React using the following code:
const Modal: React.StatelessComponent<Props> =
({ isOpen, closeModal, closeOnClick = true, style, children }: Props) => {
const closeOnEsc: (e: KeyboardEvent) => void = (e: KeyboardEvent) => {
e.stopImmediatePropagation();
if (e.keyCode === 27) {
closeModal();
}
};
if (isOpen) {
document.body.classList.add('freezeScroll');
window.addEventListener('keyup', closeOnEsc, { once: true });
return (
<div className={styles.modal} onClick={closeOnClick ? closeModal : undefined} >
<div className={`${styles.modalContent} ${closeOnClick ? styles.clickable : undefined} ${style}`} >
{children}
</div>
</div>
);
}
document.body.classList.remove('freezeScroll');
window.removeEventListener('keyup', closeOnEsc, { once: true });
return null;
};
I've only added the {once: true} option, because I can't remove it otherwise.
window.removeEventListener does get called once the modal is closed, but it doesn't remove the event listener.
Can anyone help me figure out why?
I wouldn't manually bind listeners in a SFC personally. If you really want to though, I would just make the callback remove the listener.
In your closeOnEsc callback, add window.removeEventListener('keyup', closeOnEsc);
The problem with this, if the component is unmounted without the callback being invoked, then you have a small memory leak.
I would use a class component, and ensure the listener is also removed in the componentWillUnmount lifecycle hook.
You should not attach event listener directly inside render. These are not idempotent side effects and it will cause memory leak. On each render (even if it is not commited to DOM), you are assigning event listeners After your modal is closed, you remove only last one. Right way to do it would be either using useEffect (or useLayoutEffect hook), or converting your component into class and using lifecycle methods.
Here is annotated example with hooks:
const Modal: React.StatelessComponent<Props> = ({
isOpen,
closeModal,
closeOnClick = true,
style,
children,
}: Props) => {
useEffect(
function () {
// we don't need to add listener when modal is closed
if (!isOpen) return;
// we are closuring function to properly remove event listener later
const closeOnEsc: (e: KeyboardEvent) => void = (e: KeyboardEvent) => {
e.stopImmediatePropagation();
if (e.keyCode === 27) {
closeModal();
}
};
window.addEventListener("keyup", closeOnEsc);
// We return cleanup function that will be executed either before effect will be fired again,
// or when componen will be unmounted
return () => {
// since we closured function, we will definitely remove listener
window.removeEventListener("keyup", closeOnEsc);
};
},
[isOpen]
);
useEffect(
function () {
// it is very tricky with setting class on body, since we theoretically can have multiple open modals simultaneously
// if one of this model will be closed, it will remove class from body.
// This is not particularly good solution - it is better to have some centralized place to set this class.
// This is why I moved it into separate effect
if (isOpen) {
document.body.classList.add("freezeScroll");
} else {
document.body.classList.remove("freezeScroll");
}
// just in case if modal would be unmounted, we reset body
return () => {
document.body.classList.remove("freezeScroll");
};
},
[isOpen]
);
if (isOpen) {
return (
<div
className={styles.modal}
onClick={closeOnClick ? closeModal : undefined}
>
<div
className={`${styles.modalContent} ${
closeOnClick ? styles.clickable : undefined
} ${style}`}
>
{children}
</div>
</div>
);
}
return null;
};
Again. It is very important that render method of classes and function components don't have any side effects that are not idempotent. Idempotence in this case means that if we call some side effect multiple times, it will not change the outcome. This is why you can call console.log from render - it is idempotent, at least from our perspective. Same with react hooks. You can (and should!) call them on each render without messing up react internal state. Attaching new event listener to DOM or window is not idempotent, since on each render you attach new event listener without clearing previous one.
Technically, you can first remove event listener, and then attach new without useEffect, but in this case you will have to preserve listener identity (reference) between renders somehow, or remove all keyup events from window at once. Former is hard to do (you are creating new function on every render), and latter can interfere with other parts of your program.
When a react component state changes, the render method is called. Hence for any state change, an action can be performed in the render methods body. Is there a particular use case for the setState callback then?
Yes there is, since setState works in an asynchronous way. That means after calling setState the this.state variable is not immediately changed. so if you want to perform an action immediately after setting state on a state variable and then return a result, a callback will be useful
Consider the example below
....
changeTitle: function changeTitle (event) {
this.setState({ title: event.target.value });
this.validateTitle();
},
validateTitle: function validateTitle () {
if (this.state.title.length === 0) {
this.setState({ titleError: "Title can't be blank" });
}
},
....
The above code may not work as expected since the title variable may not have mutated before validation is performed on it. Now you may wonder that we can perform the validation in the render() function itself but it would be better and a cleaner way if we can handle this in the changeTitle function itself since that would make your code more organised and understandable
In this case callback is useful
....
changeTitle: function changeTitle (event) {
this.setState({ title: event.target.value }, function() {
this.validateTitle();
});
},
validateTitle: function validateTitle () {
if (this.state.title.length === 0) {
this.setState({ titleError: "Title can't be blank" });
}
},
....
Another example will be when you want to dispatch and action when the state changed. you will want to do it in a callback and not the render() as it will be called everytime rerendering occurs and hence many such scenarios are possible where you will need callback.
Another case is a API Call
A case may arise when you need to make an API call based on a particular state change, if you do that in the render method, it will be called on every render onState change or because some Prop passed down to the Child Component changed.
In this case you would want to use a setState callback to pass the updated state value to the API call
....
changeTitle: function (event) {
this.setState({ title: event.target.value }, () => this.APICallFunction());
},
APICallFunction: function () {
// Call API with the updated value
}
....
this.setState({
name:'value'
},() => {
console.log(this.state.name);
});
The 1. usecase which comes into my mind, is an api call, which should't go into the render, because it will run for each state change. And the API call should be only performed on special state change, and not on every render.
changeSearchParams = (params) => {
this.setState({ params }, this.performSearch)
}
performSearch = () => {
API.search(this.state.params, (result) => {
this.setState({ result })
});
}
Hence for any state change, an action can be performed in the render methods body.
Very bad practice, because the render-method should be pure, it means no actions, state changes, api calls, should be performed, just composite your view and return it. Actions should be performed on some events only. Render is not an event, but componentDidMount for example.
Consider setState call
this.setState({ counter: this.state.counter + 1 })
IDEA
setState may be called in async function
So you cannot rely on this. If the above call was made inside a async function this will refer to state of component at that point of time but we expected this to refer to property inside state at time setState calling or beginning of async task. And as task was async call thus that property may have changed in time being. Thus it is unreliable to use this keyword to refer to some property of state thus we use callback function whose arguments are previousState and props which means when async task was done and it was time to update state using setState call prevState will refer to state now when setState has not started yet. Ensuring reliability that nextState would not be corrupted.
Wrong Code: would lead to corruption of data
this.setState(
{counter:this.state.counter+1}
);
Correct Code with setState having call back function:
this.setState(
(prevState,props)=>{
return {counter:prevState.counter+1};
}
);
Thus whenever we need to update our current state to next state based on value possed by property just now and all this is happening in async fashion it is good idea to use setState as callback function.
I have tried to explain it in codepen here CODE PEN
Sometimes we need a code block where we need to perform some operation right after setState where we are sure the state is being updated. That is where setState callback comes into play
For example, there was a scenario where I needed to enable a modal for 2 customers out of 20 customers, for the customers where we enabled it, there was a set of time taking API calls, so it looked like this
async componentDidMount() {
const appConfig = getCustomerConfig();
this.setState({enableModal: appConfig?.enableFeatures?.paymentModal }, async
()=>{
if(this.state.enableModal){
//make some API call for data needed in poput
}
});
}
enableModal boolean was required in UI blocks in the render function as well, that's why I did setState here, otherwise, could've just checked condition once and either called API set or not.