Loading flags with Redux saga and React functional components - reactjs

When using flags in redux state and class-based components, life is good. You start your API calls in componentWillMount and by the time the component is mounted and the render() function runs, you have your flags set correctly in redux, meaning no flash of unwanted content (FUC).
That is well, but we are using functional components now. In that case, we run API calls through useEffect(), meaning we set the flags in redux only on second render. In the case of a simple component:
function SimpleComponent() {
const isLoading = useSelector(selectIsLoading);
const dispatch = useDispatch();
useEffect(() => {
dispatch(startApiRequest());
},[dispatch]);
if (isLoading) {
return <LoadingComponent />;
}
return <ContentThatWillBreakIfApiCallIsNotFinished />;
}
The behaviour of this code is as follows:
render 1: isLoading is false, you show broken content.
after render 1: useEffect runs, sets isLoading to true
render 2: isLoading is true, you no longer show broken content
A simple solution is to init the store with isLoading set to true. That means that you will have to make sure to always return it to true when exiting components. This can lead to bugs if the same flag is used in multiple components. It is not an ideal solution.
With redux-thunk, we can use a custom hook that has internal isLoading flag and not set the flag in redux. Something like:
const apiCallWithLoadingIndicator = () => {
const [isLoading, setIsLoading] = useState(true);
const dispatch = useDispatch();
useEffect(() => {
(async () => {
await dispatch(asyncThunkReturningPromise());
setIsLoading(false);
})()
}, [setIsLoading, dispatch]);
return isLoading;
}
There doesn't seem to be a simple way of achieving this with redux-saga, where generators are used instead of promises. What is the best practice for handling loading flags with functional components in redux-saga?

Generally: componentWillMount has been deprecated for years and will probably be removed in React 18 - you should not be using that in class components either.
That said, usually it helps to just start off the initial state with something like a "uninitialized" state value that tells you that, while it is not loading, it also hasn't even started doing anything yet and to handle that the same as "loading" in your component.

Related

Can I await the update function of the useState hook

I am building an app to understand the useState hook. This app simply has a form for entering username. I am trying to save the entered username. So, I have used react useState. And I tried to await the updating function of the useState in the event handler.
const usernameChangeHandler = async (event) => {
await setEnteredUsername(event.target.value);
console.log(enteredUsername, enteredAge);
};
And when I tried to log the username it doesn't show us the current state but the previous state. Why?
const usernameChangeHandler = async (event) => {
await setEnteredUsername(event.target.value);
console.log(enteredUsername, enteredAge);
};
enteredUsername is never going to change. It's a closure variable that's local to this single time you rendered the component. It's usually a const, but even if it was made with let, setEnteredUsername does not even attempt to change its value. What setEnteredUsername does is ask react to rerender the component. When the render eventually happens, a new local variable will be created with the new value, but code from your old render has no access to that.
If you need to run some code after calling setEnteredUsername, but you don't actually care if the component has rerendered yet, the just use the value in event.target.value, since you know that's going to be the new value of the state:
const usernameChangeHandler = (event) => {
setEnteredUsername(event.target.value);
console.log(event.target.value, enteredAge);
}
If instead you need to make make sure that the component has rerendered and then do something after that, you can put your code in a useEffect. Effects run after rendering, and you can use the dependency array to make it only run if the values you care about have changed:
const [enteredUsername, setEnteredUsername] = useState('');
useEffect(() => {
console.log('rendering complete, with new username', enteredUsername);
}, [enteredUsername]);
const usernameChangeHandler = (event) => {
setEnteredUsername(event.target.value);
};
the act of setting state is asynchronous; therefore, console logging directly after setting your state will not accurately provide you with how state currently looks. Instead as many have suggested you can utilize the useEffect lifecycle hook to listen for changes in your enteredUserName state like so:
useEffect(() => {
console.log(enteredUsername);
}, [enteredUsername]);
listening for changes within the useEffect will allow you to create side effects once state has updated and caused your component to rerender. This in turn will trigger your useEffect with the enteredUsername dependency, as the enteredUserName state has changed.

React useState hook always comes as undefined when setting it in useEffect

In my React application, I have a useEffect that checks if an element has the display style none set to it. If it does then it should set the state to false, however it always comes back as undefined.
const [testingProp, setTestingProp] = useState();
useEffect(() => {
const styles = getComputedStyle(customerPropertyTypeSection.current);
if (styles.display == 'none') {
setTestingProp(false);
console.log('style set to none'); // this prints
console.log(testingProp); // this prints 'undefined'
}
}, []);
setState in React acts like an async function.
So putting a console.log(state) right after setting it, will most likely show the former value, which is undefined in this case, as it doesn't actually finish updating the state until the log command runs.
You can use a deticated useEffect hook with the relevant state as a dependency to act upon a change in the state.
Example:
useEffect(() => {
console.log(state);
}, [state]);
Basically, the callback function in the example will run every time the state changes.
P.S. - maybe you can do without the useEffect you are using here to populate the state.
If you have access to customerPropertyTypeSection.current initially, you can do something like this:
const [testingProp, setTestingProp] = useState(() => {
const styles = getComputedStyle(customerPropertyTypeSection.current);
return styles.display !== 'none';
});
If the example above works for you, then the useEffect you are using is redundant and can be removed.

Do I have to worry about useState causing a re-render?

NB: I've asked this on wordpress.stackexchange, but it's not getting any response there, so trying here.
I'm not sure if this is WordPress specific, WordPress's overloaded React specific, or just React, but I'm creating a new block plugin for WordPress, and if I use useState in its edit function, the page is re-rendered, even if I never call the setter function.
import { useState } from '#wordpress/element';
export default function MyEdit( props ) {
const {
attributes: {
anAttribute
},
setAttributes,
} = props;
const [ isValidating, setIsValidating ] = useState( false );
const post_id = wp.data.select("core/editor").getCurrentPostId();
console.log('Post ID is ', post_id);
const MyPlaceholder = () => {
return(
<div>this is a test</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
If I comment out const [ isValidating, setIsValidating ] = useState( false ); then that console.log happens once. If I leave it in, it happens twice; even if I never check the value of isValidating, never mind calling setIsValidating. I don't want to over-optimize things, but, equally, if I use this block n times on a page, the page is getting rendered 2n times. It's only on the admin side of things, because it's in the edit, so maybe not a big deal, but ... it doesn't seem right. Is this expected behavior for useState? Am I doing something wrong? Do I have to worry about it (from a speed perspective, from a potential race conditions as everything is re-rendered multiple times)?
In your example code, the console.log statement is being immediately evaluated each time and triggering the redraw/re-rendering of your block. Once console.log is removed, only the state changes will trigger re-rendering.
As the Gutenberg Editor is based on Redux, if the state changes, any components that rely on that state are re-rendered. When a block is selected in the Editor, the selected block is rendered synchronously while all other blocks in the Editor are rendered asynchronously. The WordPress Gutenberg developers are aware of re-rendering being a performance concern and have taken steps to reduce re-rendering.
When requesting data from wp.data, useEffect() should be used to safely await asynchronous data:
import { useState, useEffect } from '#wordpress/element';
export default function MyEdit(props) {
...
const [curPostId, setCurPostId] = useState(false);
useEffect(() => {
async function getMyPostId() {
const post_id = await wp.data.select("core/editor").getCurrentPostId();
setCurPostId(post_id);
}
getMyPostId();
}, []); // Run once
const MyPlaceholder = () => {
return (
<div>Current Post Id: {curPostId}</div>
);
};
const Component = MyPlaceholder;
return <Component />;
}
As mentioned in the question, useState() is used in core blocks for setting and updating state. The state hook was introducted in React 16.8, its a fairly recent change and you may come across older Gutenberg code example that set state via the class constructor and don't use hooks.
Yes, you have to worry about always put an array of dependencies, so that, it won't re-render, As per your query, let's say are planning to edit a field here is the sample code
const [edit, setEdit]= useState(props);
useEffect(() => {
// logic here
},[edit])
that [edit] will check if there is any changes , and according to that it will update the DOM, if you don't put any [](array of dependencies) it will always go an infinite loop,
I guess this is expected behavior. If I add a similar console.log to native core blocks that use useState, I get the same effect. It seems that WordPress operates with use strict, and according to this answer, React double-invokes a number of things when in strict mode.

How to avoid the memory leak warning in my react application, even though i am cleaning up my useEffect?

Warning: Can't perform React state update on the unmounted component
App.js
App.js
App.js
After using the useIsMount approach, i am unable to pass the state value as props
This happens when you try to set state on a component that has already unmounted. This is commonly seen in async operations where you make an async request, and then you try to set state once it resolves.
The process goes like this:
Component mounts
useEffect runs and fires the async request
The async function creates a closure around the set state function
The component unmounts (due to a change in UI for whatever reason)
The request resolves
The closured set state function is called - but the component that contains the state has already unmounted! Error happens here.
To fix this, you need to check to see if the component is still mounted before you call the state update function.
You can check to see if a component is mounted by using something like a useIsMounted hook. There are many examples of this hook online.
HMR commented with a link to a good useIsMounted hook, which I'l post here:
import { useRef, useEffect } from 'react';
const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
return isMounted;
};
export default useIsMounted;
To use the hook, you call it in your component:
const isMounted = useIsMounted()
And inside your async function, check to see if the component is mounted before setting state:
if(isMounted.current) {
// it's okay to set state here
}
Make sure you add the isMounted variable as a dependency of your useEffect so you have the most up to date value!

React hooks states aren't updating in log

If I write
function Component() {
const [isLoading, setLoading] = useState(true);
const request = () => {
setLoading(true)
console.log(isLoading)
setLoading(false)
console.log(isLoading)
}
}
It will log out 'true' both times. Why isn't the state updating in the console? Eventhough it works fine in the DOM.
this.setState({ ...} will show the new state value
Setting a new value in state by using setState or useState hook is an asynchronous process.
If you want to log the new value once it has changed, you have to couple it with the useEffect hook
useEffect(() => {
console.log(isLoading)
}, [isLoading]);
Arnaud's answer is 100% correct, but what confused me when playing around with this stuff was that calling setState twice didn't have the same effect as trying to console.log directly after an update.
This is because of how React handles state updates internally, and even though your log may be wrong, your state is actually updated appropriately.

Resources