I have a router state "refetch" that triggers the component to refetch its data (instead of using the cached one), but when it's done, I'd like to toggle that state back to avoid an infinite loop. What is the best way to change state parameters? Or this approach is wrong?
const { state } = useLocation<{ refetch: boolean }>()
const query = useQuery<....>()
useEffect(() => {
if (state.refetch) {
query.refetch()
state.refetch = false // feels wrong to me
}
}, [query, state])
I think instead of mutating the route state object you could issue a redirect with the refetch state set false.
Example:
const history = useHistory();
const { pathname, state } = useLocation();
const query = useQuery<....>();
useEffect(() => {
if (state.refetch) {
query.refetch();
history.replace(pathname, { refetch: false });
}
}, [query, state]);
If you're using React Query, you don't need to add an useEffect for that, you can add dependencies to a query, passing an array as the query key, eg:
useQuery(['key', ...dependencies], queryFn);
Every time the dependencies change it will refetch the query.
In your case you could try useQuery(['key', state], queryFn);
If you need to stick to the refetch of the history, you could add the toggle to the queryFn
Query Keys - React Query
If your function depends on a variable include it in your query key - Stack Overflow
PD: You shouldn't use an object or array as a dependency for useEffect or useMemo, etc. That will always cause an infinite loop.
You could stringify them first using JSON.stringify(foo), use a property of the object, maybe the length of the array. Personally, I stringify them, that way the comparison is more accurate.
Related
Beginner here, but finding this quite tricky. So some help would be appreciated!
I want to have users filter through some options. Those filters should be reflected in the URL. e.g. : http://localhost:3000/items?counter=1
Now when a user visits http://localhost:3000/items?counter=2 I want that to be reflected in state & put it in state. If the same user then changes the state somehow, I want that to be reflected in the url. I do know how to do both things.
But I feel I am running into an infinite loop here:
useEffect(() => {
router.push(`/items?counter=${counter}`, undefined, { shallow: true })
}, [counter])
useEffect(() => {
setCounter(parseInt(router.query.counter))
}, [router.query.counter])
How would I best derive my state from my query params but also always shallow-update the query params every time state changes?
Always update one of them and update the other by listening to the changes of the other. Since the state is always derived from the query, I would update the state via useEffect, and always change the query directly.
This means that you don't update the state directly. Whenever you want to update the state, you need to update the query:
const updateCounterQuery = currentCounter => router.push(`/items?counter=${currentCounter}`, undefined, { shallow: true })
useEffect(() => {
setCounter(parseInt(router.query.counter))
}, [router.query.counter])
However, why do you even need a state in this case? Always use the value that you get from the query:
const updateCounterQuery = counter => router.push(`/items?counter=${counter }`, undefined, { shallow: true })
const counter = +router.query.counter
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.
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])
Google's lighthouse tool gave my app an appalling performance score so I've been doing some investigating. I have a component called Home
inside Home I have useEffect (only one) that looks like this
useEffect(() => {
console.log('rendering in here?') // called 14 times...what?!
console.log(user.data, 'uvv') // called 13 times...again, What the heck?
}, [user.data])
I know that you put the second argument of , [] to make sure useEffect is only called once the data changes but this is the main part I don't get. when I console log user.data the first 4 console logs are empty arrays. the next 9 are arrays of length 9. so in my head, it should only have called it twice? once for [] and once for [].length(9) so what on earth is going on?
I seriously need to reduce it as it must be killing my performance. let me know if there's anything else I can do to dramatically reduce these calls
this is how I get user.data
const Home = ({ ui, user }) => { // I pass it in here as a prop
const mapState = ({ user }) => ({
user,
})
and then my component is connected so I just pass it in here
To overcome this scenario, React Hooks also provides functionality called useMemo.
You can use useMemo instead useEffect because useMemo cache the instance it renders and whenever it hit for render, it first check into cache to whether any related instance has been available for given deps.. If so, then rather than run entire function it will simply return it from cache.
This is not an answer but there is too much code to fit in a comment. First you can log all actions that change user.data by replacing original root reducer temporarlily:
let lastData = {};
const logRootReducer = (state, action) => {
const newState = rootReducer(state, action);
if (newState.user.data !== lastData) {
console.log(
'action changed data:',
action,
newState.user.data,
lastData
);
lastData = newState.user.data;
}
return newState;
};
Another thing causing user.data to keep changing is when you do something like this in the reducer:
if (action.type === SOME_TYPE) {
return {
...state,
user: {
...state.user,
//here data is set to a new array every time
data: [],
},
};
}
Instead you can do something like this:
const EMPTY_DATA = [];
//... other code
data: EMPTY_DATA,
Your selector is getting user out of state and creating a new object that would cause the component to re render but the dependency of the effect is user.data so the effect will only run if data actually changed.
Redux devtools also show differences in the wrong way, if you mutate something in state the devtools will show them as changes but React won't see them as changes. When you assign a new object to something data:[] then redux won't show them as changes but React will see it as a change.
I have a component that uses hooks state (useState) api to track the data.
The object looks like this
const [data,setData] = React.useState({})
Now I have multiple buttons which make API requests and set the data with the new key
setAPIData = (key,APIdata) => {
const dup = {
...data,
[key]:APIdata
}
setData(dup)
}
Now if I make multiple requests at the same time , it results in race conditions since setting state in react is asynchronous and I get the previous value.
In class-based components, we can pass an updater function to get the updated value, how to do this hooks based component.
You must use setData with a function as its argument. Then it will always get the previous state, no matter what order it will be called.
const [data,setData] = React.useState({})
setData(prevData => ({
...prevData,
[key]: APIdata
}));
Documentation: somewhat hidden in hook api reference.
reactjs.org/docs/hooks-reference.html#functional-updates