I want to push an 2d array into the end of a state. But somehow, when i log the state every time it changes with a useEffect, i replaces every existing array with the new one.
The variable "position" is my 2d array, also a state. Might this cause any reference problems?
I have no idea and im starting to lose my mind about this...
const [positionList, setPositionList] = useState([]);
useEffect(() => {
const updatePosition = [...positionList, position]
setPositionList(updatePosition);
}, [playerTurn])
expected output (or what i want)
positionList = [position1, position2, position3, //and so on]
but what i get is this (lets say i try to push the array three times)
positionList = [position3, position3, position3]
EDIT:
So, after some good ideas from the community, i found out that i had to copy my array i want to push (the "position") before i push it into the state. So my working code looks now like this:
const [positionList, setPositionList] = useState([])
useEffect(() => {
let positionCopy = [];
for (let i = 0; i < position.length; i++) {
positionCopy[i] = position[i].slice();
}
setPositionList([...positionList, positionCopy]);
}
You didn't add positionList as a dependency of your useEffect callback, so the positionList captured by the callback doesn't necessarily reflect the latest value.
useEffect(() => {
// return early if the update already happened to prevent recursion
const updatePosition = [...positionList, position]
setPositionList(updatePosition);
}, [playerTurn, positionList])
TBH, using useEffect to update a state hook is difficult to maintain and prone to accidental recursive behaviour.
You might get better mileage by using a state reducer with the useReducer hook. For anything beyond trivial state management, the useReducer hook is worth getting to grips with.
try:
const [positionList, setPositionList] = useState([]);
useEffect(() => {
setPositionList(state=> [...state, position]);
}, [playerTurn])
Related
I am fetching data from some url each second. If data is different than data inside my state, I want to update that state and rerender, and if data is same I do not want to do anything
First I tried most obvious thing, to set interval in useEffect on mount, but it do not work since state always return initial value which is obvious.
Second I created two states, one that holds data and other temp one, then I update temp state and on its useEffect I compare values. It does work but I still got rerender when updating that temp state, and whole point was to not have unnecessary rerender.
Third thing I tried is holding that temp data inside variable or ref, but useEffect is not working on them.
Here is last code I tried with ref so you get idea of what I am trying to do:
const MyComp = () => {
const [data, setData] = useState([])
const tempDataRef = useRef([])
useEffect(() => {
apiFetch().then((returnedArray) => {
tempDataRef.current = returnedArray
})
}, [])
useEffect(() => {
// in this solution using ref, this useeffect is not firing
if(JSON.stringify(tempDataRef.current) != JSON.stringify(data)) {
setData(tempDataRef.current)
}
}, [tempDataRef.current])
return (
<div>
{JSON.stringify(data)}
</div>
)
}
whole point was to not have unnecessary rerender.
tl;dr - it's not possible. Component has to be aware that the data has changed.
setData(tempDataRef.current) code does not fire at all, since useEffect does not listen to useRef updates.
You have two options - either store the data in the state, or keep the useRef but then you will have to apply some interval to check if the data has changed and if so - re-render the component. But of course this is pointless, because you will end up with unnecessary re-renders.
If you are worried about performance drop caused by this "extra" re-render when fetching the data you can always memoize your children so they won't re-render unnecessarily.
One thing I've recently been confused about is what the best way to run effects after a useState call, when the useState doesn't necessarily change the previous state value. For example, say I have a submit function in a form. Also say I have a state called randomNumber, and say in that submit function I call setRandomNumber and set the random number state to a random number from 1-2. Now say that for some reason every time the randomNumber is set, regardless of whether its value changes or not, we want to update the number in some database and navigate to a different page. If I use a useEffect(() => {updateDatabase(); }, [randomNumber]), the problem is that this will update the database even when the functional component first renders, which I do not want. Also, if the randomNumber doesn't change because we pick the same random number twice, we won't update the database after the second setRandomNumber call and we won't navigate to a different page which I don't want.
What would be the best way to 'await' a useState call. I read some articles saying we should potentially use flushSync, but I tried this and it doesn't seem to be working and it seems like this is a very unstable solution as of now.
Thanks!!!
You have 2 different requests here.
How to skip a useEffect on the first render. For this you could employ useRef as this does not trigger a re-render
const Comp = () => {
const firstRender = useRef(true);
useEffect(() => {
if (firstRender.current) {
firstRender.current = false;
return
} else {
// do your stuff
}
}, [randomNumber])
}
How to trigger useEffect even if you get the same number. Probably the simplest way would be to wrap your number inside an object and always generate a new object when generating a new number. This way you'll get a new ref all the time and the useEffect will always trigger. If you already have an object you could create a copy using spread operator. Something like this:
const Comp = () => {
const [randomNumber, setRandomNumber] = useState({number: 0})
useEffect(() => {
console.log(randomNumber.number)
}, [randomNumber])
// generate a new random number and set it like this
setRandomNumber({number: Math.random() + 1))
}
Why doesn't my code work? I am trying to separate these two things into one, but useSelector does not work.
This code works
const st = useSelector((state) =>
state.or.st
);
const ter = useSelector(
({ main }) => main.st.ter
);
This code does not work
I want to put these two things in one useSelector and not in two?
Issue 1: Incorrect Function Usage
The useSelector hook expects a function that takes a single parameter state.
state is the whole state held by redux.
This would mean that your combine function should look more like the following:
const { units, organistationTerminology } = useSelector(state => {
organistationTerminology = state.main.organisation.terminology;
units = state.organisationStructure.units.sort(
(a, b) => a.sortOrder - b.sortOrder
);
return {
organistationTerminology,
units
}
});
Issue 2: Changing Reference
The combined selector function returns a new object which will have a difference reference each time the selector function is computed.
The default comparison used by useSelector to detect a change between values is a reference comparison.
This will result in your component being rerendered on any store change regardless of whether state.main.organisation.terminology or state.organisationStructure.units is updated.
Issue 3: Computation in Selector
Selectors will run on each store change and the resulting value will be compared with the previous value to determine whether a component rerender needs to be triggered.
This means that even if state.organisationStructure.units never changes you will still be running a sort on every store change.
Computations should, where possible, be preformed outside of the selector function.
Issue 4: Sort on State Value
sort() sorts the array in place which would be mutating your state.
Conclusion
Selector functions should be kept as simple / quick as possible as they will be run on every store change.
Pulling multiple values from separate sections of state in one selector usually adds more complexity than it is worth.
My suggestion would be to keep it simple:
const units = useSelector(state => state.organisationStructure.units);
const organistationTerminology = useSelector(state => state.main.organisation.terminology);
const sortedUnits = [...units].sort();
// OR the sort can be memoized using units as a dependency;
const sortedUnits = useMemo(() => [...units].sort(), [units]);
If this is something you will be reusing in a number of components you could wrap it up in a custom hook:
const useTerminologyAndSortedUnits = () => {
const units = useSelector(state => state.organisationStructure.units);
const terminology = useSelector(state => state.main.organisation.terminology);
const sortedUnits = useMemo(() => [...units].sort(), [units]);
return {
units: sortedUnits,
terminology
}
};
// The following can then be used in any of your function components
const { units, terminology } = useTerminologyAndSortedUnits()
I came from here so please don't mark it as a duplicate.
I'm trying to get every value of mainState to be placed in anotherState so at the end I have an array of arrays. mainState is an changes on hover and it changes a lot so I can't assign every value I have in it to anotherState, I only need the value when clicked.
const [mainState, setMainState] = useState([])
const [anotherState, setAnotherState] = useState([])
const [currentItem, setCurrentItem] = useState(0)
//mainState changes on hover and it can change a lot
const onClickHandler = () => {
setAnotherState([...anotherState, mainState])
if (currentItem >= 4) {
// done with this component move to the next component
} else {
setCurrentItem(currentItem + 1)
setMainState([])
}
}
return (
<div onClick={onClickHandler}></div>
However, this doesn't work and even though mainState updates perfectly everytime, anotherState is always one step behind. currentItem represents an array index so I can't click one more time to get the last value of mainState in anotherState. I , using useEffect() which broke React because hover changes a lot and I ended up with an array of hundreds of values.
// tried useEffect() like in the question above, ended up with an array of hundreds of values
useEffect(() => setAnotherState([...anotherState, mainState]))
// tried using callback function like in one of the comments which didn't work and remained the same
setAnotherState(() => [...anotherState, mainState])
// another different way not using hooks is I created an empty array constant in the component
// and on click I tried pushing the value of mainState to that array using deep copy
// I keep ending up with the last array pushed only
const arrays = []
const onClickHandler = () => {
arrays.push(JSON.parse(JSON.stringify(mainState)))
}
How can I work around this?
setAnotherState is asynchronous. So if you want to get latest update value of anotherState.
You should use useEffect like
useEffect(() =>
Console.log("anotherState",anotherState)
// Do your stuff
,[anotherState])
This hook will called whenever another state gets updated using setAnotherState.
Currently my component looks like:
const { listOfStuff = [{name:"john"},{name:"smith"}] } = props
const [peopleNames, setPeopleNames] = useState([])
useEffect(() => {
listOfStuff.forEach(userName => {
setPeopleNames(peopleNames.concat([userName.name]))
})
},[listOfStuff, peopleNames])
As you can probably see this results in an infinite loop, since peopleNames is being updated. Since its included in the dependancies array.
Now, I could remove it from the dependencies array. But my linter would yell at me.
And previous experience has told me to trust my linter over my own judgement.
I feel like im missing something fundamental here.
Ideally, I would like the peopleNames state to look something like
['john','smith']
setPeopleNames(current => current.concat([userName.name]))
Then you can remove peopleNames from the dependencies array.
The second array item returned from useState ie setState can take a function as it’s argument where the parameter of the function is the current value of the state. So you can make changes based on the current state value without referencing the first argument of useState.
Further homework:
Your useEffect code can be simplified to:
useEffect(() =>
setPeopleNames(current =>
current.concat(listOfStuff.map(stuff => stuff.name)
),
[listOfStuff]);