how to prevent multiple function call in react js on value change - javascript

I want to prevent a multiple function call on user action .In my demo application I have a TABS and input field. I want to call a function on user TAB change and when user type anything on input field to call same function. Also reset the input field value when user change the tab .
Problem is my function is called twice when I change the tab. why ? I think it is due to setState only.
here is my code
https://codesandbox.io/s/sleepy-leaf-pi3ykn?file=/src/App.tsx
const AsyncCall = () => {
console.log("AsyncCallAsyncCallAsyncCallAsyncCall");
};
React.useEffect(() => {
console.log("init");
AsyncCall();
}, [value, textValue]);
React.useEffect(() => {
console.log("reset value");
setTextValue("");
}, [value]);
Step to reproduce
Run Application AsyncCall function call only one time
Type some text on input field example "abc". Every time it call function which is correct behaviour
clear console logs
Switch the second tab. if you see AsyncCall function call twice . I need to call only one time . NOT two times .this is the bug . I need to reset the input field also when user switch tabs
any way to prevent . I tried to use ref nothing works for me
I want to call function every textfield value change and tab change . But when user change Tab I need to reset the textfield value also
any suggestion ?

You can call your async call in TextField onChange like this
onChange={(e) => {
setTextValue(e.target.value);
AsyncCall();
}}
and can reset your value in Tabs onChange
handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
setTextValue("");
}
Get rid of both useEffect they are unnecessary here

According to your need
I want to call function every textfield value change and tab change .
But when user change Tab I need to reset the textfield value also
Let see the issue in your code.
React.useEffect(() => {
console.log("init");
AsyncCall();
}, [value, textValue]);
When every value and textValue changes your useEffect gonna invoke. We can remove this useEffect if we call AsyncCall() method inside your valueChange method.
For text field
const handleTextChange = () => {
setTextValue(e.target.value);
AsyncCall();
}
<TextField
id="outlined-basic"
onChange={handleTextChange} // i name it handleTextChange
value={textValue}
label="Outlined"
variant="outlined"/>
Now whenever you textValue gonna change it will call AsyncCall() method.
For tabs
const handleChange = ()=>{
setValue(newValue);
// resetting text value as you want to reset it on tab change
setTextValue("");
// Calling AsyncCall() on Tab menu change
AsyncCall();
}
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
</Tabs>
Now, there is no need for these useEffects
React.useEffect(() => {
console.log("init");
AsyncCall();
}, [value, textValue]);
React.useEffect(() => {
console.log("reset value");
setTextValue("");
}, [value]);
Suggestion: For textValue you can use De-bounce technique to avid unnecessary re-rendering
https://www.freecodecamp.org/news/debouncing-explained/

Paste this code in your handleChange method
setValue(newValue);
setTextValue("")
and remove you second useEffect() hook
React.useEffect(() => {
console.log("reset value");
setTextValue("");
}, [value]);

Related

checkbox state is not updating on spot

On save, I want to proceed with a function depending if a checkbox have been ticked or not. If the checkbox have been ticked, the function should proceed with the save, otherwise it should display a warning that the checkbox needs to be checked.
However, my functionality is not working. I noticed that if I console.log the checkbox boolean from the state, the state wasn't updated at the time I am calling the function.
const [checkBoxTick, setCheckBoxTick] = useState(false);
const [checkBoxError, setCheckBoxError] = useState(false);
const onSave = useCallback(async () => {
console.log(checkBoxTick);
if (checkBoxTick) {
*** go ahead and save ****
} else {
setCheckBoxError(true);
}
}, []);
<Checkbox
label="text here"
helperText="This field is required"
error={checkBoxError}
onChange={() => setCheckBoxTick(!checkBoxTick)}
/>
<Button color="primary" size="small" onClick={onSave}>
Save
</Button>
When I check the checkBox (which changes the state of checkBoxTick to true) and hit the button, the function gets called but the state of checkBoxTick is false although it should be true.
I know its a common state problem and the whole internet is recommending using useEffect(), but I couldn't make sense of how to use it in my case.
Any help is much appreciated :)
Thank you in advance.
UseCallback is memoizing the state of checkBoxTick. You need to pass the variable as a dependency to useCallback so that it gets updated, like so:
const onSave = useCallback(async () => {
console.log(checkBoxTick);
if (checkBoxTick) {
*** go ahead and save ****
} else {
setCheckBoxError(true);
}
}, [checkBoxTick]);

Hook useState with useEffect

I encountered this behavior. Two onChange are called in turn. One of them saves data to the state, and use the previus state to generate new one.
const onChange = (data) => {
setState1((prev) => {
console.log("onChange set state");
return [...prev, data];
});
};
The other just prints something to the console.
const onChange2 = (data) => {
console.log("onChange2");
};
onChange and onChange2 called after click on button
<button
onClick={() => {
onChange({ foo: "bar" });
onChange2();
}}
>
2
</button>
useEffect - saves data to state
const [state, setState] = useState(0);
const [state1, setState1] = useState([]);
useEffect(() => {
setState((prev) => {
console.log("use effect set state");
return prev + 1;
});
}, []);
So, if you do not use useEffect (comment out), the console log, which in the first onChange will be called first, then the console log from the second onChange
If you uncomment useEffect, then the console will log from onChange2 first, then from onChange
Why is this happening, who can tell?
CodeSandBox: https://codesandbox.io/s/eager-hooks-sczvb
The call order of onChange and onChange2 appear to change because the console.log in onChange is inside the callback passed to setState1.
If you move the console.log directly in the onChange you will see that the order is the same with or without useEffect.
const onChange = (data) => {
console.log("onChange set state");
setState1((prev) => {
return [...prev, data];
});
};
It's important to understand that when you call setState there is no guarantee that the state will be changed immediately, it's up to React to decide when to apply the new state and trigger a new render.
This is why the callback passed to setState should only return a new state and not run any effects (such as console.log) since the moment this code is run is considered in implementation detail and could change with any version of React.
Expected behavior is onChange2 called first, and onChange set state called second whatever useEffect body is commented. because onChange2 called right now. but onChange set state in setState fn called after rendered。
The "bug" looked is why onChange set state called first when first click. Hmm... find react computeExpirationForFiber. setState callback execute sync or async by different conditions. It's hard to read.

Changing a component state prop based on a Onchange of another state prop

I have a state prop that changes based on the input value(amount) the user enters(call it firstState.a). my question is how to change another state prop(fee, secondState.b) based on that value entered. I thought I could conditionally setState of the secondState in a UseEffect by having the firstState.a be the dependency which fires the useEffect whenever it changes. What am I not understanding.
pseudo code
useEffect(()=>{
if(...) {
setSecondState(secondstate.b)
} else {
setSecondState(secondstate.b)
}
}, [firstState.a])
I think this is the way that you need to use it, when the amount changes, the useeffect will be fired and if theres any change in the amount, it will update the fee as well. Or you can also make it work with onChange by setting the second state as well when you update the first state.
useEffect(() => {
setFee(amount)
}, [amount)])
Let's imaging you have 2 different states:
const [stateA, setStateA] = useState('');
const [stateB, setStateB] = useState('');
First effect to run after mounting, let's trigger the stateA change (ideally this is not the case but leave it here for the sake of example - maybe it's coming from an API call, this can be triggered maybe on user's onchange event for example of an input field):
useEffect(() => {
setStateA('value');
}, []);
Second effect will be triggered once stateA changes based on the dependency array and on its value you can call setStateB with the preferred value:
useEffect(() => {
if (stateA === 'value') {
setStateB('first condition applied');
} else {
setStateB('second condition applied');
}
}, [stateA]);
Full example of the function component's body:
const [stateA, setStateA] = useState('');
const [stateB, setStateB] = useState('');
useEffect(() => {
setStateA('value');
}, []);
useEffect(() => {
if (stateA === 'value') {
setStateB('first condition applied');
} else {
setStateB('second condition applied');
}
}, [stateA]);
Good further explanation in the documentation of Using the Effect Hook.

ref input value in Enzyme test expected to be empty string but returns undefined

Edit Here is a Code Sandbox of this: https://codesandbox.io/s/react-typescript-bt34v
Unfortunately, I cannot get the Enzyme configuration to work in the Sandbox, but the essential code for this issue is all there, and clicking the button will print the typeof value for the input box.
I'm trying to write a test that tests how an input element's value should change after a Button is clicked.
Here is the function that's called when the Button component is clicked, stopTimer, which calls a useReducer dispatch() to update the useReducer's state store, and below that is what the button looks like.
const stopTimer = (): void => {
dispatch({
payload: state.totalSeconds,
type: 'stop',
});
...
<Button handleClick={ stopTimer } />
In another file, this is what the useReducer looks like, and below that is a useEffect that is called when the store changes. It updates the value of a ref to an empty '' string whenever the store is updated.
const [store, dispatch] = useReducer(reducer, dashboardStore);
...
useEffect((): void => {
ref.current!.value = '';
}, [store]);
...
<input ref={ref} type='text'></input>
To test this with Enzyme, I mount the parent component <Dashboard /> and I give the input an initial value of 'hello'. I then invoke the Button's handleClick prop, which should call the useReducer's dispatch function, updating the store, then causing useEffect to fire, which should set the input's value to an empty string.
I'm expecting the input's value to be an empty string after this, but it ends up being undefined.
I'm also using Enzyme's debug() to try to verify what the DOM looks like at various times. When I do not invoke the Button's handleClick, or if I do not call wrapper.update(), the input's value is 'hello' as expected, but debug() shows the input not having a value prop after update() is called.
it('should contain an empty string when the stop button is clicked', () => {
const wrapper = mount(<Dashboard />);
wrapper.find('input').props().value = 'hello';
act(() => wrapper.find(Button).props().handleClick());
wrapper.update();
console.log(wrapper.find('input').debug());
expect(wrapper.find('input').props().value).toBe('');
});

How do I fix HandleToggleState?

I have a simple material-ui toggle in my react component. I want to use it to toggle state (false/true). If I start with useState(false) the first time I click the toggle it says false rather than true.
I'm wondering if another react hook would solve for this. useEffect, useCallback...
const Component = () => {
const [toggleValue, setToggleValue] = useState(false);
const handleToggleChange = () => {
setToggleValue(!toggleValue);
console.log("toggle value in handle: " + toggleValue);
};
return(
<FormControlLabel
control={
<Switch
checked={toggleValue}
onChange={handleToggleChange}
value="my toggle"
/>
}
/>
);
};
I would expect setPreseason(!preseason); to set the state opposite of what it currently is. True to false and false to true.
It probably is but when I log the state on the next line it logs the initial state and will always log the opposite of what the toggle is.
The state updater function returned by useState is asynchronous
If you need to react to state changes useEffect is the place for it
const Component = () => {
const [toggleValue, setToggleValue] = useState(false);
const handleToggleValue = () => {
setToggleValue(!toggleValue);
};
useEffect(() => {
console.log("toggleValue: " + toggleValue);
// second argument to useEffect is an array of dependencies
// this function is going to run every time one of the dependencies
// changes
}, [toggleValue])
return (
<FormControlLabel
control={
<Switch
checked={toggleValue}
onChange={handleToggleValue}
value="my toggle"
/>
}
/>
);
}
The issue is about which value toggleValue is inside the closure. Is not what you expect. Instead pass a callback to setToggleValue. You will get the current state back, that you can then change.
const handleToggleValue = () => {
setToggleValue((toggleValue) => !toggleValue);
}
You are doing it correctly, except that toggleValue is just a local variable and is not changed just by calling setToggleValue (the state will be changed, but that happens asynchronously).
You could do something like this instead:
const handleToggleValue = () => {
const newToggleValue = !toggleValue;
setToggleValue(newToggleValue);
console.log('toggleValue: ' + newToggleValue);
}
It depends what your intention is with the new toggle value. This simply fixes what you were doing with the console.log call. But you may run into further trouble after that, given that you are using the new toggle value before the state is updated. But that is for another SO question...

Categories

Resources