I want to update only a single element when using setState in a function
import { useState } from "react";
export default function App(){
const [state, setState] = useState("foo");
return(
<Component1/>
<Component2/>
<Component3/>
);
}
I need some way of updating one some of those elements, but not all.
In functional components, you can wrap your component with React.memo. With this way, React will memorize the component structure and on next render, if the props still the same, React does not render again and use the memorized one. For more information, https://reactjs.org/docs/react-api.html#reactmemo
Basically wrap with React.memo. Below code, when state1 change, Component2's render count won't increase because its props stays same. But when state2 change, both of them will render.
export const Component2 = React.memo(({ state2 }) => {
const renderCount = useRef(0);
renderCount.current = renderCount.current + 1;
return (
<div style={{ margin: 10 }}>
Component2(with React.memo): <b>Rendered:</b> {renderCount.current} times,{" "}
<b>State2:</b> {state2}
</div>
);
});
export default function App() {
const [state1, setState1] = useState(1);
const [state2, setState2] = useState(1);
return (
<div className="App">
<div onClick={() => setState1((state) => state + 1)}>
Click to change state1
</div>
<div onClick={() => setState2((state) => state + 1)}>
Click to change state2
</div>
<Component1 state1={state1} />
<Component2 state2={state2} />
</div>
);
}
I created a sandbox for you to play. Click the buttons and see React.memo in action. https://codesandbox.io/s/xenodochial-sound-gug2x?file=/src/App.js:872-1753
Also, with Class Components, you can use PureComponents for the same purpose. https://reactjs.org/docs/react-api.html#reactpurecomponent
Related
I am learning React.
There are several componentized buttons.
When I click on one button, the other button components, including the parent component, are also re-rendered.
import { useState } from 'react'
const Button = ({ setState, text }) => {
console.log(`${text} rendering`)
return (<button
onClick={e => setState(prevState => !prevState)}
>
{text}
</button>)
}
const Page = () => {
console.log('Page rendering')
const [aaa, setAaa] = useState(false)
const [bbb, setBbb] = useState(false)
return (<>
<div>
<Button
setState={setAaa}
text="A button"
/>
<pre>{JSON.stringify(aaa, null, 2)}</pre>
</div>
<div>
<Button
setState={setBbb}
text="B button"
/>
<pre>{JSON.stringify(bbb, null, 2)}</pre>
</div>
</>)
}
export default Page
If anyone has more information, we would appreciate it if you could enlighten us.
Thank you in advance.
Anytime you set state in a component, the component and all of its children will re-render. You could memoize the children by wrapping them in React.memo so that if their props don't change they don't re-render. However, that will increase the memory footprint of your app, so often it only makes sense to memoize components that are expensive/slow to re-render, and your Button component is fairly simple to render.
I have a react page that has several components on it with a state that shows a modal. I dont want all the components in the app to re render when the modal shows.
const CustomersPage = () => {
const [showModal, setShowModal] = useState(false);
const dataSource = [...omitted data]
return (
<>
<Modal
visible={showModal} />
<div>
<div>
<div>
<Button type="primary" onClick={() => setShowModal(true)}>
Create
</Button>
</div>
<CustomForm />
</div>
<CustomList dataSource={dataSource} />
</div>
</>
);
};
When the showModal value changes, the components CustomForm component and the CustomList component re renders but I dont want them to re render every time the modal shows because the list can have over 100 components. How can I do this?
Edit.
const CustomList = ({ dataSource }) => {
return (
<div>
{dataSource?.map(i => (
<CustomCard
id={i.id}
...other props
/>
))}
</div>
);
};
const CustomCard = ({
... props
}) => {
return (
<>
<Card
...omitted properties
</Card>
</>
);
};
You can wrap the List and Form components in a React.memo component.
This way they will only re-render when their props change their identity.
See:
https://scotch.io/tutorials/react-166-reactmemo-for-functional-components-rendering-control
You can avoid unnecessary re-rendering with memo and hooks like useMemo and useCallback if you are using FC. Or if your are in CC the your create your component pure component that prevent unnecessary render.
Make your function component memo by wrapping component with Reaact.memo, then this will help to check and render component if there is any changes down there in your this child component. Despite all hooks like useCallback and useMemo are also helpfull for optimization.
There are tons of the articles are there about the use cases of these hooks, go and have look at them. They are really helpful.
Thanks
I need to change the state in one child component when a button is clicked in another child component. Both the childs have the same parent component.
import React from "react":
import A from "...";
import B from "...";
class App extends React.Component {
render() {
<div>
<A />
<B />
</div>
}
}
In this example, when a button in component A is pressed, the state in component B needs to be changed.
This application sounds like the perfect use case for "Lifting State Up", i.e. holding the main state in the parent component. Then you basically just pass down handlers (to change the parent state) to component A (this becomes the button's onClick handler), then pass down the state you want to show to component B.
When you click the button, the setState is called in the parent component, which automatically re-renders all children whose props change (including component B).
Here's more detailed info: https://reactjs.org/docs/lifting-state-up.html
EDIT: The reply below reminded me that I should probably add some code to illustrate - but I've made a few changes that simplify things.
import React, {useState} from "react":
import A from "...";
import B from "...";
const App = () => {
const [show, setShow] = useState(false);
function handleToggle() {
// Decouple the implementation of the parent state change from the child
// Pass a function to change the state (async/batching reasons)
setShow(show => !show);
}
return (
<div>
<A show={show} onToggle={handleToggle} />
<B show={show} onToggle={handleToggle} />
</div>
)
}
const A = ({show, onToggle}) => (
<div>
<p>show: {show}</p>
<button onClick={onToggle}>
toggle
</button>
</div>
)
const B = ({show, onToggle}) => (
<div>
<p>show: {show}</p>
<button onClick={onToggle}>
toggle
</button>
</div>
)
So basically we don't care how the state in the parent is changed. We just know that when the button in the child component is clicked, we want to trigger that change. So all we really have to do is call the function passed down via props - we don't have to pass any params.
The parent will then handle any clicks inside the handleToggle function, and you can change this implementation in the future without the child knowing anything. Perhaps you want to change to use mobx to handle state, or run some other code before finally changing the state. Since both are decoupled, you're all good! I've also adjusted setShow to use a function (benefits described here: https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous).
A supplement to the answer above:
import React, {useState} from "react":
import A from "...";
import B from "...";
const App = () => {
const [show, setShow] = useState(false)
return (
<div>
<A show={show} setShow={setShow} />
<B show={show} setShow={setShow} />
</div>
)
}
const A = ({show, setShow}) => (
<div>
<p>show: {show}</p>
<button onClick={() => setShow(!show)}>
toggle
</button>
</div>
)
const B = ({show, setShow}) => (
<div>
<p>show: {show}</p>
<button onClick={() => setShow(!show)}>
toggle
</button>
</div>
)
I'm new to React and I'd like some help please. I'm having a button and a component inside my app.js which is the main file
import React from 'react'
const App = () => {
const {data, loading, error} = useQuery(GET_DATA, {
variables: {...}
})
console.log(data)
state = {
clickSort: false
}
let clickSort = () => {
this.setState({
clickSort: true
})
}
return (
<div className="myApp">
<button onClick="{this.clickSort}">Click Me</button>
<div className="myClass">
<FooComponent fooData={data} clickSort={this.state.clickSort} />
</div>
</div>
)
}
What I want to do is when I click the button to sort the array of data I'm rendering in my component in a desc order. I was thinking of passing another parameter like a flag in the component, but I'm not sure how can I do this
If both of your components (<Button /> and <List />) are wrapped within common parent (<Parent />) you may employ the concept, known as lifting state up
Essentially, it is binding event handler within one of the child component's props (onSort() of <Button />) to the callback within parent (handleSort() of <Parent />), as well as binding dependent child prop (isSorted of <List />) to the state variable of common parent (sorted of <Parent />).
With that, you simply keep track of sorted flag within parent state (using useState() hook) and once handleSort() is triggered, it modifies that flag and consequent re-render of dependent components (<List />) takes place:
const { render } = ReactDOM,
{ useState } = React
const sampleData = ['itemC', 'itemA', 'itemD', 'itemB']
const Button = ({onSort}) => <button onClick={onSort}>Sort it</button>
const List = ({listData, isSorted}) => {
const listToRender = isSorted ? listData.sort((a,b) => b > a ? 1 : -1) : listData
return (
<ul>
{listToRender.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
}
const Parent = () => {
const [sorted, setSorted] = useState(false),
handleSort = () => setSorted(true)
return (
<div>
<Button onSort={handleSort} />
<List listData={sampleData} isSorted={sorted} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
It looks from your question that you want to let a child component (FooComponent) know that the button has been clicked so that it can process (sort) the data it has received.
There are a lot of approaches to this. For instance, you could pass a boolean property to the child component that is a flag for it to do the sorting. So the parent component tracks when the button has been clicked, and the child component just observes this (perhaps in componentDidUpdate).
This would change slightly if you are using functional components, rather than class based components, but it gives you an idea.
state = {
requestSort: false
}
requestSort = () => {
this.setState({
requestSort: true
}
}
render() {
return (
<>
<button id="myBtn" onClick={this.requestSort}>Click Me</button>
<div className="myClass">
<FooComponent requestSort={this.state.requestSort} fooData={data} />
</div>
</>
)
}
Alternatively, since the data is being passed to the child component as well, you could have the parent sort it when it is clicked. It depends on if you are doing anything else with the data (i.e. is only FooComponent the one that should have the sorted copy of the data or not).
Pass the data from the state into FooComponent and write a function that sorts the data in that state. The data will instantly be updated in the child component once the state has updated in the parent component because the child component will rerender once it's noticed that the data in the parent component doesn't match the data that it previously received. Below is an example.
import React from 'react'
const FooComponent = ({ fooData }) => (
<div>
{fooData}
</div>
)
export default class Home extends React.Component {
constructor(props){
super(props);
this.state = {
data: [1, 4, 2, 3]
}
}
sortData() {
const { data } = this.state;
this.setState({
data: data.sort((a, b) => b - a),
})
}
render(){
const { data } = this.state;
return (
<div>
<button id="myBtn" onClick={() => this.sortData()}>Click Me</button>
<div className="myClass">
<FooComponent fooData={data} />
</div>
</div>
)
}
}
The code below is my slimmed down app. I have an exercise state in the parent which has a value at exercise[index].remainingSets. I update this value in the parent when a child element is clicked by having an onClick which calls completedSet function. I print this value on the screen in the child, but when this value changes the printed value in the child does not change. I see that the props in the child has changed with the react extension but no re-render/update.
This is what the parent looks like.
const [exercise, setExercise] = useState([]);
const completedSet = (e, index) => {
let completedSetExercise = exercise;
completedSetExercise[index].remainingSets -= 1;
setExercise(completedSetExercise);
e.target.style.backgroundColor = '#CD5C5C';
}
return(
<>
<ExerciseForm onSubmit={onSubmit} />
{Object.keys(exercise).map(key => <Exercise
key={key}
index={key}
exercise={exercise[key]}
/>)}
</>);
}
export default App;```
```import React, {useState, useEffect} from 'react';
const Exercise = (props) => {
const sets =[]
for (var i = 0; i < props.exercise.Sets; i++) {
sets.push(
<li
className="exercise_reps"
onClick={(e) => props.completedSet(e, props.index)}
key={i}>
<span> {props.exercise.Reps} </span>
</li>
)
}
return (
<div>
<div className="exercise_title">
<h2>{props.exercise.Exercise}</h2>
<button onClick={() => props.removeExercise(props.index)}>Remove Exercise</button>
</div>
{props.exercise.remainingSets}
</div>
)
}
export default Exercise;
Issue was I was making and passing a shallow copy to child so it did not see it as different. I used the spread operator and assigned state to a new variable before assigning it. let completedSetExercise = [...exercise]
`