re-render useState for each object in an array - javascript

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

Related

React component rendering even when there is no change in the state value

From the React Docs, what I have learnt is that the component will re-render only if there is a change in the value of a state.
For instance
import React, { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
console.log("I am rendering");
const handleButtonClick = () => {
setCount(0);
};
return (
<>
<button onClick={handleButtonClick}>Increment</button>
Count value is: {count}
</>
);
}
The message I am rendering is printed only once even if we click the button because the setCount function is setting the value to 0 which is the present value of count
Since there is no change in the present and future value therefore, the Component does not re-render.
Unexpected Behaviour
However, the similar behaviour is not observed when we add an extra line setCount(1) before setCount(0)
import React, { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
console.log("I am rendering");
const handleButtonClick = () => {
setCount(1); //this line has been added extra
setCount(0);
};
return (
<>
<button onClick={handleButtonClick}>Increment</button>
Count value is: {count}
</>
);
}
In principle, there is no change in the output of the final count value. However, if we click the button, the component re-renders and prints the message I am rendering
I could not find an explanation for this behaviour. Is this behaviour on expected lines?.
Shouldn't the component re-render only when the final value of the state is different from the current value ?
Sometimes, Reacts needs another render phase to decide if it needs a bailout. By the way, when we saying "bailout" meaning bailing out the Reconciliation process.
Notice the documentation on Bailing out a state update:
Note that React may still need to render that specific component again before bailing out.
Here is another example of such case demonstrating the idea:
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [state, setState] = React.useState(0);
useEffect(() => {
console.log("B");
}, [state]);
console.log("A");
return (
<>
<h1>{state}</h1>
<button onClick={() => setState(42)}>Click</button>
</>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
You notice the next logs and their explanations:
A // First render
B // Mount
A // State change from 0 -> 42, triggers render
B // useEffect dep array change, triggers callback
A // **Our issue**, React needs another render
The value does change when you press the button. First, it changes to 1 then to 0 but this runs very fast that you don't get to see it.
to see this, you could add a setTimeout
const handleButtonClick = () => {
setCount(1); //this line has been added extra
setTimeout(() => {
setCount(0);
}, 500);
};

How to prevent unwanted re-renders to stop another API call in react js?

Here is a parent component (Cart) and a child component (CartItem).
in the parent, there is a button that counts on the state that changes by the checkAvailability function which passed to the child via props,
import {useState} from "react";
import CartItem from "./CartItem";
const Cart = ({cart}) => {
const [available, setAvailable] = useState(true);
const checkAvailability = (check) => {
setAvailable(check)
}
return (
<>
{cart.items.map((item) => (
item.is_valid &&
<CartItem
key={item.id}
checkAvailability={checkAvailability}
/>
))}
<button disabled={available} >Click Me!</button>
</>
)
}
export default Cart;
in the child component, an API call returns true or false called by useEffect.
import {useState, useEffect} from "react";
const CartItem = ({checkAvailability}) => {
const [newData, setNewData] = useState(null);
const handleCheck = async () => {
const data = await api.call();
setNewData(data)
if(newData.available === false) {
checkAvailability(false)
} else if(newData.available === true) {
checkAvailability(true)
}
};
useEffect(() => {
handleCheck();
}, []);
return (
<div> Item </div>
)
};
export default CartItem;
issue:
every time the components mount, the API call in the child returns a value, that value gets passed to the parent by the checkAvailability function as a prop, which changes the state in the parent, when the state changes a re-render happen which restarts the circle infinitely.
the main thing is the button gets disabled when the API call returns a {false} value. if this way won't do the job, is there another way of doing it?.
what is the solution?.
Even if you resolved the current issue of infinite re-renders, you'll still have multiple API requests if there are multiple <CartItem/> components.
A better approach will be to move the API call to the parent. This will ensure it's called once regardless of the number of cart items it has. If the cart items need to know the value of available, then pass it to them.
const CartItem = ({ available }) => {
return <div> Item </div>
}

React.js infinite re-render

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.

Why does my onClick handler not log out the latest version of state and how can I troubleshoot this?

Sandbox
import React, { useEffect, useState } from "react";
export default function App() {
let [button, setButton] = useState(null);
let [num, setNum] = useState(5);
function revealState() {
console.log(num);
}
function changeState() {
setNum(Math.random());
}
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
setButton(el);
}, []);
return (
<>
{button}
<button onClick={changeState}>Change state</button>
</>
);
}
Clicking on the 'Log state button' successfully logs num state. Clicking on the 'Change state button' successfully changes num state. Re-clicking the 'Log state button' doesn't log the updated value of state - it logs the old one.
Why is this? My guess is that, since useEffect runs only the once, it references only the first revealState function which references only the first num variable. Because it's not in the component's return statement it doesn't get 'refreshed'.
Whatever the cause of the problem, what're some work-arounds? Some of the requirements are:
the tag can't be rendered directly in the return statement.
we have to have the useEffect that's there and it needs to have a dep array of some sort (its undesirable for it to fire every-time the function component is re-executed).
In the real project, some important changes to the tags useEffect's callback renders might get made - therefore it's impractical to re-run the useEffect by putting something like num in its dep array.
IMO, the neatest solution is to simply add the updated event listener, every time the page is rendered:
useEffect(() => {
el.onclick = onClickHandler
});
The event listener always has access to the latest state (and props). IMO, this solution is more scalable than previously-mentioned solutions - if my event listener has to track the latest versions of multiple state & props, this could get messy. With this, all I need to do is add extra listeners in into this one useEffect callback. Thoughts?
import React, { useEffect, useState, useCallback } from "react";
export default function App() {
let [button, setButton] = useState(null);
let [num, setNum] = useState(5);
const revealState = useCallback(() => {
console.log(num);
}, [num])
function changeState() {
setNum(Math.random());
}
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
setButton(el);
}, [revealState]);
return (
<>
{button}
<button onClick={changeState}>Change state</button>
</>
);
}
you can listen to the revealState in useEffect. which gets initialized only when num is changed achieved using useCallback. so whenever you click the button the num is changed which initializes the revealState function and not initialized on other rerenders
you have to add num as dependency to useEffect:
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
setButton(el);
}, [num]);
After more clarification on your problem it seems you need to watch over both the num and the HTML state. Combining both Alan and kishore's code together is the solution. Since the useEffect is only watching the num in Alan's answer so if any changes to the tag will not cause it to rerun. kishore also mentioned another fix which is to use the useCallback but what needs to be watch is the button and not the num. Like this:
const updateButton = useCallback(function (newButton) {
setButton(newButton);
}, [button])
useEffect(() => {
const el = (
<button id="logStateButton" onClick={revealState}>
Log state
</button>
);
updateButton(el)
}, [num]);
This will tell useEffect to watch num and will return a new button only when button state is changed.

The component does not work asynchronously

I have a component that makes a request and displays a list of jobs.
import React, { useState, useEffect, Fragment } from 'react';
import { Jobs } from '../components/Jobs.component';
export const Fixed = () => {
const [jobs, setJobs] = useState([]);
useEffect(() => {
getItems();
}, []);
async function getItems() {
const url = 'http://localhost:8081/api/fixed/list';
const res = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
});
const data = await res.json();
console.log(data);
setJobs(data.jobsList);
console.log(jobs);
}
return (
<Fragment>
{jobs.map(job => (
<div>
<Jobs job={job} />
</div>
))}
</Fragment>
);
};
My problem is that the first console outputs an array of jobs, but the second console displays an empty array. And an empty value is passed to the job component, which causes an error.
He does not have time to write the work in a state? Where am I wrong?
Method setJobs needs some time to change state so console.log runs faster than value changes.
You should render list if the array length is bigger than 0.
{jobs.length && jobs.map(job => <Jobs job={job} />)}
State updates are run asynchroniously
The reason your console.log shows an empty array is because setJobs runs asynchroniously and will update jobs value on next render. Looking at react setState documentation (same as useState react hooks) :
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state.
And so
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
const ... jobs ... is a constant - it will be a different constant in 2 different renders, but it will not change value during a single render
The jobs inside getItems is a closure and will reference to the value from the first render, while setJobs will only change the value in second render.
It's similar to the following:
const rememberJobs = (jobs) => () => console.log(jobs)
const first = rememberJobs([])
const second = rememberJobs([1, 2, 3])
first()
second()

Categories

Resources