I have a functional component and some function is called directly in the body (that function changes the state && it's needed to be run each render). Is it 100% okay, or could it cause bugs?
const myFunc = (setMyState, ...) => {
...
setMyState(...)
}
const MyComponent = () => {
const [myState, setMyState] = useState(...)
myFunc(setMyState, ...)
return(<div>some content...</div>)
}
This will cause an infinite loop. This is why:
component renders first time
function updates component state
component renders because of state update
function runs again and updates component again
etc
You can definitely avoid this by using if-statements inside the function, but the best way to update a component state is either by user input/api calls or props updating(your case I suppose)
The best way to do that in my opinion is using the useEffect hook
Related
Let's say I have this simple dummy component:
const Component = () => {
const [state, setState] = useState(1);
setState(1);
return <div>Component</div>
}
In this code, I update the state to the same value as before directly in the component body. But, this causes too many re-renders even if the value stayed the same.
And as I know, in React.useState, if a state value was updated to the same value as before - React won't re-render the component. So why is it happening here?
However, if I try to do something simillar with useEffect and not directly in the component body:
const Component = () => {
const [state, setState] = useState(1);
useEffect(()=>{
setState(1);
},[state])
return <div>Component</div>
}
This is not causing any infinte loop and goes exactly according to the rule that React won't re-render the component if the state stayed the same.
So my question is: Why is it causing an infinte loop when I do it directly in the component body and in the useEffect it doesn't?
If someone has some "behind the sences" explanation for this, I would be very grateful!
TL;DR
The first example is an unintentional side-effect and will trigger rerenders unconditionally while the second is an intentional side-effect and allows the React component lifecycle to function as expected.
Answer
I think you are conflating the "Render phase" of the component lifecycle when React invokes the component's render method to compute the diff for the next render cycle with what we commonly refer to as the "render cycle" during the "Commit phase" when React has updated the DOM.
See the component lifecycle diagram:
Note that in React function components that the entire function body is the "render" method, the function's return value is what we want flushed, or committed, to the DOM. As we all should know by now, the "render" method of a React component is to be considered a pure function without side-effects. In other words, the rendered result is a pure function of state and props.
In the first example the enqueued state update is an unintentional side-effect that is invoked outside the normal component lifecycle (i.e. mount, update, unmount).
const Component = () => {
const [state, setState] = useState(1);
setState(1); // <-- unintentional side-effect
return <div>Component</div>;
};
It's triggering a rerender during the "Render phase". The React component never got a chance to complete a render cycle so there's nothing to "diff" against or bail out of, thus the render loop occurs.
The other example the enqueued state update is an intentional side-effect. The useEffect hook runs at the end of the render cycle after the next UI change is flushed, or committed, to the DOM.
const Component = () => {
const [state, setState] = useState(1);
useEffect(() => {
setState(1); // <-- intentional side-effect
}, [state]);
return <div>Component</div>;
}
The useEffect hook is roughly the function component equivalent to the class component's componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods. It is guaranteed to run at least once when the component mounts regardless of dependencies. The effect will run once and enqueue a state update. React will "see" that the enqueued value is the same as the current state value and won't trigger a rerender.
Similarly you could use the useEffect hook and completely remove the dependency array so it's an effect that would/could fire each and every render cycle.
const Component = () => {
const [state, setState] = useState(1);
useEffect(() => {
setState(1);
});
return <div>Component</div>;
}
Again, the useEffect hook callback is guaranteed to be invoked at least once, enqueueing a state update. React will "see" the enqueued value is the same as the current state value and won't trigger a rerender.
The takeaway here is to not code unintentional and unexpected side-effects into your React components as this results in and/or leads to buggy code.
When invoking setState(1) you also trigger a re-render since that is inherently how hooks work. Here's a great explanation of the underlying mechanics:
How does React.useState triggers re-render?
I have the following component where within the useEffect, I am calling some data reading related
functions meant to happen once on load.
The problem is, some of the prop data are not available at this stage (still undefined) like the prodData and index.
They are only available when I get into the Nested components like <NestedComponent1 />.
I wish to move this logic into the nested components which will resolve this issue.
But I do not want to repeat these code inside the useEffect for each component. Instead looking to write these 7 lines once maybe in a function
and just call it with the 3 NestedComponents.
Issue is that there is a higher order function wrapping here plus all the values like prodData and index is coming from Redux store.
I can't just move all these logic inside useEffect into a normal JS function and instead need a functional component for this.
And if I make a functional component to perform these operations, I can't call it in the useEffect for each of the NestedComponents.
Cos this is not valid syntax.
React.useEffect(() => {
<NewlyCreatedComponentWithReadingFunctionality />
}, []);
Thus my query is, is there a way I could write a functional component which has the data reading logic inside its useEffect.
And then extend this functional component for each of the functional components so that the useEffect would just fire
when each of these NestedComponents are called?
Doesn't seem to be possible to do this thus looking for alternatives.
This is the existing component where some of these prop values are undefined at this stage.
const MyComponent = ({
prodData,
index,
country,
highOrder: {
AHigherOrderComponent,
},
}) => {
// this is the logic which I am looking to write once and be
// repeatable for all the NestedComponent{1,2,3}s below.
React.useEffect(() => {
const [, code] = country.split('-');
const sampleData = prodData[index].sampleData = sampleData;
const period = prodData[index].period = period;
const indication = prodData[index].indication = indication;
AHigherOrderComponent(someReadDataFunction(code, sampleData));
AHigherOrderComponent(someReadDataFunction(code, period);
AHigherOrderComponent(someReadDataFunction(code, indication);
}, []);
return (
{/* other logics not relevant */}
<div>
<div>
<NestedComponent1 />
<NestedComponent2 />
<NestedComponent3 />
</div>
</div>
);
};
export default connect( // redux connect
({
country,
prodData,
index,
}) => ({
country,
prodData,
index,
})
)(withHighOrder(MyComponent));
React components implement a pattern called composition. There are a few ways to share state between parts of your React application but whenever you have to remember some global state and offer some shared functionality, I would try and manage that logic inside a context provider.
I would try the following:
Wrap all your mentioned components inside a context provider component
Offer the someReadDataFunction as a callback function as part of the context
Within your provider, manage react state, e.g. functionHasBeenCalled that remembers if someReadDataFunction has been called already
Set functionHasBeenCalled to true inside someReadDataFunction
Call someReadDataFunction inside your components within a useEffect based on the props data
This way, your application globally remembers if the function has been executed already but you can still use the latest data within your useEffect within your components to call someReadDataFunction.
I understand why useCallback is good. It doesn't create new functions if deps didn't change.
Is the same thing true for state functions ?
for example:
// parent component
const [val, setVal] = useState<boolean>(false);
<childComponent
:setVal={setVal}
/>
Now if parent component re-renders for some other reason than val field, setVal function will be created again, which will cause childComponent to re-render again for no obvious reasons. Is this true ? and if so, I guess, there's no way to work around this ?
No it's fine to pass setVal. useState takes care of returning the same setVal instance on every render cycle.
Is it possible to reload a component in react without using the help of a function like onClick. I want my component to reload again after it just got loaded.
I tried using
window.location.reload(false); in the constructor. I even tried using useEffect() but they make the page reload infinitely.
Is there any work around?
I have seen people accomplish this by setting a dummy setState method that is strictly used to trigger a refresh. Consider this component:
export const Proof = () => {
console.log("Component re-rendered")
const [dummyState,rerender] = React.useState(1);
const onClick = () => {
rerender(dummyState + 1);
}
React.useEffect( () => {
console.log("dummyState's state has updated to: " + dummyState)
}, [dummyState])
return(
<div>
<button onClick={onClick}>reRender</button>
</div>
)
}
Whenever you want to rerender the component you just need to trigger a change in the dummyState. Clicking the button will cause the components state to change, which will cause the component to rerender (i used a console.log() for proof). It is worth noting that simply calling a method that instantly changes the state of the component will result in an infinite loop.
From reading your comment above, it sounds like you are passing a value as a prop to a child component and you would like the value to be recalculated as soon as any interaction with that component occurs. Ideally, the interaction itself should cause the recalculation. But if you would just like to quickly recalculate the value and rerender the component as soon as it renders then i think this would work:
export const Proof = () => {
console.log("Component re-rendered")
const [dummyState,rerender] = React.useState(1);
//the empty brackets will cause this useEffect
//statement to only execute once.
React.useEffect( () => {
rerender(dummyState + 1);
}, [])
return(
<div>
<p>dummyState</p>
</div>
)
}
You could also recalculate the value in the useEffect method, as it will get called as soon as the component initially renders but not on any subsequent rerenders (due to the empty brackets as the second parameter to the useEffect method)
Have you tried to enter some dependecies to your useEffect hook ?
Like :
useEffect(() => {
console.log('hello');
}, [isLoad];
Here you're component will re-render only if isLoad is changing.
React docs state: don’t call Hooks inside loops, conditions, or nested functions.
Does calling a hook means just calling useState e.g. const [state, useState] = useState(0)?
What about calling setter in conditionals ?
Is this code breaking rules of hooks ?
const [oneHook, setOneHook] = useState(0)
const [anotherHook, setAnotherHook] = useState(false)
if (something) {
setOneHook(1)
setAnotherHook(true)
} else {
setOneHook(0);
setAnotherHook(false)
}
Thanks !
No, that code example does not break the rules of hooks. Every time the component renders there will be exactly the same number of calls to useState in exactly the same order, so that will be fine.
I do want to point out that setting state right away in the body of the component doesn't make much sense. When the component mounts it will start rendering with the initial values from state, but then before it can finish rendering the state has already changed and it has to start over. But presumably that's just an artifact of the example, and in your real code the if would be inside a useEffect or some other practical code location.
React docs state: don’t call Hooks inside loops, conditions, or nested functions.
Alright,the following code shows the example for the above statement. I had the same issue where i needed to set the state inside of a loop, like following
const [someArray, setArray] = useState([]);
someValue.forEach((value, index) => {
setArray([...someArray, value]) //this should be avoided
});
Above thing i have achieved like this following
var temp = [];
var counter = 0;
someValue.forEach((value, index) => {
temp.push(value);
counter++;
if (counter === someValue.length) {
setArray(temp)
}
});
if you are setting a state inside the loop than each time the component re renders which you do not want to get into.
Is this code breaking rules of hooks
No Your code looks fine, as you are setting up the state only based on condition for only once when the component renders