Mid Component Life Cycle API Call - reactjs

From my understanding fetching data is considered a side effect and should be within the use effect. But I'm wondering if this rule can be broken mid component lifecycle. It's obvious why useEffect is important for fetching data when the component initially mounts. It's less obvious after.
Here is an example of what I'm doing. It seems to work fine, and after the data is fetched the component rerenders.
function ComponentThatFetchesList() {
const [list, setList] = useState([]);
const fetchData = () => {
asyncFetchData().then(data => {
setList(list);
});
}
return (
<div>
<button onClick={fetchData}>fetch data</button>
<ui>{list.map(l => (<li>{l}</li>))}</ul>
</div>
);
}
But from my understanding fetching data is considered a side effect and should be within the use effect. So perhaps something like the following is recommended (I added searchText to have something to trigger the useEffect).
function ComponentThatFetchesList() {
const [list, setList] = useState([]);
const [searchText, setSearchText] = useState("");
useEffect(() => {
asyncFetchData(searchText).then(data => {
setList(list);
});
}, [searchText]);
return (
<div>
<input box to enter search text>
<button that sets search text>
<ui>{list.map(l => (<li>{l}</li>))}</ul>
</div>
);
}
Am I at risk of introducing bugs to be found later if I go with the first method?

Welcome #daniel-longfellow to Stack Overflow 👋
The method looks fine as the data loading occurs on a user event.
(And to micro-optimization, you might wrap fetchData in useCallback but if there is no performance issue, it'd only it make it harder to read).
You'd use the second (useEffect) when the component needs to fetch automatically or a state changes.
But as I do not know the exact use-cases, you can go with either one and refactor later on.
And lastly, this question might not be a good Stack Overflow topic as it's not a specific problem but can be opinionated (a recommend question).
Please refer to What topics can I ask about here?
I 👍 as you are a new contributor so won't be aware of it 😉.

Related

React hooks : how can I update a state within useEffect when the state itself is the dependency?

I know there already are already some related questions, like How can React useEffect watch and update state?, but still, I don't get it totally.
Let's say I set an index state based on a prop; and I need to sanitize that value anytime it is set.
<MyComponent index={4}/>
This is how I attempted to do it:
useEffect(() => {
setIndex(props.index);
}, [props.index]);
useEffect(() => {
const sanitized = sanitizeIndex(index);
setIndex(sanitized);
},[index])
const sanitizeIndex = index => {
//check that index exists in array...
//use fallback if not...
//etc.
return index
}
It does not work (infinite loop), since the state is watched and updated by the second useEffect().
Of course, I could avoid this by calling sanitizeIndex() on the prop, so I only need a single instance of useEffect():
useEffect(() => {
setIndex(sanitizeIndex(props.index));
}, [props.index]);
Thing is, I call setIndex plenty of times in my code, and I fear to miss using sanitizeIndex.
Is there another way to "catch" and update a state value being set ?
Thanks !
This seems like a good case for a custom hook. Here's an example of how to implement one for your case (given the information currently provided in your question), including comments about how/why:
Be sure to read the documentation for useCallback if you are not already familiar with it. It's especially important to understand how to use the dependency array (link 1, link 2) when using hooks which utilize it (like useCallback and useEffect).
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.16.12/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useCallback, useEffect, useState} = React;
/**
* You didn't show exactly how you are sanitizing, so I'm using this function
* instead. It will simply make sure the input number is always even by
* adding 1 to it if it's odd.
*/
function makeEven (n) {
return n % 2 === 0 ? n : n + 1;
}
function useSanitizedIndex (sanitizeIndex, unsanitizedIndex) {
const [index, setIndex] = useState(sanitizeIndex(unsanitizedIndex));
// Like setIndex, but also sanitizes
const setSanitizedIndex = useCallback(
(unsanitizedIndex) => setIndex(sanitizeIndex(unsanitizedIndex)),
[sanitizeIndex, setIndex],
);
// Update state if arguments change
useEffect(
() => setSanitizedIndex(unsanitizedIndex),
[setSanitizedIndex, unsanitizedIndex],
);
return [index, setSanitizedIndex];
}
function IndexComponent (props) {
// useCallback memoizes the function so that it's not recreated on every
// render. This also prevents the custom hook from looping infinintely
const sanitizeIndex = useCallback((unsanitizedIndex) => {
// Whatever you actually do to sanitize the index goes in here,
// but I'll just use the makeEven function for this example
return makeEven(unsanitizedIndex);
// If you use other variables in this function which are defined in this
// component (e.g. you mentioned an array state of some kind), you'll need
// to include them in the dependency array below:
}, []);
// Now simply use the sanitized index where you need it,
// and the setter will sanitize for you when setting it (like in the
// click handler in the button below)
const [index, setSanitizedIndex] = useSanitizedIndex(sanitizeIndex, props.index);
return (
<div>
<div>Sanitized index (will always be even): {index}</div>
<button onClick={() => setSanitizedIndex(5)}>Set to 5</button>
</div>
);
}
function Example () {
const [count, setCount] = useState(0);
return (
<div>
<div>Count: {count}</div>
<button onClick={() => setCount(n => n + 1)}>Increment</button>
<IndexComponent index={count} />
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>
So think of useEffect like an event listener in javascript. It's not the same thing, but think of it like that. The event or "what's being watched", in this case, you've asked it to watch props.index. It will run that function inside the useEffect every time anything in the dependency array (props.index - in your case) changes. So what's happening here is you're updating props.index every time props.index changes. This is your infinite loop.
Couple things here, create a copy of props.index as something ie.
const [something, setSomething = useState(props.index);
(I won't get into destructuring, but worth looking that up too)
You don't want to manipulate your props directly the way you are doing.
This solves that, as well as gives you the correct way to look at your useEffect. Since you want to update something whenever that prop changes, you could leave props.index (again look up destructuring) in your dependency array, and change the useEffect to:
const [something, setSomething] = useState(props.index);
useEffect(() => {
setSomething(props.index);
}, [props.index]);
As another pointed out, this is difficult without knowing exactly what you're doing, but this is kind of an overview which hopefully helps you understand what is going on here and why you're getting a loop here.
You mentioned you fear missing out on sanitizing, then you should not be using setIndex directly. Instead, you can create a new function to santize and set the index.
useEffect(() => {
setSanitizeIndex(props.index);
}, [props.index]);
const setSanitizeIndex = (value) => {
const sanitizeIndex = sanitizeIndex(value);
setIndex(sanitizeIndex)
}
With that, you should not be calling setIndex any more in your codes, but only call setSanitizeIndex.
One potential solution for this that I have done in the past with a similar issue is to indirectly trigger the useEffect. Create a dummy state that does not relate to the state being updated and update the dummy state whenever you want the effect to be triggered.
const [dummyState, setDummyState] = useState(0)
useEffect(() => {
setIndex(props.index);
}, [props.index]);
useEffect(() => {
const sanitized = sanitizeIndex(index);
setIndex(sanitized);
},[dummyState])
const sanitizeIndex = index => {
//check that index exists in array...
//use fallback if not...
//etc.
return index
}
return (
//update the state you want to update then update the dummy state to
//trigger the useEffect
<button onClick={() =>{setIndex(123);
setDummyState(dummyState++);}/>
)
I think the accepted answers solution is a lot less janky but in more complex situations this might be your easiest and most easy-to-understand solution

is useCallback a performance boost for the react app?

could someone explain to me if the use of useMemo and useCallback is expensive or cheap? I've checked the React's source code and I think they are cheap to use, but I've also read some people saying they are not.
In a Next.js context using useCallback:
const MyComp = () => {
const router = useRouter():
const handleClick = useCallback(() => router.push('/some-path'), [router]);
return <button onClick={handleClick} />
}
vs plain arrow function here:
const MyComp = () => {
const router = useRouter():
return <button onClick={() => router.push('/some-path')} />
}
Am I saving re-renders with useCallback?
The cost of memoize and comprare the dependencies array [router], is more expensive?
Additional info: Checking the React's code I saw that they compare the deps array items using Object.is instead of === or ==.
Someone knows why?
Also checking this component render:
import {useCallback, useState} from "react";
let i = 0
const CallbackDemo = () => {
const [value, setValue] = useState('');
console.log('render', i++);
const handleClick = useCallback(() => setValue(Math.random()), []);
return (
<div>
<span>{value}</span>
<button onClick={handleClick}>New set</button>
</div>
);
}
export default CallbackDemo;
I saw the same count of renders using or not using useCallback
Am I saving re-renders with useCallback?
Not in that specific case, no. The useCallback code does let you save roughly the cost of an assignment statement because it doesn't have to update the click handler on the button element, but at the cost of a function call and going through the array of dependencies looking for differences. So in that specific case, it's probably not worth doing.
If you had a more complex child component, and that component was optimized not to re-render when called with the same props (for instance, via React.memo or React.PureComponent or similar), or you were passing the function to a bunch of child components, then you might get some performance improvements by not making them re-render.
Consider this example, where simpleClickHandler is always recreated but memoizedClickHandler is reused¹ via useCallback:
const { useState, useCallback } = React;
const Parent = () => {
const [counter, setCounter] = useState(0);
console.log("Parent called");
const simpleClickHandler = () => {
console.log("Click occurred");
setCounter(c => c + 1);
};
const memoizedClickHandler = useCallback(() => {
console.log("Click occurred");
setCounter(c => c + 1);
}, []);
return (
<div>
Count: {counter}
<Child onClick={simpleClickHandler} handlerName="simpleClickHandler">simpleClickHandler</Child>
<Child onClick={memoizedClickHandler} handlerName="memoizedClickHandler">memoizedClickHandler</Child>
</div>
);
};
const Child = React.memo(({handlerName, onClick, children}) => {
console.log(`Child using ${handlerName} called`);
return (
<div onClick={onClick}>{children}</div>
);
});
ReactDOM.render(<Parent/>, document.getElementById("root"));
<div id="root"><?div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
Note that when you click, only the child being passed simpleClickHandler re-renders, not the one being passed memoizedClickHandler, because memoizedClickHandler's value is stable but simpleClickHandler's value changes every time.
Using useCallback requires more work in the parent (checking to see if the previous function can be reused), but may help save work in child components.
It's important to note that the stability useCallback (and useMemo) give you are only appropriate for performance reasons, not correctness. React can throw away the previous copy of a handler and new a new one if it wants to, even if the deps haven't changed. If you need a correctness guarantee (the result definitely 100% will not change unless the deps change), you have use a ref. But that's a very rare use case.
Additional info: Checking the React's code I saw that they compare the deps array items using Object.is instead of === or ==.
Someone knows why?
Primarily because NaN === NaN is false, because all comparisons with NaN are false. So if they used ===, any deps array containing NaN would always be considered different from the previous one. But using Object.is avoids that problem, because Object.is(NaN, NaN) is true.
¹ Note that "reused" here means: a new handler is created every time, just like simpleClickHandler, but useCallback may return the previous handler you've given it if the deps haven't changed, so the new one is thrown away. (JavaScript engines are really quick at allocating and reclaiming short-lived objects.)
useCallback like useMemo indeed improve performance
but!!! you don't need to use it to everything because it will make your website slower.
this is better for the heavy lifting components like for charts and stuff like that.
that consume a lot of resource and take a lot of time to process and this will make this specific component load much more faster and not stuck everytime you do a change.
you can see deep compression like this:
react useEffect comparing objects
this link will show you for instance how to do a deep compression for use effect

Why do multiple setState calls in an async function cause multiple renders?

The issue is summarized well here; basically, if you have an async function in useEffect (which is where you'd expect such functions), you cause a re-render of your component for every state that is updated. I generally don't want to be bunching up things as in the authors solution/workaround, and to me this behavior doesn't make sense (you'd expect all your state updates to happen together).
Is this by design? If so, is there a better way to deal with this, such that I can perform all my updates without having to worry about ordering, etc? This feels like a bug, but maybe just a flaw in my understanding.
Code for reference:
export default function App (): JSX.Element {
const [user, setUser] = useState(null)
const [pending, setPending] = useState(false)
useEffect(() => {
setPending(true)
fetchUser().then((fetchedUser) => {
setPending(false)
setUser(fetchedUser) // This updated value won't initially be seen by other effects dependant on 'pending'
})
}, [])
// …
}
Ended up figuring this out; see this. You need to manually batch things currently, with ReactDOM.unstable_batchedUpdates(() => { ... }). Despite the name, it is seemingly widely regarded as quite stable.
It is also corrected in the currently in-development Concurrent Mode for React.

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.

Should useEffect be the preferred hook for large data handling?

I'm trying to overcome rendering delays and general "took too long" violation errors in the console.
For example, I have an onClick that calls setShowModal(true). When showModal === true it renders a component that processes a lengthy object. This results in onClick causing a Violation] 'click' handler took 1000ms console warning and significant latency in the user's ability to interact with the app while this component prepares to render.
To get around this, I implemented the following:
function SlowComponent(props.lengthyObject) {
const [initialRenderingComplete, setRenderingComplete] = useState(false);
const [isProcessing, setIsProcessing] = useState(true)
const [slowProcessedObject, setSlowProcessedObject] = useState([])
useEffect(() => {
if(initialRenderingComplete) {
setSlowProcessedObject(
processData(props.lengthObject)
)
}, [initialRenderComplete])
return isProcessing
? 'This will render pretty quickly'
: <div>
This will take up to 1 second to become available and the browser will complain if I don't render something while the data is processing
<button onClick={() => triggerAnotherSlowProcess()}>Update lengthy object</button>
</div>;
}
In short, is this how useEffect is intended to be used or should I be taking another hook or strategy into consideration? Are there any risks of memory leaks with this setup?

Resources