Reputation: 83
I am currently building a React App with Hooks. I have got a component (View) that loads data from the somewhere and generates components (Clicker, ClickerDisplay) dynamically that on the one hand show the data and on the other hand alternate the data.
The app basically works, but my concern is the performace of it. My state updates every 100ms and it would be a big overhead if everything would be rerendered on change.
Is it possible to only rerender the components that should change?
Another thing that seems pretty ugly to me is the way i update my state. setState([...state]);
Is there a better aproach to do it?
Sidenotes:
In my example code id am currently only passing the item properties into the child components, but i am planing on passing the whole item into them.
I also want to keep the main states in the parent component because the state of the app should be built up and saved to one big JSON Object.
Data
//Some Random Data
const things = [
{
"id": 0,
"name": "thing 1",
},
{
"id": 1,
"name": "thing 2",
},
{
"id": 2,
"name": "thing 3",
}
];
View.js
export default function View(props) {
const tmp = things.map(item => {
return { ...item, amount: 0 };
});
const [state, setState] = useState(tmp);
//alternates the amount and updates the state
function updateAmount(item, newAmount) {
item.amount = newAmount;
setState([...state]);
}
function createClicker(item) {
const { name, amount } = item;
return (<Clicker
name={name}
amount={amount}
clicked={() => updateAmount(item, amount + 1)} />
);
}
function createClickerDisplay(item) {
const { name, amount } = item;
return (<ClickerDisplay
name={name}
amount={amount} />
);
}
return (
<ul>
{state.map(item =>
<li>
{createClicker(item)} = {createClickerDisplay(item)}
</li>
)}
</ul>
);
}
Clicker.js
//component to alternate data
function Clicker(props) {
const { clicked, name, amount } = props;
return <button onClick={() => clicked(name)}>{name}: {amount}</button>;
}
ClickerDisplay.js
//component to display data
function ClickerDisplay(props) {
const { name, amount } = props;
return <span>{name}: {amount}</span>;
}
Upvotes: 1
Views: 967
Reputation: 5303
React components, unless configured to re-render conditionally based on specific changes to the state and props, will re-render on every life cycle. In your case, as you are using functional components, you can leverage React.memo()
to memoize these components, meaning that will only render if the values inside the passed props change. Be careful with this though when you have complex structures bacuase the default comparator only does shallow comparison.
The snippet below illustrates the use of a memoized component, as well as address the state mutation you are performing as part of the click event handler.
const things = [
{ "id": 0, "name": "thing 1" },
{ "id": 1, "name": "thing 2" },
{ "id": 2, "name": "thing 3"}
];
function Clicker({ clicked, name, amount }) {
return <button onClick={() => clicked(name)}>{name}: {amount}</button>;
}
// Memoized component that only updates when its simple props change
const ClickerDisplayMemoized = React.memo(function ClickerDisplay(props) {
const { name, amount, index } = props;
console.log(`Updating ClickerDisplay ${index + 1}`);
return <span>{name}: {amount}</span>;
});
function View() {
const thingsWithAmounts = things.map((item) => ({ ...item, amount: 0 }));
const [state, setState] = React.useState(thingsWithAmounts);
// Updated callback that avoids state mutation
function updateAmount(index, newAmount) {
setState(state.map((item, i) => ({
...item,
amount: (i === index ? newAmount : item.amount)
})));
}
const createClicker = ({ name, amount }, index) => (
<Clicker name={name} amount={amount}
clicked={() => updateAmount(index, amount + 1)} />
);
const createClickerDisplay = ({ name, amount }, index) => (
<ClickerDisplayMemoized name={name} amount={amount} index={ index } />
);
return (
<ul>
{state.map((item, i) => (
// this component needs a key as it's part of a map()
<li key={ i }>
{createClicker(item, i)} = {createClickerDisplay(item, i)}
</li>
))}
</ul>
);
}
ReactDOM.render(<View />, document.getElementById('root'));
/* this is just so the console in this snippet doesn't cover the components */
.as-console-wrapper { max-height: 50% !important; }
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Upvotes: 1