I m having one sub child component which is inside a loop of parent component. when one of the sub child components is updating the state of parent component, it is re-rendering the all children since it is loop. How can i avoid the re-render for each iteration. It should update that particular sub child.
import React, { useState } from "react";
function Parent() {
const [selectedChild, setSelectedChild] = useState([]);
const onChangeHandle = (event, id) => {
const checked = event.target.checked;
let updatedArray = [...selectedChild];
if (checked) {
if (!selectedChild.includes(id)) {
updatedArray.push(id);
}
} else {
var index = updatedArray.indexOf(id);
if (index !== -1) {
updatedArray.splice(index, 1);
}
}
setSelectedChild(updatedArray);
};
return (
<div>
<table>
<tbody>
{[1, 2, 3].map((value, index) => {
return (
<Child
key={index}
index={index}
value={value}
handle={onChangeHandle}
isSelected={selectedChild.includes(index)}
/>
);
})}
</tbody>
</table>
<div>{selectedChild}</div>
</div>
);
}
function Child({ index, value, handle, isSelected }) {
console.log("rendering child");
return (
<tr>
<td>
<SubChild
isChecked={isSelected}
onChangeHandle={handle}
index={index}
/>
</td>
<td>
hello {index} {value}
</td>
</tr>
);
}
function SubChild({ isChecked, onChangeHandle, index }) {
console.log("rendering subchild");
return (
<input
type="checkbox"
checked={isChecked}
onChange={(event) => onChangeHandle(event, index)}
/>
);
}
export default function App() {
return (
<div className="App">
<Parent />
</div>
);
}
Current behaviour: In above code, When i m clicking on the checkbox(which is Sub child) in one of the children component, it is updating the parent component state(selectedChild). So the loop is executing and all children(all table rows) are re rendering.
Expected behaviour: Only that particular sub child have to go for re-render (even it should not re-render child)
Demo: https://codesandbox.io/s/reactqa2-0c0md?file=/src/App.js
Little related question: How to avoid rerender all child components which in loop when parent component state update
You should use memoization (useCallback/React.memo) and rewrite handle logic with functional updates.
Also, you avoid Child to render, since you have a new value after rendering.
// Make a stable callback
const onChangeHandle = useCallback((event, id) => {
setSelectedChild((updatedArray) => {
if (event.target.checked) {
if (!updatedArray.includes(id)) {
return [...updatedArray, id];
}
} else {
return updatedArray.filter((currId) => currId !== id);
}
return updatedArray;
});
}, []);
// Memoize the component
const MemoChild = React.memo(Child);
const MemoSubChild = React.memo(SubChild);
Related
So I have a parent Component and a child component. And I use the child component twice in my parent component. I pass them two different state values as props and two different events as props. I have tried to memoize both the callbacks , but both the child are re-rendered even if one child callback is triggred. Why is useCallback not working.
Parent Component:
import { useState, useCallback, useEffect, useMemo } from 'react';
import './App.css'
import List from "./components/list";
import LocalList from "./components/localList";
function App() {
const itemsToBuy = [
'Baby Shoes',
'Grinder',
'Car'
]
const [buyList, updateBuyList] = useState(itemsToBuy);
const [sellList, updateSellList] = useState([
'Bed',
'Sofa'
]);
/** code to check the re-rendering of the componnet */
useEffect(() => {
console.log(`parent is being rendered`)
})
/**trying to update the state from internal method to be passed as props */
const updateBuyClick = useCallback(val => {
updateBuyList(prev => [...prev, val])
}, [buyList])
const updateSellClick = useCallback(val => {
console.log('memo of sell is called')
updateSellList(prev => [...prev, val])
}, [sellList])
return (
<>
<div className='container'>
<div>
<h1>Items To Buy</h1>
<List itemsArray={buyList} onUpdateClick={updateBuyClick} buttonText='Add Items to Buy' idx={'list One'}></List>
</div>
<div>
<h1>Items to Sell</h1>
<List itemsArray={sellList} onUpdateClick={updateSellClick} buttonText='Add Items to Sell' idx={'list Two '}></List>
</div>
{/* <div>
<h1>List that is not re-rendere</h1>
<LocalList buttonText='Add Items to LocalList' idx={'list3 '}></LocalList>
</div> */}
</div>
</>
);
}
export default App;
Child Component:
import { useState , useEffect} from "react";
import './list.css'
function List({ itemsArray = [], buttonText, onUpdateClick, idx }) {
let currentSell = '';
useEffect(() => {
console.log(`${idx} is being rendered`)
})
const updateCurrentSell = (val) => {
currentSell = val;
}
return (
<>
<ul>
{itemsArray.map((value, index) => {
return <li key={index}>{value}</li>
})}
</ul>
<div>
<input type='text' onChange={(e) => { updateCurrentSell(e.target.value) }}></input>
<button onClick={() => { onUpdateClick(currentSell) }}>{buttonText}</button>
</div>
</>
)
}
export default List;
There are two reasons that's not working:
You're telling useCallback to throw away the stored copy of your function when the buyList or sellList changes by including those in your dependencies array. You don't need those dependencies, because you're (correctly) using the callback version of the state setters. So you aren't using buyList or sellList in the callbacks. Just remove them from the arrays.
const updateBuyClick = useCallback(val => {
updateBuyList(prev => [...prev, val])
}, [])
// ^^−−− empty
const updateSellClick = useCallback(val => {
console.log('memo of sell is called')
updateSellList(prev => [...prev, val])
}, [])
// ^^−−− empty
useCallback only does half the necessary work: making sure the functions don't change unnecessarily. But your List component has to do the other half of the work: not re-rendering if its props don't change. With a function component, you do that with React.memo:
const List = React.memo(function List({ itemsArray = [], buttonText, onUpdateClick, idx }) {
// ...
});
React.memo memoizes the component and reuses its last rendering if its props don't change. (You can customize that by providing a callback as its second argument, see the documentation for details.)
Between those two changes, you'll see only the appropriate instances of List re-render when things change.
Live Example:
const { useState, useCallback, useEffect, useMemo } = React;
function App() {
const itemsToBuy = [
"Baby Shoes",
"Grinder",
"Car"
];
const [buyList, updateBuyList] = useState(itemsToBuy);
const [sellList, updateSellList] = useState([
"Bed",
"Sofa"
]);
// *** Note: No need for this to be in `useEffect`
console.log(`parent is being rendered`)
const updateBuyClick = useCallback(val => {
updateBuyList(prev => [...prev, val]);
}, []);
const updateSellClick = useCallback(val => {
updateSellList(prev => [...prev, val])
}, []);
return (
<div className="container">
<div>
<h1>Items To Buy</h1>
<List itemsArray={buyList} onUpdateClick={updateBuyClick} buttonText="Add Items to Buy" idx={"list One"}></List>
</div>
<div>
<h1>Items to Sell</h1>
<List itemsArray={sellList} onUpdateClick={updateSellClick} buttonText="Add Items to Sell" idx={"list Two "}></List>
</div>
</div>
);
}
const List = React.memo(function List({ itemsArray = [], buttonText, onUpdateClick, idx }) {
// *** `currentSell` stuff should be in state, not a local variable
const [currentSell, setCurrentSell] = useState("");
console.log(`${idx} is being rendered`);
return ( // <>...</> is fine, I had to change it because the
// version of Babel Stack Snippets use is out of date
<React.Fragment>
<ul>
{itemsArray.map((value, index) => {
return <li key={index}>{value}</li>
})}
</ul>
<div>
<input type="text" onChange={(e) => { setCurrentSell(e.target.value); }}></input>
<button onClick={() => { onUpdateClick(currentSell); }}>{buttonText}</button>
</div>
</React.Fragment>
);
});
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Is it possible to pass the data from the child component to the parent component using props?
-Parent component
--- ItemList component.
--- DisplatSelect component from the itemList component
I have a list of item in the child component which came from to the parent component, then I want to send the index of the selected data to the other child component located in the parent component.
Can't example well, kindly see the attached screenshot for other references.
Thanks a lot!
enter image description here
You can keep the data in the Parent component and use a function to pass the props from the child to the Parent. This concept is called Lifting State Up where you define the state at the highest common ancestor so all the child components are using the same data which in this case is the selecetd item
function Parent() {
const [selectedItem, setSelectedItem] = useState(null);
const data = []; // Your Data
return (
<>
<h1>Your selected Item = {selectedItem}</h1>
{data.map((item) => {
<Child item={item} setSelectedItem={setSelectedItem} />;
})}
</>
);
}
function Child({ item, setSelectedItem }) {
return <Button onClick={() => setSelectedItem(item.id)}> {item} </Button>;
}
The simplest way, I think, is for the child component where the selection is made to accept a function properly, something like onSelectionChanged. If you had a button for each item passed to the child you could do something like:
Child Component A
const ChildA = ({ items, onSelectionChanged }) => {
return (
<div>
{items.map((item, index) => (
<button onClick={() => onSelectionChanged(index)}>Item</button>
))}
</div>
)
}
Child Component B
const ChildB = ({ selectedItem }) => {
return (
<div>
Selected {selectedItem}
</div>
)
}
Parent Component
const Parent = () => {
const [selection, sets election] = useState({});
const onSelectionChanged = index => {
console.log(`ChildA selection changed: ${index}`);
}
return (
<div>
<ChildA items={items} onSelectionChanged={onSelectionChanged} />
<ChildB selectedItem={selection} />
</div>
)
}
So when your child component handles a change in the selection, it invokes the function passed as a prop onSelectionChanged. You can pass whatever data you want from ChildA to that function.
Note that the parent Component keeps the selected value (from ChildA) in local state, then passes that value to ChildB via a prop.
You can have a state variable in the parent component and pass it to child components to share data between them. I'll post a sample code block on how you can do this for your case.
export default function ParentComponent (props) {
const data = ['image_1_url', 'image_2_url', ...] // Data for the images
const [selectedIndex, setSelectedIndex] = useState(-1); // Selected index (-1 represents no selection)
return (
<ImageList data={data} selectImage={setSelectedIndex} />
{(selectedIndex !== -1) ? (<SelectedImage data={data[selectedIndex]} />) : (<No ImageSelected/>)}
);
}
And the image list component can then use the selectImage prop to select the image
export default function ImageList (props) {
return (
<div>
props.data.map((imageUrl, index) => (
<div onClick={() => {props.setSelected(index)}}>
<img src={imageUrl}/>
</div>
))
</div>
);
}
Yes it's possible. We have one parent state value and update every on click child component to the component.
import React, { useState } from "react";
const Child1 = (props) => {
return (
props.items.map( (item, index) => (
<button key={index.toString()} onClick={() => { props.updateIndex(item.id) }}>
{item.name}
</button>
) )
)
}
const Child2 = (props) => {
return (
<h1>Item selected: {props.selectItem}</h1>
)
}
const ParentComponent = () => {
const listItems = [
{
id:1,
name: "sample name 1"
},
{
id:2,
name: "sample name 2"
}
]
const [selectItem, setSelectItem] = useState('None');
return (
<>
<Child1 items={listItems} updateIndex={setSelectItem}/>
<Child2 selectItem={selectItem}/>
</>
)
}
export default function App() {
return (
<div className="App">
<ParentComponent/>
</div>
);
}
I have a Form and Input components, which are rendered as below.
<Form>
<Field />
<Field />
<Field />
</Form>
Form component will act as wrapper component here and Field component ref are not being set here. I want iterate through props.children in Form Component and want to assign a ref attribute to each children. Is there any possibility to achieve this?
You need Form to inject your refs with React.Children and React.cloneElement APIs:
const FunctionComponentForward = React.forwardRef((props, ref) => (
<div ref={ref}>Function Component Forward</div>
));
const Form = ({ children }) => {
const childrenRef = useRef([]);
useEffect(() => {
console.log("Form Children", childrenRef.current);
}, []);
return (
<>
{React.Children.map(children, (child, index) =>
React.cloneElement(child, {
ref: (ref) => (childrenRef.current[index] = ref)
})
)}
</>
);
};
const App = () => {
return (
<Form>
<div>Hello</div>
<FunctionComponentForward />
</Form>
);
};
You can map children create new instance of component based on it using one of two ways showed in React Docs.
With React.Children.map and React.cloneElement (this way, key and ref from original element are preserved)
Or only with React.Children.map (Only ref from original component is preserved)
function useRefs() {
const refs = useRef({});
const register = useCallback((refName) => ref => {
refs.current[refName] = ref;
}, []);
return [refs, register];
}
function WithoutCloneComponent({children, ...props}) {
const [refs, register] = useRefs();
return (
<Parent>
{React.Children.map((Child, index) => (
<Child.type
{...Child.props}
ref={register(`${field-${index}}`)}
/>
)}
</Parent>
)
}
function WithCloneComponent({children, ...props}) {
const [refs, register] = useRefs();
return (
<Parent>
{
React.Children.map((child, index) => React.cloneElement(
child,
{ ...child.props, ref: register(`field-${index}`) }
)
}
</Parent>
)
}
I have a page called: List.js which renders the component: Box for every item on the List page. My List.js also consists of a component called: MainSpace. Now every Box component will consists of a button with an onClick function. Depending on which Box component you click, I want to fill the MainSpace with data coming from that specific Box component, but I can't seem to figure out how to do this.
render() {
for (let index in data) {
list.push(
<Box data={data[index]}/>
);
}
return (
<div>
{list}
<MainSpace>
</div>
);
}
Now the Box component looks something like this:
class Box extends Component {
fillMainSpace(){
//Send this.prop.data to the list.js file and fill the MainSpace component from there I think?
}
render(){
return (
<div>
<span>{this.props.data.text}</span>
<button onClick={this.fillMainSpace}></button>
</div>
);
}
}
Mainspace doesn't have much. I just want to load for example the this.props.data.image from the clicked button from the Box component. Even console.log(this.props.data); would be sufficient. Calling the <MainSpace> component in every <Box> component is not an option, since that would render a lot of unnecessary extra components.
So my question being:
How would I be able to access the this.props.data of the clicked button of a <Box> component and use it in my <MainSpace> component?
EDIT:
Adding the MainSpace Component:
class MainSpace extends Component {
render(){
return (
<div>
{this.props.data.image}
</div>
);
}
}
You can save selected item in state like selectedItem and then pass that data to MainSpace
const List = props => {
const [selectedItem, setSelectedItem] = useState(props.data[0]);
const onSelect = item => {
setSelectedItem(item);
};
return (
<div calssName="list">
{props.data.map(item => <Box data={item} onSelect={onSelect} />)}
<MainSpace data={selectedItem} />
}
}
const Box = props => {
const { data, onSelect } = props;
return (
<div>
<span>{data.someKey}</span>
<button onClick={() => onSelect(data)} >Select</button>
</div>
);
};
Using class components
class Box extends Component {
fillMainSpace = () => {
const { onSelect, data } = this.props;
onSelect(data);
}
render(){
const { data } = this.props;
return (
<div>
<span>{data.text}</span>
<button onClick={this.fillMainSpace} }></button>
</div>
);
}
}
class List extends React.Component {
state = { selectedItem: this.props.data[0] };
onSelect = item => {
this.setState({ selectedItem: item });
}
render() {
const { data } = this.props;
const { selectedItem } = this.state;
const list = data.map(item => <Box data={item} onSelect={this.onSelect} />);
return (
<div>
{list}
<MainSpace data={selectedItem}>
</div>
);
}
}
I have the React app below (jsfiddle):
const ListItem = (props) => {
return (
<div className={props.active ? "active" : ""}>Item {props.index}</div>
)
}
const initialItems = ["item1", "item2", "item3", "item4", "item5"]
const App = (props) => {
const [activeIndex, setActiveIndex] = React.useState(0);
const goUp = () => {
if(activeIndex <= 0) return;
setActiveIndex(activeIndex - 1);
}
const goDown = () => {
if(activeIndex >= initialItems.length - 1) return;
setActiveIndex(activeIndex + 1);
}
return (
<div>
<p>
<button onClick={goUp}>Up</button>
<button onClick={goDown}>Down</button>
</p>
<div>
{initialItems.map((item, index) => (
<ListItem active={index === activeIndex} index={index} key={index} />
))}
</div>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
Using buttons you can highlight the current list element. The issue with the current approach is that on every active index change it re-renders the full list. In my case, the list might be very big (hundreds of items) with a more complicated layout, which introduces performance problems.
How might this code be modified so it updates only specific list item components and doesn't trigger re-render of all others? I'm looking for a solution without third-party libraries and without direct DOM manipulations.
You can wrap ListItem with React.memo() as here.
This is your ListItem component,
const ListItem = (props) => {
return (
<div className={props.active ? "active" : ""}>Item {props.index}</div>
)
};
By using React.Memo(),
const ListItem = React.memo((props) => {
return (
<div className={props.active ? "active" : ""}>Item {props.index}</div>
)
});
In this case ListItem is only rendered when props gets changed.
See for updated JsFiddle and check with console.log() s.