How to checddsa hooks? - reactjs

I need to detect every change of variable state BUT check code:
useEffect(() => {
if (value) {
setSelect(value);
}
}, [value]);
code above checkin every second and i got error:
Warning: Maximum update depth exceeded. This can happen when a
component calls setState inside useEffect, but useEffect either
doesn't have a dependency array, or one of the dependencies changes on
every render.
I need to check only when value is changed not every second!
value is props data from parent component.
const OrganisationUnitInput = ({
input
}) => {
const [showModal, setShowModal] = useState(false);
const value = input.value || [];
const value = input.value || [];
const handleChange = useCallback(
(value) => {
input.onChange(value);
setShowModal(false);
},
[input]
);
}

It is happening because you are setting value state and passing same value as dependency array . which results in re rendering

You misunderstood the useEffect hook, when you pass the second argument with some variable you are telling to react that the function of the effect must be executed if the variable change.
So in your code You are making an infinity loop.
To detect the change on the var only pass the var in the array and your function will be executed if any change occurred.
const Comp = () => {
const [value, setValue] = useState(0)
useEffect(() => {
// This code will be execute any time that the var value
// change
console.log("The value change, now is: ", value)
// so if you change the var here your will cause another call
// to this effect.
}, [value])
return <button onClick={() => { setValue(prev => prev + 1) }}>
Change Value
</button>
}

Related

React prop function called after useState set hook kills hook

I have an PostImagesList component that updates when an image is removed. I also send the removed image id to the parent for deletion if the post update is ultimately committed.
I'm trying to understand why a prop function (onRemoveImage) kills the setImages useState hook, unless I remove the same image twice(?) Presently in the parent I am only logging the id sent from the child so nothing is changing with the data.
// <PostImagesList />
const handleRemoveImage = (e, idx, imageId) => {
e.stopPropagation();
let newFilesArr = [...images];
newFilesArr.splice(idx, 1);
setImages(newFilesArr);
onRemoveImage(imageId); // If I comment this out, the setImages updates correctly
};
images is initially set in PostImagesList in a useEffect hook:
useEffect(() => {
setImages(
imagesByPostId?.edges
? imagesByPostId?.edges
.filter(Boolean)
.map((edge) => edge.node)
.filter(Boolean)
: [],
);
};
I also tried setting the images in the declaration instead of in a useEffect hook:
const [images, setImages] = useState(() => {
return imagesByPostId?.edges
? imagesByPostId?.edges
.filter(Boolean)
.map((edge) => edge.node)
.filter(Boolean)
: [];
});
I want to update the list locally in <PostImagesList/>, and send the deleted image id to the parent, but not rerender <PostImagesList/>.
I can't see where you declared setImages but if you want to set the initial state you can do like:
const [state, setState] = React.useState(whatever)
If you want to set it depending on complex conditions, then you can use React.useReducer instead of React.useState or define initial state like:
const [state, setState] = React.useState(() => {
if (itsCool) return "cool";
else return "not cool";
});
If you need to use that useEffect hook and you want to set something at the initial render then you need to pass an empty dependency Array, else you can run into "Maximum update depth exceeded" because setState will call repeatedly. So do it like:
useEffect(() => {
setImages(
imagesByPostId?.edges
? imagesByPostId?.edges
.filter(Boolean)
.map((edge) => edge.node)
.filter(Boolean)
: [],
);
}, []); // here is the empty dependency array
More about useEffect if you are interested:
Note that useEffect is not only componentDidMount but also componentDidUpdate, so the callback function you pass will executed on every component-update. If you pass a dependency array the callback function you pass will get executed only when the dependeny (reference) changes. Something like:
const { useEffect } = (function() {
var _dep = null;
function useEffect(clb, dep) {
if (_dep === null) {
_dep = dep;
clb();
} else if (_dep !== dep) {
_dep = dep;
clb();
} else {
return;
}
}
return {
useEffect
};
})();
var counter = 1;
const cbk = () => console.log(counter);
useEffect(cbk, counter);
useEffect(cbk, counter); // cbk will not execute
useEffect(cbk, counter); // cbk will not execute
counter++;
useEffect(cbk, counter);
to simpily the useEffect funtion we don't pass a dependency array we just pass one dependency.

e.target.value onChange input react

I have this simple onChange function, but I have problem. When I change my input first time and for example enter "1" my variable inputOne is returns empty string:
const [inputOne, setInputOne] = useState('');
const onChange = (e) => {
setInputOne(e.target.value);
console.log(e.target.value); // first changing of input returns 1
console.log(inputOne); // first changing of input returns EMPTY string
}
<div className="container">
<input type="number" name='input1' value={inputOne} onChange={onChange} />
</div>
but when I change this input again and add one more "1"(in total 11) my console is:
console.log(e.target.value); // returns 11
console.log(inputOne); // returns 1
Why it's happening with my variable inputOne?
New code:
const [inputOne, setInputOne] = useState('');
useEffect(() => {
console.log(inputOne);
}, [inputOne])
const onChange = (e) => {
setInputOne(e.target.value);
console.log(e.target.value);
setTimeout(() => {
if(e.target.value){
const filteredpost = posts[0].filter(mfo => mfo.minPrice <= Number(inputOne));
setPostsToShow(filteredpost.slice(0, 20));
setPost(filteredpost);
}else{
const filteredpost = posts[0];
setPostsToShow(filteredpost.slice(0, 20));
setPost(filteredpost);
}}, 1000);
}
setState is an async function.
In your case, setInputOne queues the change and returns a Promise,
that will not be resolved until the next tick (or even later, if reacts thinks it is worth it to gain some performance).
So the timeline is like this:
Type into input
Trigger onChange
setInputOne (queue the change)
console.log (the value that is queued)
console.log (the variable that is queued)
next tick and consequently the change of the variable.
You can see this with the useEffect hook:
useEffect(() => {
console.log(`tell me when inputOne changes`);
}, [inputOne])
UPDATE
inputOne will never be your updated value inside the onChange function. The onChange function stores the last value until re-render.
Pass your setTimeout to the useEffect OR change inputOne to e.target.value since they will always be the same.
State update in Reactjs is an asynchronous process, therefore it won't be reflected immediately in the next line due to it's asynchronous nature.
If you want to monitor the state whenever its updated, you can use useEffect hook, and place inside its the dependency array the piece of state you want to track.
In your case:
useEffect(() => {
console.log(inputOne);
}, [inputOne])
This will be triggered, every time inputOne changes. If you want to use the value from the inputOne to call another function you should implement that logic inside the useEffect, instead of doing it inside the function onChange which updates the inputOne state.
useEffect(() => {
if(inputOne){
const filteredpost = posts[0].filter(mfo => mfo.minPrice <= Number(inputOne));
setPostsToShow(filteredpost.slice(0, 20));
setPost(filteredpost);
}
else
{
const filteredpost = posts[0];
setPostsToShow(filteredpost.slice(0, 20));
setPost(filteredpost);
}
}, [inputOne]);
Get rid of the timeout. It's unnecessary.

How to correctly use React `useCallback`'s dependencies list?

I have an example like this:
codesandebox
I want to modify a state value in a callback, then use the new state value to modify another state.
export default function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState("0");
const [added, setAdded] = useState(false);
const aNotWorkingHandler = useCallback(
e => {
console.log("clicked");
setCount(a => ++a);
setText(count.toString());
},
[count, setCount, setText]
);
const btnRef = useRef(null);
useEffect(() => {
if (!added && btnRef.current) {
btnRef.current.addEventListener("click", aNotWorkingHandler);
setAdded(true);
}
}, [added, aNotWorkingHandler]);
return <button ref={btnRef}> + 1 </button>
However, after this handler got called, count has been successfully increased, but text hasn't.
Can you guys help me to understand why this happened? and how to avoid it cleanly?
Thank you!
If count and state are always supposed to be in lockstep, just with one being a number and one being a string, then i think it's a mistake to have two state variables. Instead, just have one, and derive the other value from it:
const [count, setCount] = useState(0);
const text = "" + count;
const [added, setAdded] = useState(false);
const aNotWorkingHandler = useCallback(
e => {
setCount(a => ++a);
},
[]
);
In the above useCallback, i have an empty dependency array. This is because the only thing that's being used in the callback is setCount. React guarantees that state setters have stable references, so it's impossible for setCount to change, and thus no need to list it as a dependency.
There are few things causing the issue.
Setter does not update the count value immediately. Instead it "schedules" the component to re-render with the new count value returned from the useState hook. When the setText setter is called, the count is not updated yet, because the component didn't have chance to re-render in the mean time. It will happen some time after the handler is finished.
setCount(a => ++a); // <-- this updates the count after re-render
setText(count.toString()); // <-- count is not incremented here yet
You are calling addEventListener only once and it remembers the first value of count. It is good you have aNotWorkingHandler in the dependencies - the onEffect is being re-run when new count and thus new handler function comes. But your added flag prevents the addEventListener from being called then. The button stores only the first version of the handler function. The one with count === 0 closured in it.
useEffect(() => {
if (!added && btnRef.current) { // <-- this prevents the event handler from being updated
btnRef.current.addEventListener("click", aNotWorkingHandler); // <-- this is called only once with the first instance of aNotWorkingHandler
setAdded(true);
} else {
console.log("New event handler arrived, but we ignored it.");
}
}, [added, aNotWorkingHandler]); // <-- this correctly causes the effect to re-run when the callback changes
Just removing the added flag would, of course, cause all the handlers to pile up. Instead, just use onClick which correctly adds and removes the event handler for you.
<button onClick={aNotWorkingHandler} />
In order to update a value based on another value, I'd probably use something like this (but it smells of infinite loop to me):
useEffect(
() => {
setText(count.toString());
},
[count]
);
Or compute the value first, then update the states:
const aNotWorkingHandler = useCallback(
(e) => {
const newCount = count + 1;
setCount(newCount);
setText(newCount.toString());
},
[count]
);
I agree with #nicholas-tower, that if the other value does not need to be explicitly set and is always computed from the first one, it should be just computed as the component re-renders. I think his answer is correct, hope this context answers it for other people getting here.

useEffect re-renders too many times

I have this component, that needs to fetch data, set it to state and then pass it to the children.
Some of the data also needs to be set in context.
My problem is that using useEffect, once called the API, it will re-render for each setvalue() function I need to execute.
I have tried passing to useEffect an empty [] array, still getting the same number of re-renders, due to the fact that the state is changing.
At the moment the array is containg the set...functions to prevent eslint to throw warnings.
Is there a better way to avoid this many re-renders ?
const Home = (props) => {
console.log("TCL: Home -> props", props);
const classes = useStyles();
const [value, setValue] = React.useState(0);
//CONTEXT
const { listSavedJobs, setListSavedJobs, setIsFullView} = useContext(HomeContext);
const {
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
} = useContext(UserContext);
// STATE
const [searchSettings, setSearchSettings] = useState([]);
const [oppData, setOppData] = useState([]);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const handleChangeIndex = index => {
setValue(index);
};
//API CALLS
useEffect(() => {
const triggerAPI = async () => {
setIsFullView(false);
const oppResponse = await API.getOpportunity();
if(oppResponse){
setOppData(oppResponse.response);
}
const profileResponse = await API.getUserProfile();
if(profileResponse){
setUserName(profileResponse.response.first_name);
setUserLastName(profileResponse.response.last_name);
setUserEmail(profileResponse.response.emailId);
}
const profileExtData = await API.getUserProfileExt();
if(profileExtData){
setAvatarProfile(profileExtData.response.avatar);
setListSavedJobs(profileExtData.response.savedJobs);
setSearchSettings(profileExtData.response.preferredIndustry);
}
};
triggerAPI();
}, [
setOppData,
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
setListSavedJobs,
setIsFullView,
]);
...```
Pass just an empty array to second parameter of useEffect.
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
Source
Edit: Try this to avoid rerenders. Use with caution
Only Run on Mount and Unmount
You can pass the special value of empty array [] as a way of saying “only run on mount and unmount”. So if we changed our component above to call useEffect like this:
useEffect(() => {
console.log('mounted');
return () => console.log('unmounting...');
}, [])
Then it will print “mounted” after the initial render, remain silent throughout its life, and print “unmounting…” on its way out.
Prevent useEffect From Running Every Render
If you want your effects to run less often, you can provide a second argument – an array of values. Think of them as the dependencies for that effect. If one of the dependencies has changed since the last time, the effect will run again. (It will also still run after the initial render)
const [value, setValue] = useState('initial');
useEffect(() => {
// This effect uses the `value` variable,
// so it "depends on" `value`.
console.log(value);
}, [value])
For more clarification useEffect
If you are using React 18, this won't be a problem anymore as the new auto batching feature: https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching
If you are using an old version, can refer to this solution: https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html

Reading component state just after setting when using useState hook in react

This console.log is not working: It'll just print the previous state value as set is async.
const SomeCompo = () => {
const [count, set] = useState(0);
const setFun = () => {
console.log(count);
set(count + 1);
console.log(count);
}
return <button onClick={setFun}>count: {count}</button>
}
I had to read the count in the render itself:
const SomeCompo = () => {
const [count, set] = useState(0);
console.log(count);
const setFun = () => {
set(count + 1);
}
return <button onClick={setFun}>count: {count}</button>
}
Is there a better way to read the value as I don't want to console for every render.
You can use useEffect for this,
useEffect(() => {
console.log(count);
}, [count]) //[count] is a dependency array, useEffect will run only when count changes.
I would suggest not to use setInterval. I would do something like useEffect. This function will be called each time you do a setState. Just like you had callback after setState. Pass the count state in the array, it will watch only for the count change in the state and console your count.
useEffect(() => {
console.log(count);
}, [count]);
Also if you dont need to rerender your other components, you might wanan use useMemo and useCallback. https://www.youtube.com/watch?v=-Ls48dd-vJE
Here to more read: https://reactjs.org/docs/hooks-effect.html
The way to get a state value is to use useEffect and use the state as a dependency. This means that when we change a value the render cycle will finish and a new one will start, then useEffect will trigger:
useEffect( () => { console.log(value); }, [value] );
If you would need to read the value in the same cycle as it is changed a possibility could be to use the useState set function. This shows the latest value just before updating it:
setValue( latest_value => {
const new_value = latest_value + 1;
console.log(new_value);
return new_value;
} );

Resources