Set state and run function on a single onClick event - javascript

I need to set state and run a function on a onClick event. How do I accomplish this in React?
I've atttempted to put these two things in their own function, but that give me this warning below.
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
editLink(onEditLink, link) {
this.setState({showContent: false});
onEditLink(link);
}
render() {
return (
<a onClick={this.editLink(this.props.onEditLink, this.props.link)}>Edit</a>
)
}

The problem is you are calling the function right away instead of assigning a new function which will be executed when the element is clicked:
editLink(onEditLink, link) {
this.setState({showContent: false});
onEditLink(link);
}
render() {
return (
<a onClick={() => this.editLink(this.props.onEditLink, this.props.link)}>Edit</a>
)
}
What you want to do is pass a function, which when executed, will call your this.editLink function with those parameters.

setState accepts a callback function as its second argument, I suggest you utilize that:
editLink(onEditLink, link) {
this.setState({showContent: false}, () => {
onEditLink(link);
});
}
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.

Related

JS executing out of order when dispatching redux actions

I have a more complex version of the following pseudo-code. It's a React component that, in the render method, tries to get a piece of data it needs to render from a client-side read-through cache layer. If the data is present, it uses it. Otherwise, the caching layer fetches it over an API call and updates the Redux state by firing several actions (which theoretically eventually cause the component to rerender with the new data).
The problem is that for some reason it seems like after dispatching action 1, control flow moves to the top of the render function again (starting a new execution) and only way later continues to dispatch action 2. Then I again go to the top of the render, and after a while I get action 3 dispatched.
I want all the actions to fire before redux handles the rerender of the component. I would have thought dispatching an action updated the store but only forced components to update after the equivalent of a setTimeout (so at the end of the event loop), no? Is it instead the case that when you dispatch an action the component is updated synchronously immediately, before the rest of the function where the dispatch happens is executed?
class MyComponent {
render() {
const someDataINeed = CachingProvider.get(someId);
return (
<div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
);
}
}
class CachingProvider {
get(id) {
if(reduxStoreFieldHasId(id)) {
return storeField[id];
}
store.dispatch(setLoadingStateForId(id));
Api.fetch().then(() => {
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
});
return null;
}
}
In addition to #TrinTragula's very important answer:
This is React behaviour. Things that trigger rerenders that are invoked synchronously from an effect/lifecycle or event handler are batched, but stuff that is invoked asnychronously (see the .then in your code) will trigger a full rerender without any batching on each of those actions.
The same behaviour would apply if you would call this.setState three times in a row.
You can optimize that part by adding batch which is exported from react-redux:
Api.fetch().then(() => {
batch(() => {
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
})
});
You should never invoke heavy operations inside of a render function, since it's going to be triggered way more than you would like to, slowing down your app.
You could for example try to use the useEffect hook, so that your function will be executed only when your id changes.
Example code:
function MyComponent {
useEffect(() => {
// call your method and get the result in your state
}, [someId]);
return (
<div>{someDataINeed == null ? "Loading" : someDataINeed }</div>
);
}

What is the advantage of using componentDidUpdate over the setState callback?

Why is using componentDidUpdate more recommended over the setState callback function (optional second argument) in React components (if synchronous setState behavior is desired)?
Since setState is asynchronous, I was thinking about using the setState callback function (2nd argument) to ensure that code is executed after state has been updated, similar to then() for promises. Especially if I need a re-render in between subsequent setState calls.
However, the official React Docs say "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."
And that's all they say about it there, so it seems a bit vague. I was wondering if there was a more specific reason it is recommended to not use it? If I could I would ask the React people themselves.
If I want multiple setState calls to be executed sequentially, the setState callback seems like a better choice over componentDidUpdate in terms of code organization - the callback code is defined right there with the setState call. If I use componentDidUpdate I have to check if the relevant state variable changed, and define the subsequent code there, which is less easy to track. Also, variables that were defined in the function containing the setState call would be out of scope unless I put them into state too.
The following example might show when it might be tricky to use componentDidUpdate:
private functionInComponent = () => {
let someVariableBeforeSetStateCall;
... // operations done on someVariableBeforeSetStateCall, etc.
this.setState(
{ firstVariable: firstValue, }, //firstVariable may or may not have been changed
() => {
let secondVariable = this.props.functionFromParentComponent();
secondVariable += someVariableBeforeSetStateCall;
this.setState({ secondVariable: secondValue });
}
);
}
vs
public componentDidUpdate(prevProps. prevState) {
if (prevState.firstVariableWasSet !== this.state.firstVariableWasSet) {
let secondVariable = this.props.functionFromParentComponent();
secondVariable += this.state.someVariableBeforeSetStateCall;
this.setState({
secondVariable: secondValue,
firstVariableWasSet: false,
});
}
}
private functionInComponent = () => {
let someVariableBeforeSetStateCall = this.state.someVariableBeforeSetStateCall;
... // operations done on someVariableBeforeSetStateCall, etc.
this.setState({
firstVariable: firstValue,
someVariableBeforeSetStateCall: someVariableBeforeSetStateCall,
firstVariableWasSet: true });
//firstVariable may or may not have been changed via input,
//now someVariableBeforeSetStateCall may or may not get updated at the same time
//as firstVariableWasSet or firstVariable due to async nature of setState
}
Also, apart from componentDidUpdate being generally recommended, in what cases would the setState callback be more appropriate to use?
Why is using componentDidUpdate more recommended over the setState callback function?
1. Consistent logic
When using the callback argument to setState(), you might have two separate calls to setState() in different places which both update the same state, and you'd have to remember to use the same callback in both places.
A common example is making a call to a third-party service whenever a piece of state changes:
private method1(value) {
this.setState({ value }, () => {
SomeAPI.gotNewValue(this.state.value);
});
}
private method2(newval) {
this.setState({ value }); // forgot callback?
}
This is probably a logic error, since presumably you'd want to call the service any time the value changes.
This is why componentDidUpdate() is recommended:
public componentDidUpdate(prevProps, prevState) {
if (this.state.value !== prevState.value) {
SomeAPI.gotNewValue(this.state.value);
}
}
private method1(value) {
this.setState({ value });
}
private method2(newval) {
this.setState({ value });
}
This way, the service is guaranteed to be called whenever the state updates.
Additionally, state could be updated from external code (e.g. Redux), and you won't have a chance to add a callback to those external updates.
2. Batched updates
The callback argument of setState() executes after the component is re-rendered. However, multiple calls to setState() are not guaranteed to cause multiple renders, due to batching.
Consider this component:
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { value: 0 };
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate: ' + this.state.value);
}
onClick = () => {
this.setState(
{ value: 7 },
() => console.log('onClick: ' + this.state.value));
this.setState(
{ value: 42 },
() => console.log('onClick: ' + this.state.value));
}
render() {
return <button onClick={this.onClick}>{this.state.value}</button>;
}
}
We have two setState() calls in the onClick() handler, each simply prints the new state value to the console.
You might expect onClick() to print the value 7 and then 42. But actually, it prints 42 twice! This is because the two setState() calls are batched together, and only cause one render to occur.
Also, we have a componentDidUpdate() which also prints the new value. Since we only have one render occurring, it is only executed once, and prints the value 42.
If you want consistency with batched updates, it's usually far easier to use componentDidMount().
2.1. When does batching occur?
It doesn't matter.
Batching is an optimization, and therefore you should never rely either on batching occurring or it not occurring. Future versions of React may perform more or less batching in different scenarios.
But, if you must know, in the current version of React (16.8.x), batching occurs in asynchronous user event handlers (e.g. onclick) and sometimes lifecycle methods if React has full control over the execution. All other contexts never use batching.
See this answer for more info: https://stackoverflow.com/a/48610973/640397
3. When is it better to use the setState callback?
When external code needs to wait for the state to be updated, you should use the setState callback instead of componentDidUpdate, and wrap it in a promise.
For example, suppose we have a Child component which looks like this:
interface IProps {
onClick: () => Promise<void>;
}
class Child extends React.Component<IProps> {
private async click() {
await this.props.onClick();
console.log('Parent notified of click');
}
render() {
return <button onClick={this.click}>click me</button>;
}
}
And we have a Parent component which must update some state when the child is clicked:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
}
private setClicked = (): Promise<void> => {
return new Promise((resolve) => this.setState({ clicked: true }, resolve));
}
render() {
return <Child onClick={this.setClicked} />;
}
}
In setClicked, we must create a Promise to return to the child, and the only way to do that is by passing a callback to setState.
It's not possible to create this Promise in componentDidUpdate, but even if it were, it wouldn't work properly due to batching.
Misc.
Since setState is asynchronous, I was thinking about using the setState callback function (2nd argument) to ensure that code is executed after state has been updated, similar to .then() for promises.
The callback for setState() doesn't quite work the same way as promises do, so it might be best to separate your knowledge.
Especially if I need a re-render in between subsequent setState calls.
Why would you ever need to re-render a component in between setState() calls?
The only reason I can imagine is if the parent component depends on some info from the child's DOM element, such as its width or height, and the parent sets some props on the child based on those values.
In your example, you call this.props.functionFromParentComponent(), which returns a value, which you then use to compute some state.
Firstly, derived state should be avoided, since memoization is a much better option. But even so, why not just have the parent pass the value directly down as a prop? Then you can at least compute the state value in getDerivedStateFromProps().
//firstVariable may or may not have been changed,
//now someVariableBeforeSetStateCall may or may not get updated at the same time
//as firstVariableWasSet or firstVariable due to async nature of setState
These comments don't make much sense to me. The asynchronous nature of setState() doesn't imply anything about state not getting properly updated. The code should work as intended.

Choosing the correct way to call function in stateful component

So i am having tough time figuring/understanding the correct way to call method inside a class in javascript for example
consider we have stateful component with various method like
addIngredientHandler = (type) => { //Adds one to the state of ingredient }
and
purchasingHandlerOpen = () => this.setState({purchasing: true}) //will show a order summary pop-up if we have more than one ingredient
We pass both of them to child component (using props) by calling them in a return of our stateful component like this
<BuildControls
ingredientAdded={this.addIngredientHandler}
purchasingHandlerOpen={this.purchasingHandlerOpen}
purchasableHandler={this.state.purchasable} />
and In our stateless child component we do
<BuildControl
ingredientAdded={() => props.ingredientAdded(el.type)}
/>))}
<button className={Classes.OrderButton} disabled={!props.purchasableHandler} onClick={props.purchasingHandlerOpen}>Order</button>
</div
Here we have use this at one place
ingredientAdded={() => props.ingredientAdded(el.type)}
and this in another
onClick={props.purchasingHandlerOpen}>
So my question is when do we call a method/function using {() => props.ingredientAdded(el.type)} and when do we use {props.purchasingHandlerOpen} and when do we probably do something like {props.purchasingHandlerOpen()}
Slight Note: In the above example where i do
<button className={Classes.OrderButton} disabled={!props.purchasableHandler} onClick={props.purchasingHandlerOpen}>Order</button>
If I do something like {props.purchasingHandlerOpen()} it throws infinite render error message, I I do something like {() => props.purchasingHandlerOpen} the button does not work.
First of all, you have to understand that the thing you're passing here are just functions, so there is nothing principally different in those 2 ways
There are few points you need to consider though:
First: since react.js uses shallow comparison, every time you're passing
ingredientAdded={() => props.ingredientAdded(el.type)}
you're actually pass function created just now, so it may cause unneeded calls of your children render function (you could easily avoid this by using shouldComponentUpdate though). This could lead to possible performance issues on big react trees so that you second approach is preferred.
Second: you could easily mix a some value via your first approach, something like
ingredientAdded={() => props.ingredientAdded(el.type, SOMETHING_FROM_STATE)}
Third. You can easily modify your event handlers and pass down them in react tree by generating functions which return functions:
class App extends React.Component {
generateFunction(something) {
return (arg) => {
this.props.myFunction(something, arg)
}
}
render() {
return (
<div>
<FirstComponent onClick={this.generateClickFunction('First')} />
<SecondComponent onClick={this.generateClickFunction('Second')} />
</div>
}
}
}
UPD
onClick should always receive function, not its results, like that:
<button ... onClick={props.purchasingHandlerOpen} />
if you are changing onClick to {props.purchasingHandlerOpen()} you are calling the function, so you're passing its result to props.
If you are changing onClick to {() => purchasingHandlerOpen} you are passing undefined (it's not a props.purchasingHandlerOpen, but purchasingHandlerOpen is undefined) so that React considers there is no a callback passed to the props
{() => props.ingredientAdded(el.type)} creates a new funciton which binds the el.type, see arrow functions
{props.purchasingHandlerOpen} does nothing since we do not execute the function (there are no (), call, apply). We simply pass the function reference.
{props.purchasingHandlerOpen()} runs the function.

Set State not getting called in parent function in React

I have a function defined in my component class which is calling it's parent function where that child is being utilized.
completeSession(id) {
this.props.completeSession(id);
}
This function calls the parent function with id. The parent function is:
completeSession(id) {
console.log("Entered parent complete Session");
console.log(id);
this.setState({feedback_id: id});
console.log(this.state.feedback_id);
}
Here as you can see I have 3 console logs and my console.log(id) is giving a proper value but this.state.feedback_id comes as undefined. Over here the this.setState should update feedback_id and this should update the child component also.
renderPast() {
return(
<PastSessions timezone={this.state.timezone}
feedback_id={this.state.feedback_id}
sessions={this.state.sessions}
completeSession={this.completeSession.bind(this)}
cancelSession={this.cancelSession.bind(this)} />
)
}
So I'm passing the feedback id back to the component so I'm not sure why it is not setting the state as intended. Any help would be appreciated.
Please keep in mind that setState is asynchronous - so state is not updated immidiately after calling it. You can provide a callback function as setState second argument which will be called when the state is actually updated:
this.setState({feedback_id: id}, () => {
console.log('state updated');
console.log(this.state.feedback_id);
});
According to React setState documentation:
... the second parameter 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.
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value.

Can I register click-listener after state transition?

I have a component that looks like this:
<div ref='carousel' onClick={this.mobileZoomOut()} className='carousel'>
The mobileZoomout is suppose to register a special condition that only applies to small screens:
mobileZoomOut () {
const elem = this.state.zoom.carousel
if (this.zoomed('carousel') && elem.scale < 1.1) {
this.setState({zoom: {}})
}
}
The regular zoom is registered like this:
this.flky = new Flickity('.carousel', flickityOptions)
this.flky.on('staticClick', (e) => {
if (this.zoomed()) {
this.setState({ zoom: {} })
} else {
this.zoomIn('carousel', 0.774, 0)
this.zoomIn('thumbs', 0.208, 0.774)
}
})
staticClick is a custom event from the image-slider flickity, it is disabled when zooming in on mobile. That is why I need another zoom-out-event on mobile.
When adding mobileZoomOut I get this error, I believe the reason is that the click event on the carousel register both events, I don't want the onClick to be registered until after the staticClick -event is done.
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
I have tried replacing onClick with onMouseDown/onMouseUp it does not help. I think I could do it with a timeout, but I would like to avoid that and I'm not really sure how.
You are not passing your function as a prop, but rather executing it and passing its returned value.
foo() executes the foo-function. You want it executed when the event is triggered though.
this should do the trick.
<div ref='carousel' onClick={this.mobileZoomOut} className='carousel'>
you should be passing reference to function as a props.
<div ref='carousel' onClick={this.mobileZoomOut.bind(this)} className='carousel'>

Categories

Resources