React.js infinite re-render - javascript

i got a issue with my code. My function "getNames" rerender may times, but i want it to render once? have you got any clue ?
import grounds from './../../UballersGroundsData.json';
export default function Groundlist() {
function getNames(jsonObj){
for(let item in jsonObj){
console.log("item = " + item);
for(let property in jsonObj[item] ){
console.log(jsonObj[item]);
// if (property === "groundName"){
// console.log(jsonObj[item][property]);
// }
}
}
}
return(
<div>
<h1>Hello world!</h1>
<ul>
{getNames(grounds)}
</ul>
</div>
)
}
Thank you !

You should put your function inside of a useEffect hook, then set it to a state hook with useState. Then, map out the list items for your list (assuming you are returning an array from your function). If you want it to only run getNames on the first render, you would set it up the useEffect hook with an empty dependency array. Code should look something like this:
import React, { useEffect, useState } from 'react'
import grounds from './../../UballersGroundsData.json';
export default function Groundlist() {
const [names, setNames] = useState([]) // Initial state with empty array
useEffect(() => {
function getNames(jsonObj){
// your function logic here...
}
const result = getNames(grounds) // Call your function
setNames(result) // set it to names state hook
}, []) // Empty array here means it will only use the useEffect on the first render.
return(
<div>
<h1>Hello world!</h1>
<ul>
{Array.from(names).map(name => <li>{name}</li>)}
</ul>
</div>
)
}

You can use useMemo react hook to memoize the returned value i.e. skip unnecessary / heavy calculations due to change in other state, props or context variables.
Example:
import { useMemo } from "react"
export default function Groundlist(props) {
const grounds = props.data // if grounds is passed as props from Parent component
const groundsMemo = useMemo(() => {
// do all the heavy calculations here
// (e.g. do the work of getNames function)
// and return some JSX or Array (data)
// returned value will be memoized;
// means it will be re-calculated only if "grounds" changes
// Hence, no unnecessary calls to getNames (heavy calculations)
}, [grounds])
return (
<div>
{/* Use this if groundsMemo is JSX */}
<ul>{groundsMemo}</ul>
{/* Use this if groundsMemo is an Array (data) */}
<ul>{groundsMemo.map(item => <li key={some_key}>
{item.property}
</li>)}</ul>
</div>
)
}

Try using useMemo and useCallBack you want to optimize your react app.
React Official docs clearly described how to use it: useMemo
useCallBack

You should never call a function inside return scope of render.
It's normal for a component to re-render without proper treatment.
Taking in mind the other 2 answers
You can use the full power of useEffect, useCallback and React.memo to prevent anything from re-render.
import React from 'react';
import grounds from './../../UballersGroundsData.json';
function Groundlist() {
// initiate state
const [names, setNames] = React.useState([]);
// This will prevent the Function from recalculate - useCallback
const getNames = React.useCallback(function (jsonObj) {
for(let item in jsonObj){
console.log("item = " + item);
for(let property in jsonObj[item] ){
console.log(jsonObj[item]);
// if (property === "groundName"){
// console.log(jsonObj[item][property]);
// }
}
}
}, []);
// Will make function run only once and nevermore - useEffect
React.useEffect(() => {
setNames(getNames());
}, [])
return(
<div>
<h1>Hello world!</h1>
<ul>
{names.map(a => <li>{a}</li>)}
</ul>
</div>
)
}
// Will prevent React from try to re-render without changing in props, so as your component has no props, will never re-render without yourself unmounting first
export default React.memo(Groundlist);
In another cases you can control exact when the component should recalculate your names using the last argument of functions
useCallback(() => {}, []) //<---
For example
useCallback(() => {}, [updateState]);
when updateState change the function will be recreated.

Related

re-render useState for each object in an array

When I click the MultipleComponent button, all logs in the function return null.
The second time I click it, it returns the previous values.
How can I get the current status in each log within the map function?
When I call the function in the useEffect hook, useEffect runs after the first render. I don't want it to run after the first render.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { observer } from "mobx-react-lite";
function App() {
const [component, setComponent] = useState([]);
useEffect(() => {});
const newArray = [1, 2, 3];
const Test = observer(() => {
return (
<div>
<p>Test</p>
</div>
);
});
const Test2 = observer(() => {
return (
<div>
<p>Test2</p>
</div>
);
});
const Test3 = observer(() => {
return (
<div>
<p>Test3</p>
</div>
);
});
function MultipleComponent() {
newArray.map(async (x) => {
if (x === 1) {
setComponent((ps) => [...ps, Test]);
console.log(component);
} else if (x === 2) {
setComponent((ps) => [...ps, Test2]);
console.log(component);
} else {
setComponent((ps) => [...ps, Test3]);
console.log(component);
}
});
}
return (
<div>
{component.map((Input, index) => (
<Input components={component} key={index} />
))}
<button onClick={() => setComponent([...component, Test])}>
Single Component
</button>
<button onClick={() => MultipleComponent()}>Multiple Component</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
codensadbox: https://codesandbox.io/s/react-hooks-useeffect-forked-shxvl6
When I click the MultipleComponent button, all logs in the function return null.
The second time I click it, it returns the previous values.
React state updates are asynchronous and do not update any values in the current scope. They trigger a render, and then your component executes again with that new state.
See this for more info: The useState set method is not reflecting a change immediately
How can I get the current status in each log within the map function?
You can't get the state that has been changed until the next render, but you don't need to because you have the value that you set. Just use that if you need to. For example:
setComponent((ps) => {
const newState = [...ps, Test];
console.log(newState);
return newState;
});
When I call the function in the useEffect hook, useEffect runs after the first render. I don't want it to run after the first render.
Your effect has no dependencies, which means that it will run after every render. Just pass an array of dependencies to the effect in order to only execute it when those change. If you pass an empty array, then it will only every execute once.
useEffect(() => console.log('I run only once'), []);
See the docs on useEffect for more: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Hi as per the documentation "calling the set function does not change state in the running code". If you need to use the next state, you can save it in a variable before passing it to the set function.
Here is a link of your updated code.
https://codesandbox.io/s/react-hooks-useeffect-forked-m7ipwb?file=/src/index.js
When I call the function in the useEffect hook, useEffect runs after the first render. I don't want it to run after the first render.
By default, Effects run after every render. You can tell React to skip unnecessarily re-running the Effect by specifying an array of dependencies as the second argument to the useEffect call. Start by adding an empty [] array

Prevent infinite loop on function component with default parameter and useEffect

When creating a function component in React and setting a default parameter everything works like expected and the component will be rendered once. But as soon as you add a hook like useEffect and use this parameter in the dependency array the component rerenders forever.
I've created a simple demo here: https://codesandbox.io/s/infinite-useeffect-loop-on-default-value-tv7hj?file=/src/TestComponent.jsx
The reason is quite obvious, because when using an object as default parameter, it will be created again and will not be equal to the previous one. And of course this doesn't happen on primitive default parameter values like number or string.
Is there any better way to avoid this side effect besides using defaultProps?
Yes, instead of setting the default value of value to being an object, just set it to false. Then check if value is truthy, if it is, then access the correct properties, otherwise, just show a default value. New code.
It would be something like:
import { useEffect, useState } from "react";
const TestComponent = ({ value = false }) => {
const [calcValue, setCalcValue] = useState(0);
useEffect(() => {
setCalcValue((cur) => cur + 1);
}, [value]);
return (
<div>
{value ? value.name : "Test"}:{calcValue}
</div>
);
};
The reason you get infinite loops is because the reference of value keeps changing.
The first time the component is rendered, it sees a new reference to value, which triggers the useEffect, which in turns modifies the state of the component, and this leads to a new render, which causes value to be re-created once again because the old reference to that variable has changed.
The easiest way to deal with this is to just create a default value outside the component and use that (basically the same as the defaultProps solution):
import { useEffect, useState } from "react";
const defaultValue = {name: "Test"}; // <-- default here
const TestComponent = ({ value = defaultValue }) => {
const [calcValue, setCalcValue] = useState(0);
useEffect(() => {
setCalcValue((cur) => cur + 1);
}, [value]);
return (
<div>
{value.name}:{calcValue}
</div>
);
};
Doing this will ensure that each time the component renders, it sees the same reference for value, therefore the useEffect hook only runs once.
Another way of dealing with this is to first wrap your component with memo, then create a new state variable which takes on the original value, and make your useEffect hook depend on this new state variable:
const TestComponent = React.memo(({ value = {name: "Test"} }) => {
const [calcValue, setCalcValue] = useState(0);
const [myValue, setMyValue] = useState(value);
useEffect(() => {
setCalcValue((cur) => cur + 1);
}, [myValue]);
return (
<div>
{myValue.name}:{calcValue}
</div>
);
});
The reason why we wrap the component with memo is so that it only re-renders after a state change if the prop had changed in value (instead of reference). You can change the way memo detects props changes by providing a custom comparison function as a second parameter.

Hook's useState initial value never changes in callback that is memoized

In this simple example, I am memoizing Child component using useMemo and passing callback back to parent function to add data to react hook array, which is initially set as empty.
My question is as follows: Why hook data never changes and keeps it's initial state in callback function that comes from Child component which is memoized. However, when checking data hook in useEffect method or using that hook in rendering - it always has the newest state.
It is more of a general question what happens behind the hood and what is the best way, for example, to check against duplicate values in callback functions from memoized child components if data always has initial state.
I am aware of useReducer and that I can manipulate data in hooks setter method before adding it, but maybe there are other methods?
Parent component:
import React, {useEffect, useState} from 'react';
import Child from "./Child";
function App() {
const [data, setData] = useState([]);
useEffect(()=>{
console.log("UseEffect", data)
},[data]);
return (
<div>
<Child callback={(val)=>{
console.log("Always returns initial state", data); // <----------Why?
setData(old=>{
console.log("Return previous state", old);
return [...old, val]
})
}} />
Length: {data.length /*Always gets updated*/}
</div>
);
}
export default App;
Child component: In real scenario it is a map, that I want to render only once, without causing re-renders.
import React, {useMemo} from "react"
export default function Child({callback}) {
return useMemo(()=>{
return <button onClick={()=>callback(1)}>
Press!
</button>
},[])
}
Is there a way to return new reference of callback, without adding dependencies in useMemo method
Like this
// returns *(always the same)* function that will forward the call to the latest passed `callback`
function useFunction(callback) {
const ref = React.useRef();
ref.current = callback;
return React.useCallback(function() {
const callback = ref.current;
if (typeof callback === "function") {
return callback.apply(this, arguments);
}
}, []);
}
and then:
export function Child({ callback }) {
const handler = useFunction(callback);
return useMemo(() => {
return <button onClick={() => handler(1)}>Press!</button>;
}, []);
}
or
function App() {
const [data, setData] = useState([]);
const callback = useFunction((val) => {
console.log("Always returns the latest state", data);
setData([...data, val]);
});
return (
<div>
<Child callback={callback} />
Length: {data.length /*Always gets updated*/}
</div>
);
}
function Child({ callback }) {
return useMemo(() => {
return <button onClick={() => callback(1)}>
Press!
</button>
}, [])
}
As you said, your Child component is memoized, which means it will only update if one of its dependency changes.
So at the first rendering, you create your Child component with the function you pass to the callback prop, and at this time, data is []. If you click on the button, data is updated correctly and so is the function passed to callback prop to Child, but since you did not set any dependency to useMemo, your Child component will not update and will still return its memoized version, with first callback prop it received, which indeed always log the initial value of data: [].
So all you need is to add callback to the list of dependencies of useMemo:
export function Child({ callback }) {
return useMemo(() => {
return <button onClick={() => callback(1)}>Press!</button>;
}, [callback]);
}
This way, when the callback prop changes, your Child component will also update its onClick event handler.
Also, I recommend using eslint-plugin-react npm package, which will instantly warn you about missing dependencies in React hooks, and more generally about bad practices in your code.

I have read the React Docs on Hooks and I'm confused. When is the useEffect Hook clean up function called?

The react docs explanation on when the useEffect clean up function is called is confusing and generic to be honest.
They even confuse you more by comparing the class mental model with hooks. Class based components work differently from function based components with hooks.
React remembers the effect function you provided to useEffect, and runs it after flushing changes to the DOM which is understandable.
Now how and when is the function which is returned ("clean up function") called?
Code example below:
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
The function returned from the function given as first argument to useEffect will be called when any element in the array given as second argument changes, or on every render if no second argument is given, or when the component is unmounted.
Example
const { useEffect, useState } = React;
function MyComponent({ prop }) {
useEffect(() => {
console.log('Effect!');
return () => console.log('Cleanup!')
}, [prop])
return (
<div>{prop}</div>
);
}
function App() {
const [value, setValue] = useState(0);
useEffect(() => {
setInterval(() => {
setValue(value => value + 1);
}, 1000)
}, [])
return value < 3 ? <MyComponent prop={value} /> : null;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Without using the second parameter it will get called on every render.
This is often overkill, so it often a good idea to use the second parameter even if it's empty []..
eg.
useEffect(() => {....}, []);
Doing the above will only then call the cleanup when the component is physically detached from the DOM.
You can also pass props instead of [], this is handy if say a prop change, like what chat room you was in, it would then cleanup the current chat room, and initialize the new chat room etc.
So in your example passing [props.friend.id] would make sense, because if the id changed, it would make sense to call the cleanup, and then run the effect again for the new id.

How to force a functional React component to render?

I have a function component, and I want to force it to re-render.
How can I do so?
Since there's no instance this, I cannot call this.forceUpdate().
🎉 You can now, using React hooks
Using react hooks, you can now call useState() in your function component.
useState() will return an array of 2 things:
A value, representing the current state.
Its setter. Use it to update the value.
Updating the value by its setter will force your function component to re-render,
just like forceUpdate does:
import React, { useState } from 'react';
//create your forceUpdate hook
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => value + 1); // update state to force render
// A function that increment 👆🏻 the previous state like here
// is better than directly setting `setValue(value + 1)`
}
function MyComponent() {
// call your hook here
const forceUpdate = useForceUpdate();
return (
<div>
{/*Clicking on the button will force to re-render like force update does */}
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
You can find a demo here.
The component above uses a custom hook function (useForceUpdate) which uses the react state hook useState. It increments the component's state's value and thus tells React to re-render the component.
EDIT
In an old version of this answer, the snippet used a boolean value, and toggled it in forceUpdate(). Now that I've edited my answer, the snippet use a number rather than a boolean.
Why ? (you would ask me)
Because once it happened to me that my forceUpdate() was called twice subsequently from 2 different events, and thus it was reseting the boolean value at its original state, and the component never rendered.
This is because in the useState's setter (setValue here), React compare the previous state with the new one, and render only if the state is different.
Update react v16.8 (16 Feb 2019 realease)
Since react 16.8 released with hooks, function components have the ability to hold persistent state. With that ability you can now mimic a forceUpdate:
function App() {
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
console.log("render");
return (
<div>
<button onClick={forceUpdate}>Force Render</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Note that this approach should be re-considered and in most cases when you need to force an update you probably doing something wrong.
Before react 16.8.0
No you can't, State-Less function components are just normal functions that returns jsx, you don't have any access to the React life cycle methods as you are not extending from the React.Component.
Think of function-component as the render method part of the class components.
Official FAQ now recommends this way if you really need to do it:
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
forceUpdate();
}
Simplest way 👌
if you want to force a re-render, add a dummy state you can change to initiate a re-render.
const [rerender, setRerender] = useState(false);
...
setRerender(!rerender); //whenever you want to re-render
And this will ensure a re-render, And you can call setRerender(!rerender) anywhere, whenever you want :)
I used a third party library called
use-force-update
to force render my react functional components. Worked like charm.
Just use import the package in your project and use like this.
import useForceUpdate from 'use-force-update';
const MyButton = () => {
const forceUpdate = useForceUpdate();
const handleClick = () => {
alert('I will re-render now.');
forceUpdate();
};
return <button onClick={handleClick} />;
};
Best approach - no excess variables re-created on each render:
const forceUpdateReducer = (i) => i + 1
export const useForceUpdate = () => {
const [, forceUpdate] = useReducer(forceUpdateReducer, 0)
return forceUpdate
}
Usage:
const forceUpdate = useForceUpdate()
forceUpdate()
If you already have a state inside the function component and you don't want to alter it and requires a re-render you could fake a state update which will, in turn, re-render the component
const [items,setItems] = useState({
name:'Your Name',
status: 'Idle'
})
const reRender = () =>{
setItems((state) => [...state])
}
this will keep the state as it was and will make react into thinking the state has been updated
This can be done without explicitly using hooks provided you add a prop to your component and a state to the stateless component's parent component:
const ParentComponent = props => {
const [updateNow, setUpdateNow] = useState(true)
const updateFunc = () => {
setUpdateNow(!updateNow)
}
const MyComponent = props => {
return (<div> .... </div>)
}
const MyButtonComponent = props => {
return (<div> <input type="button" onClick={props.updateFunc} />.... </div>)
}
return (
<div>
<MyComponent updateMe={updateNow} />
<MyButtonComponent updateFunc={updateFunc}/>
</div>
)
}
The accepted answer is good.
Just to make it easier to understand.
Example component:
export default function MyComponent(props) {
const [updateView, setUpdateView] = useState(0);
return (
<>
<span style={{ display: "none" }}>{updateView}</span>
</>
);
}
To force re-rendering call the code below:
setUpdateView((updateView) => ++updateView);
None of these gave me a satisfactory answer so in the end I got what I wanted with the key prop, useRef and some random id generator like shortid.
Basically, I wanted some chat application to play itself out the first time someone opens the app. So, I needed full control over when and what the answers are updated with the ease of async await.
Example code:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ... your JSX functional component, import shortid somewhere
const [render, rerender] = useState(shortid.generate())
const messageList = useRef([
new Message({id: 1, message: "Hi, let's get started!"})
])
useEffect(()=>{
async function _ () {
await sleep(500)
messageList.current.push(new Message({id: 1, message: "What's your name?"}))
// ... more stuff
// now trigger the update
rerender(shortid.generate())
}
_()
}, [])
// only the component with the right render key will update itself, the others will stay as is and won't rerender.
return <div key={render}>{messageList.current}</div>
In fact this also allowed me to roll something like a chat message with a rolling .
const waitChat = async (ms) => {
let text = "."
for (let i = 0; i < ms; i += 200) {
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
messageList.current.push(new Message({
id: 100,
message: text
}))
if (text.length === 3) {
text = "."
} else {
text += "."
}
rerender(shortid.generate())
await sleep(200)
}
if (messageList.current[messageList.current.length - 1].id === 100) {
messageList.current = messageList.current.filter(({id}) => id !== 100)
}
}
If you are using functional components with version < 16.8. One workaround would be to directly call the same function like
import React from 'react';
function MyComponent() {
const forceUpdate = MyComponent();
return (
<div>
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
But this will break if you were passing some prop to it. In my case i just passed the same props which I received to rerender function.
For me just updating the state didn't work. I am using a library with components and it looks like I can't force the component to update.
My approach is extending the ones above with conditional rendering. In my case, I want to resize my component when a value is changed.
//hook to force updating the component on specific change
const useUpdateOnChange = (change: unknown): boolean => {
const [update, setUpdate] = useState(false);
useEffect(() => {
setUpdate(!update);
}, [change]);
useEffect(() => {
if (!update) setUpdate(true);
}, [update]);
return update;
};
const MyComponent = () => {
const [myState, setMyState] = useState();
const update = useUpdateOnChange(myState);
...
return (
<div>
... ...
{update && <LibraryComponent />}
</div>
);
};
You need to pass the value you want to track for change. The hook returns boolean which should be used for conditional rendering.
When the change value triggers the useEffect update goes to false which hides the component. After that the second useEffect is triggered and update goes true which makes the component visible again and this results in updating (resizing in my case).

Categories

Resources