useSWR - Revalidate not working as expected - reactjs

I use useSWR on a project, and I really like it, but am having an issue with revalidation.
I have a HOC to encapsulate form components in order to not render them until the validation has been run, because if the form component gets initialized with cached data it won't be reupdated after the revalidation, something like this:
useEffect(() => {
if (isValidating) {
setNumberOfTimesThatHasBeenValidating((n) => n + 1);
}
}, [isValidating, setNumberOfTimesThatHasBeenValidating]);
const requiredNumberOfTimes = 1;
const hasValidatedRequiredNumberOfTimes =
(numberOfTimesThatHasBeenValidating >= requiredNumberOfTimes &&
!isValidating) ||
numberOfTimesThatHasBeenValidating > requiredNumberOfTimes;
if (!hasValidatedRequiredNumberOfTimes) {
return <>{fallback}</>;
}
return <RealComponent />
The idea is that when the isValidating turns true and then false, a validation has happened. I previously had implemented with a single boolean state variable "hasValidatedAtLeastOnce" and I thought it worked fine, but then I noticed that even when I use mutate() the isValidating becomes true and false one time before the fetcher function runs. What is that supposed to mean? Why is it that isValidating becomes false if the fetcher hasn't been run? What is being validated then?
Is this behaviour consistent enough that I can safely implement this just raising the required number of times to 2? Setting "shouldRevalidate" on mutate to true doesn't seem to make a difference either.

Related

How can I execute conditionally when using useEffect in Reactnative?

I created an async function called func in useEffect.
When data is received from firestore, "urlsdata" data is saved with setUrl. After that, if there is data in the url contained in useState, I hide the SplashScreen and change the renderbool to true.
However, when useEffect is executed when the app is first rendered, for some reason it is executed twice, so the api is executed twice in total. I think this is very unnecessary, so the code below is executed only when renderbool is false.
But it still gets executed twice when renderbool is false. How do I make func run only once?
Note that url and renderbool are mandatory.
this is my code
const [url, setUrl] = useState('');
const [renderbool, setRenderbool] = useState(false);
useEffect(() => {
const func = async () => {
if(!renderbool) {
const urlsdata= await storage().ref('/main.jpg').getDownloadURL();
setUrl(urlsdata);
if(url) {
SplashScreen.hide();
setRenderbool(true);
}
}
}
func();
},[url, renderbool]);
Your useEffect is dependent on two variables i.e. url and renderbool in addition to executing on first render, it will execute when ever these variables are re-assigned. Meaning you are listening for changes of url and changing it on the same method.
If you only need to fetch the url once then create a useEffect with [], get the url in this one and have and have another useEffect with [url] which hides the splash screen.
If you don't like this idea then simply create a flag that indicates whether, it has executed, like const [splashHidden, setSplashHidden] = useState(false); then check for this
//EVIL EVIL
if(splashhidden)
{
setSplashHidden(true);
//rest of the code
}
this should force it to only execute once... however the first option is the better way
I think you need to change your second if statement.
When you set a state, you can't immediately check the value of it. The state will be updated after your code done running. So, in your case, the first time your useEffect run (on mount) and when you check
setUrl(urlsdata); <- url state still empty here
if (url) <- this will return false, which leaves your `renderbool` state still `false`.
But, because your url state is now changed after the code is done, the useEffect will be called again for the second time. So, what you can do is to check your urlsdata instead
if (urlsdata) {
SplashScreen.hide();
setRenderbool(true);
}

Isn't this validation synchronous?

Taking a look at this commit in the Formik.tsx file, the validation was moved from (in the case of touched as an example):
React.useEffect(() => {
if (
prevState.touched !== state.touched &&
!!validateOnBlur &&
!state.isSubmitting &&
isMounted.current != null
) {
const [validate, cancel] = makeCancelable(validateForm());
validate.then(x => x).catch(x => x); // catch the rejection silently
return cancel;
}
return;
}, [
prevState.touched,
state.isSubmitting,
state.touched,
validateForm,
validateOnBlur,
isMounted,
]);
to here:
const setFieldTouched = React.useCallback(
(
field: string,
touched: boolean = true,
shouldValidate: boolean = true
) => {
dispatch({
type: 'SET_FIELD_TOUCHED',
payload: {
field,
value: touched,
},
});
return validateOnBlur && shouldValidate
? validateFormWithLowPriority(state.values)
: Promise.resolve();
},
[validateFormWithLowPriority, state.values, validateOnBlur]
);
Jumping to the current implementation, it looks like this:
const setFieldTouched = useEventCallback(
(field: string, touched: boolean = true, shouldValidate?: boolean) => {
dispatch({
type: 'SET_FIELD_TOUCHED',
payload: {
field,
value: touched,
},
});
const willValidate =
shouldValidate === undefined ? validateOnBlur : shouldValidate;
return willValidate
? validateFormWithHighPriority(state.values)
: Promise.resolve();
}
);
But why and how do the latter two ensure that the validation will be run with the "freshest" values? With the effect you might dispatch a change, then a blur (both will change the state). The state will then change at some point causing the effect to then run with the current values.
In the latter, the validation - when following the trail of calls - should still run synchronously (executor function of a promise is executed synchronously) - only the setting of the errors will wait until the promise is done (has explicit then). This should mean that when i call setFieldValue and then setFieldTouched immediately after, that the state will still be the old one (since state change is asynchronous). An example would be that i could dispatch a change to a state, and try to access the same state immediately after, which will still be the old state (code sandbox).
However when looking at this code sandbox, the values passed to validate (from the validation flow in setFieldTouched are the actual fresh values. I don't see why this is the case...this might very well be a mistaken interpretation of the flow on my side, but that is why i pose this question.
Edit
Turns out, the whole question stemmed from codesandbox using an old version of formik#1.3.2, which has different behavior from the current version of Formik. This led to confusion about the expected behavior from reading the source of formik#2.1.6 but seeing behavior that did not match the source.
It's a classic pitfall, reading or editing one source file, but executing another.
Original answer below:
You are asking a bunch of very interesting questions. I'll try to answer all of them, but first I want to talk a bit about how React rendering works in general, as that will inform the answers below.
First, let's look at this code example:
export default function App() {
const [count, setCount] = useState(0);
const doubleCount = useMemo(() => count * 2, [count]);
const buttonRef = useRef();
return (
<div className="App">
<button
ref={buttonRef}
onClick={() => {
setCount(count + 1);
console.log(count, doubleCount, buttonRef.current.textContent);
}}
>
{doubleCount}
</button>
</div>
);
}
(based on: https://twitter.com/RyanCarniato/status/1353801009844240389/photo/1)
Code Sandbox
Clicking the button 3 times prints:
0 0 "0"
1 2 "2"
2 4 "4"
This illustrates an important point - at the point where the click event is executed and you call setState(count + 1), the setState() operation itself has not been executed - it is instead scheduled as a change that will be resolved by the next React render. The value of count variable has not immediately increased. This is because the onClick() function was created when the last render happened, and it has captured whatever value count had at that time.
You must treat variables that come from React hooks like useState(), useReducer() and useMemo() as immutable - they do not change inline, they will only be updated with the next render.
This means that calling setState() itself a few times might also not do exactly what you expect. Let's say we start with count = 0.
setCount(count + 1); // same as setCount(0 + 1)
setCount(count + 1); // same as setCount(0 + 1)
setCount(count + 1); // same as setCount(0 + 1)
console.log(count) // 0, it will be 1 in the next render
If you wanted a value to depend on a previously set value, you should do:
setCount(count => count + 1); // same as setCount(0 => 0 + 1)
setCount(count => count + 1); // same as setCount(1 => 1 + 1)
setCount(count => count + 1); // same as setCount(2 => 2 + 1)
console.log(count) // still 0 here, but it will be 3 in the next render
With the above in mind, we can answer the following question:
However when looking at this code sandbox, the values passed to validate (from the validation flow in setFieldTouched are the actual fresh values. I don't see why this is the case...this might very well be a mistaken interpretation of the flow on my side, but that is why I pose this question.
The reason you see different values is that you see the values variable is what it was at the time you clicked the button, and before setFieldValue() has executed, as it behaves like setCount() in the example above. Also, the code sandbox runs Formik 1.3.2, I've explained why that version is special below. You wouldn't see fresh values with the latest version.
How does validate() get the correct data if the values data in React hasn't updated yet?
The value passed to the validate callback of Formik is computed by Formik itself on the fly, as part of the event handler! This is done so that the validation errors can also be calculated without waiting on the rerender of the value. It's basically a performance optimization, and not strictly necessary. Still, let's dive in:
What is useEventCallback()?
You'll see this sprinkled throughout Formik code. All it is is a mild performance optimization - the variables inside that function will always be the variables from the current render, but the function will have a stable referential value. That simply means that setFieldValue from one render is strictly equal (===) to setFieldValue from another render. This is used to optimize some memoization techniques.
In practice, every time you see useEventCallback() you should mentally imagine it's just a normal function;
How does setFieldTouched() ensure that the validation will be run with the "freshest" values?
It doesn't in Formic 2.x! setFieldTouched() runs against whatever values were last rendered.
Warning: The next section explores the behavior of React Class Components. They are not recommended for new projects, React Hooks should be used instead
In Formik 1.x however, setFieldTouched() added validations to be ran as a callback with second parameter to React.Component.setState() - this callback executes AFTER the next render.
So what was happening there was, setFieldTouched() queued this callback, later setFieldValue() updated the value, and then after React rerendered it executed the callback queued by setFieldTocuhed().
How does setFieldValue() work and why does validate() have fresh values?
(This is based on Formik 2.1.6)
I will now inline and comment all of the relevant code that is run in setFieldValue():
const setFieldValue = (field: string, value: any, shouldValidate?: boolean) => {
// use dispatch() from React.useReducer() to update the value of the field and trigger a next render
dispatch({
type: 'SET_FIELD_VALUE',
payload: {
field,
value,
},
});
// from here on out I've inlined the code of validateFormWithHighPriority()
// set IS_VALIDATING state on the form.
// This change executes as part of the above render, alongside the value change!
dispatch({ type: 'SET_ISVALIDATING', payload: true });
// create a fresh copy of `values` with the updated field
const updatedValues = setIn(state.values, field, value);
Promise.all(
// validation functions go here; They will run with the `updatedValues`
// check out the functions `runAllValidations()` and ``
validate(updatedValues)
).then(combinedErrors => {
// this code is guaranteed to execute AFTER React has rendered the `SET_ISVALIDATING` state above,
// and will trigger a NEW React render
dispatch({ type: 'SET_ISVALIDATING', payload: false });
dispatch({ type: 'SET_ERRORS', payload: combinedErrors });
});
}
In effect, what is happening is that setFieldValue() both triggers a new React render, and also calculates what values would be in that new render with setIn(state.values, field, value). It then passes that to validate(), though it does it in a somewhat roundabout way.
I hope this was helpful and that I was able to answer your questions. If there is anything unclear ask away in the comments

useEffect not triggering when object property in dependence array

I have a context/provider that has a websocket as a state variable. Once the socket is initialized, the onMessage callback is set. The callback is something as follows:
const wsOnMessage = (message: any) => {
const data = JSON.parse(message.data);
setProgress(merge(progress, data.progress));
};
Then in the component I have something like this:
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress[pvc.metadata.uid]])
return (
{/* stuff */}
);
}
However, the effect isn't triggering when the progress variable gets updated.
The data structure of the progress variable is something like
{
"uid-here": 0.25,
"another-uid-here": 0.72,
...etc,
}
How can I get the useEffect to trigger when the property that matches pvc.metadata.uid gets updated?
Or, how can I get the component to re-render when that value gets updated?
Quoting the docs:
The function passed to useEffect will run after the render is
committed to the screen.
And that's the key part (that many seem to miss): one uses dependency list supplied to useEffect to limit its invokations, but not to set up some conditions extra to that 'after the render is committed'.
In other words, if your component is not considered updated by React, useEffect hooks just won't be called!
Now, it's not clear from your question how exactly your context (progress) looks like, but this line:
setProgress(merge(progress, data.progress));
... is highly suspicious.
See, for React to track the change in object the reference of this object should change. Now, there's a big chance setProgress just assignes value (passed as its parameter) to a variable, and doesn't do any cloning, shallow or deep.
Yet if merge in your code is similar to lodash.merge (and, again, there's a huge chance it actually is lodash.merge; JS ecosystem is not that big these days), it doesn't return a new object; instead it reassigns values from data.progress to progress and returns the latter.
It's pretty easy to check: replace the aforementioned line with...
setProgress({ ...merge(progress, data.progress) });
Now, in this case a new object will be created and its value will be passed to setProgress. I strongly suggest moving this cloning inside setProgress though; sure, you can do some checks there whether or not you should actually force value update, but even without those checks it should be performant enough.
There seems to be no problem... are you sure pvc.metadata.uid key is in the progress object?
another point: move that dependency into a separate variable after that, put it in the dependency array.
Spread operator create a new reference, so it will trigger the render
let updated = {...property};
updated[propertyname] =value;
setProperty(()=>updated);
If you use only the below code snippet, it will not re-render
let updated = property; //here property is the base object
updated[propertyname] = value;
setProperty(()=>updated);
Try [progress['pvc.metadata.uid']]
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress['pvc.metadata.uid']])
return (
{/* stuff */}
);
}

React useEffect forces me to add dependencies that trigger an infinite loop

Why is React forcing me with their linter plugin to add dependencies that I don't want?
For example, I want my effect to trigger only when a certan value changes, yet the linter tells me to add even functions to the dependencies, and I don't want that.
Why it forces me to do that? What do I gain from that?
/**
* Gets all items, pages, until 250th.
*/
useEffect(() => {
let mounted = true;
if (loadUntil250th && !paginationProps.complete) {
mounted && setLoading(true);
let limit = 250 - paginationProps.page * BATCH_LIMIT;
fetchListItems(paginationProps, limit, paginationProps.page * BATCH_LIMIT)
.then((results) => {
if (mounted) {
setPaginationProps({
...paginationProps,
page: 250 / BATCH_LIMIT,
autoLoad: false,
complete: paginationProps.totalItems <= 250,
});
setListItems(results.listItems);
setLoading(false);
}
})
.catch((err) => {
logger.log('LOADMORE FAILED:', err);
mounted && setPaginationProps({ ...paginationProps, complete: true });
mounted && setLoading(false);
});
}
return () => {
mounted = false;
};
}, [loadUntil250th]);
It wants this array of dependencies, which result in a infinite loop
[loadUntil250th, logger, paginationProps, setListItems]);
I want to understand why it is required, if I don't want them.
The 'exhaustive-deps' lint rule is designed to protect against stale closures, where useEffect references props or state used in the callback but not present in the dependency array. Since logger, paginationProps, and setListItems can theoretically change between renders, it's not safe to reference them inside useEffect without also including them in the dependency array to ensure you're always receiving and acting on up-to-date data. You can think of useEffect as essentially generating a snapshot of all state and props when it gets created and only updating that if one of its dependencies changes.
For instance, without including paginationProps in the dependencies list, if fetchListItems ever modifies the value of paginationProps then useEffect won't have access to that updated value until loadUntil250th changes.
As referenced in this answer, part of the issue is that your usage of useEffect() is unidiomatic. If all you're doing is subscribing to changes to loadUntil250th, you'd be better off moving this function elsewhere and calling it with your code that modifies loadUntil250th.
If you want to keep your code in the useEffect hook, you have a few options:
Assuming that paginationProps and setPaginationProps are derived from a useState hook, you can eliminate the dependency on paginationProps by passing a function to setPaginationProps instead of an object. So your code would become:
setPaginationProps(paginationProps => {
...paginationProps,
page: 250 / BATCH_LIMIT,
autoLoad: false,
complete: paginationProps.totalItems <= 250,
});
Move setListItems inside the useEffect hook if possible. This ensures that you can control whatever props/state that function depends on. If that's not possible, you have a few options. You can move the function outside the component altogether to guarantee that it doesn't depend on props or state. Alternatively, you can use the useCallback hook along with the function to control its dependencies and then list setListItems as another dependency.
It's unlikely that the logger function changes between renders, so you're probably safe keeping that inside your dependency array (although it's odd that the linter would expect that).
If you're still curious, this article is helpful at detailing how useEffect and the dependency array actually works.

Can I use dynamic properties in dependency array for useEffect?

So I have a question regarding useEffect dependenices
This is from the react docs:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
What does this mean exactly, does React keep track of the count variable and its value, and reacts when the value changes, or does React keep track of the first element in the array and its value.
What do I mean by this? Let me explain more. So if we have something like this [name] as dependencies. At the moment of evaluation, the array might result with ['Bob'] or ['Steve']. Clearly this is a change and the useEffect will rerender the component. But how does it check it?
Does it keep track of name or does it keep track of dependencyArray[0]. If we take a look in the previous example, both of these would result to true, name and the first element both changed their values from 'Bob' to 'Steve'. But how does it actually work?
Currently in my code I am using something like this [employees[selectedEmployee].name], where selectedEmployee is something clickable on the UI and it becomes 'Bob' or 'Steve'
ex:
const employees = {
Bob: {
name: 'Bob'
},
Steve: {
name: 'Steve'
}
}
This means that in the end, when evaluated, the dependency array will still result with ['Bob'] --> ['Steve'], and if React is evaluating the dependencyArray[0] then that has clearly changed and component should rerender, but If it keeps track of the reference, then I am changing the reference altogether and it may cause problems.
So what's the correct approach? Can I use dynamic properties like employees[selectedEmployee].name as a dependency?
count is a value, not a reference.
It's just good old Javascript, nothing fancy:
const myArray = [ count ]; // new array containing the value of variable 'count'
const myFunction = () => {
document.title = `You clicked ${count} times`;
}
useEffect(
myFunction,
myArray
);
// Means actually:
// "Run this function if any value in the array
// is different to what it was last time this useEffect() was called"
does React keep track of the ... value, or ... the reference ?
React doesn't really 'keep track' of any of them. It only checks the difference to a previous call, and forgets about everything else.
Can I use dynamic properties as a dependency?
Yes, you can (because they are not as 'dynamic' as you think).
So what's the correct approach?
Better think less of any react-magic going on, but
understand that the component is a function, and believe React calls it when necessary and
think about the variables (properties and state) used inside it, from a plain Javascript perspective.
Then your 'dynamic properties' become 'constant variables during one function call'. No matter which variables change dynamically and how, it will always be one value last time and one value now.
Explaination:
The important 'trick' here is, that the component is just a javascript function, that is called like 'whenever anything might have changed', and consequently useEffect() is also called (as useEffect() is just a function call inside the component).
Only the callback function passed to useEffect is not always called.
useEffect does not render the component, useEffect is called when the component is called, and then just calls the function given to it, or not, depending on if any value in the dependencies array is different to what it was last time useEffect() was called.
React might rerender the component if in the function given to useEffect there are any changes made to the state or something (anything that makes React to think it has to rerender), but that's as a result of this state change, where ever it came from, not because of the useEffect call.
Example:
const MyComponent = (props) => {
// I'm assigning many const here to show we are dealing with local constants.
// Usually you would use this form (using array destructuring):
// const [ selectedEmployee, setSelectedEmployee ] = useState( someInitialValue );
const myStateValueAndSetter = useState( 'Bob' );
const selectedEmployee = myStateValueAndSetter[0];
const setSelectedEmployee = myStateValueAndSetter[1];
const employees = {
Bob: { name: 'Bob' },
Steve: { name: 'Steve' }
};
const currentName = employees[ selectedEmployee ].name;
useEffect(() => {
document.title = 'current name: ' + currentName;
}, [ currentName ]);
return <MyClickableComponent onClick={( newValue ) => {
setSelectedEmployee( newValue )
}}>;
};
click on MyClickableComponent calls the current setSelectedEmployee( newValue ) function.
(The constant selectedEmployee is not changed!)
MyComponent() is called again.
(This is a new function call. All the constants are gone! Only React stores some state in the background.)
useState() is called, the result is stored in a new constant selectedEmployee.
useEffect() is called, and decides if its callback should be called, depending on the previous and the current value of selectedEmployee.
If the callback is not called and nothing else is changed, you might not notice that anything has happened at all.
<MyClickableComponent ... /> is rendered.

Resources