I have an array with thousands of strings and is passed to a component:
Main component:
const array = ['name1', 'name2', 'name3'];
const [names, setNames] = useState(array);
const onClick = (index) => {
setNames(names.map((name, i) => {
if (i === index) {
return 'name changed';
}
};
};
return (
<ul>
{array.map((name, index) => (
<li key={index}>
<ShowName name={name} key={index} onClick={() => onClick(index)} />
</li>
)}
</ul>
);
ShowName component:
let a = 0;
export default function ShowName({ name, onClick }) {
a += 1;
console.log(a);
return (
<button type="button" onClick={onClick}>{name}</button>
);
}
There's also a button which changes a name randomly. But whenever the button is pressed, all the ShowName components are rerendering. I've been trying to use useCallback and useMemo, but the components are still rerendering x times (x is the length of the array).
const ShowNameHoc = ({ name }) => {
return <ShowName name={name} />
};
return (
<div>
{array.map((name, index) => <ShowNameHoc name={name} key={index} />)}
</div>
);
What should I do if I only want to rerender the component with a change?
You have a misunderstanding in the concepts here. The default is for React to call render on all children, regardless of whether the props changed or not.
After that happened, React will compare that new Virtual DOM to the current Virtual DOM and then only update those parts of the real DOM that changed.
That's why the code in a render method should be quick to execute.
This behavior can be changed by using features like useMemo, PureComponents or shouldComponentUpdate.
References:
https://reactjs.org/docs/rendering-elements.html (Bottom):
Even though we create an element describing the whole UI tree on every tick, only the text node whose contents have changed gets updated by React DOM.
https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation
Even though React only updates the changed DOM nodes, re-rendering still takes some time. In many cases it’s not a problem, but if the slowdown is noticeable, you can speed all of this up by overriding the lifecycle function shouldComponentUpdate, which is triggered before the re-rendering process starts.
...
In most cases, instead of writing shouldComponentUpdate() by hand, you can inherit from React.PureComponent. It is equivalent to implementing shouldComponentUpdate() with a shallow comparison of current and previous props and state.
Also, read this for some more background info: https://dev.to/teo_garcia/understanding-rendering-in-react-i5i
Some more detail:
Rendering in the broader sense in React means this (simplified):
Update existing component instances with the new props where feasible (this is where the key for lists is important) or create a new instance.
Calling render / the function representing the component if shouldComponentUpdate returns true
Syncing the changes to the real DOM
This gives you these optimization possibilities:
Ensure you are reusing instances instead of creating new ones, e.g. by using a proper key when rendering lists. Why? New instances always result in the old DOM node to be removed from the real DOM and a new one to be added. Even when unchanged. Reusing an instance will only update the real DOM if necessary. Please note: This has no effect on whether or not render is being called on your component.
Make sure your render method doesn't do heavy lifting or if it does, memoize those results
Use PureComponents or shouldComponentUpdate to prevent the call to render altogether in scenarios where props didn't change
Answering your specific question:
To actually prevent your ShowName component from being rendered - into the Virtual DOM - if their props changed, you need to perform the following changes:
Use React.memo on your ShowName component:
function ShowName({ name, onClick }) {
return (
<button type="button" onClick={onClick}>{name}</button>
);
}
export default memo(ShowName);
Make sure the props are actually unchanged by not passing a new callback to onClick on each render of the parent. onClick={() => onClick(index)} creates a new anonymous function every time the parent is being rendered.
It's a bit tricky to prevent that, because you want to pass the index to this onClick function. A simple solution is to create another component that is passed the onClick with the parameter and the index and then internally uses useCallback to construct a stable callback. This only makes sense though, when rendering your ShowName is an expensive operation.
That is happening because you are not using the key prop on <ShowName/> component.
https://reactjs.org/docs/lists-and-keys.html
it could look something like this
return (
<div>
{array.map(name => <ShowName key={name} name={name} />)}
</div>
);
Related
I have a react app that renders a list of cards in a container widget. The container widget has a useEffect where we subscribe to an observable and update the array that is then used to render the cards inside the component. Each time any of the cards change, the observable emits new values resulting in creating of the array all over again and thus all the cards are re-rendered. However this re-render causes a noticeable UI lag.
Here is the stripped down version of code from the container component.
const obsRef = useRef<Subscription>(null);
const [apiResArray, setApiResArray] = useState([]);
useEffect(() => {
if(obsRef.current) obsRef.current.unsubscribe();
const mySubscription = myObservable$(changingValue)
.subscribe((val) => {
setApiResArray(val.apiArray);
});
obsRef.current = mySubscription;
return () => {
obsRef.current && obsRef.current.unsubscribe();
};
}, [changingValue])
return (
<CardWrapper $showMenu={showMenu}>
{ apiResArray.map((res, resIndex) => {
return (
<Card
data={{
// a json object with props
}}
key={res?.hKey}
/>
);
})}
</CardWrapper>
);
The Card component here simply renders the content based on props passed to it. I know that since data is an Object, referential equality may fail and I have tried memoizing the component but even that does not help.
There is a lot more to these components but posting all the code won't make sense. I wish to understand what possibly might be causing the list re-render to be such a heavy operation that the whole UI gets stuck for a second or two. The array contains around only 100 objects or so. It happens whenever changingValue changes. I can share more information as required.
Any suggestions on improving the performance are highly appreciated.
React will mount and unmount the component in DEV mode to validate effect cleaner and any side effect.
I'm concerned about that subscribe and unsubscribe in your effect.
If you want changingValue to be defined why not just wrap it inside an if statement ?
if (changingValue) {
const mySubscription = myObservable$(changingValue)
.subscribe((val) => {
setApiResArray(val.apiArray);
});
obsRef.current = mySubscription;
}
Another observation is at render of items
<Card
data={
{
// a json object with props
}
}
key={res?.hKey} // Why is this a falsy value ?
/>
Falsy value can make key prop change those re-rendering the component, one with React internal key value and another with your implementation value
I have a function which basically generates dynamic dom as below
const arrMarkup = [];
const getMarkup = () => {
{
if(true){
arrMarkup.push(<Accordion expanded={expanded === cust.name} onChange={handleChange(cust.name)}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Typography className={salesEvent.name && classes[salesEvent.name]}></Typography>
</AccordionSummary>
</Accordion>
)
})
})
}
return <div>{arrMarkup}</div> ;
}
Now, i am trying to execute this function is on useEffect as below
useEffect(() => {
getMarkup();
}, [cust]);
and trying to add in return of JSX as
return (
<div>
{arrMarkup}
</div>
)
but can not see the markup added, however i can see its added in array arrMarkup. What's wrong?
React only re-renders your component when its state or props change. As far as one can tell from your question, arrMarkup isn't either a state member or a prop. (If it were, directly modifying it would be against the React rules, because you must not directly modify state or props.)
It's hard to tell you what to do without more information, but you probably want arrMarkup to be a state member, for instance (in a functional component using hooks):
const [arrMarkup, setArrMarkup] = useState([]);
and then to update it appropriately, for instance:
setArrMarkup(current => [...current, <NewStuff>goes here</NewStuff>]);
Note that I used the callback version of the state setter. That's important when you're updating state based on existing state, since state updates may be asynchronous and can be batched (so the state information you already have can be stale).
FWIW, a couple of other observations:
It's unusual to have the useEffect dependency be cust (a single object as far as I can tell) and have triggering the effect add an entry to an array that has previous entries for previous values of cust which (apparently) you're no longer storing in the component's state anywhere. That just feels very off, without more context.
You haven't shown the definition of handleChange, but onChange={handleChange(cust.name)} looks like it's probably incorrect. It calls handleChange, passing in cust.name, and the uses its return value os the change handler. Did you mean onChange={() => handleChange(cust.name)}, so that handleChange is called when the event occurs?
Today I spent quite a bit of time debugging an issue with state resetting itself in child components on every re-render. After eventually solving it I realized that I don't fully understand how React functional components and Array.map() work together, which is why I'm hoping that someone can shed some light on the issue I was having:
Let's say I have an ItemWrapper component that returns (among other things) an ItemListA component. ItemListA maps over an array and returns a list of Item components. Each Item component has its own state that changes on certain actions.
The way I did it at first:
const ItemWrapper = ({ items }) => {
const [someState, setSomeState] = useState(null);
const someFunction = value => setSomeState(value);
...
const ItemListA = () => items.map(item =>
<Item
key={item.id}
item={item}
callback={someFunction}
/>
)
...
return (
<div>
<ItemListA />
</div>
);
};
The problem: whenever the someFunction callback was invoked in one of the Item children, this caused ItemWrapper to re-render and reset the state of all of the other Item children.
Solved it by storing the item list in a local variable rather than a component:
const ItemWrapper = ({ items }) => {
const [someState, setSomeState] = useState(null);
const someFunction = value => setSomeState(value);
...
const itemListB = items.map(item =>
<Item
key={item.id}
item={item}
callback={someFunction}
/>
);
...
return (
<div>
{itemListB}
</div>
);
};
I don't feel like I fully understand what's going on here. My guess would be that storing the item list in a functional component somehow made it so that the ItemListA component and its children (with the exception of the one that triggered the callback - no idea why) were destroyed and then rebuilt whenever the ItemWrapper component was re-rendered, meaning that there was no trace left of their previous state. Is it because ItemListA is a function and every re-render creates a new reference?
Is it because ItemListA is a function and every re-render creates a new reference?
Yep, that's pretty much it.
When figuring out what changes to make to the DOM, react compares the virtual dom before with the virtual dom afterwards, and looks for changes. Doing an exhaustive comparison would be expensive, so they make some assumptions to speed things up. One of those assumptions is that if a component's type has changed, then that entire subtree is assumed to have changed. (For more info see react's article on reconciliation)
So in this case, react sees ItemListA from the first time, and ItemListA from the second time, and they are different component types. They look very similar to our eyes, but they're different references, which means they're different to react. So react has to unmount the old ones and mount the new ones.
With your second code, you're not creating a new type of component on every render, you're just creating an array with elements in it. The two arrays are different references, but that's ok since it's not a type of component.
I want to know if it's an anti-pattern or if it affects the component somehow to do something like this:
render() {
const MyFuncComponent = ({ prop1, prop2 }) => (
// code here
)
return (
<div>
<MyFuncComponent prop1={something} prop2={else} />
</div>
)
}
Yes, this is an anti-pattern for the same reason we shouldn't use a Higher-Order-Component inside of render.
Don’t Use HOCs Inside the render Method
React’s diffing algorithm (called reconciliation) uses component identity to determine whether it should update the existing subtree or throw it away and mount a new one. If the component returned from render is identical (===) to the component from the previous render, React recursively updates the subtree by diffing it with the new one. If they’re not equal, the previous subtree is unmounted completely.
Normally, you shouldn’t need to think about this. But it matters for HOCs because it means you can’t apply a HOC to a component within the render method of a component:
render() {
// A new version of EnhancedComponent is created on every render
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// That causes the entire subtree to unmount/remount each time!
return <EnhancedComponent />;
}
The problem here isn’t just about performance — remounting a component causes the state of that component and all of its children to be lost.
This means that the new component will appear in the React tree (which can be explored with the react-devtools) but it won't retain any state and the lifecycle methods like componentDidMount, componentWillUnmount, useEffect will always get called each render cycle.
Solutions
Since there are probably reasons for dynamically creating a component, here are some common patterns that avoid the pitfalls.
Define the new component outside
Either in its own file, or directly above the parent component's definition. Pass any variable as props instead of using the parent component's scope to access the values.
const MyFuncComponent = ({ prop1, prop2 }) => <>{/* code here */}</>;
const MyComponent = props => (
<div>
{props.list.map(({ something, thing }) => (
<MyFuncComponent prop1={something} prop2={thing} />
))}
</div>
);
Helper function
A regular function that returns JSX can be defined and used directly inside another component. It won't appear as a new component inside React's tree, only its result will appear, as if it was inlined.
That way, we can also use variables from the enclosing scope (like props.itemClass in the following example) in addition to any other parameters.
const MyComponent = props => {
// Looks like a component, but only serves as a function.
const renderItem = ({ prop1, prop2 }) => (
<li className={props.itemClass}> {/* <-- param from enclosing scope */}
{prop1} {prop2} {/* other code */}
</li>
);
return <ul>{props.list.map(renderItem)}</ul>;
};
It could also be defined outside the component since it's really flexible.
const renderItem = (itemClass, { prop1, prop2 }) => (
<li className={itemClass}>
{prop1} {prop2} {/* other code */}
</li>
);
const MyComponent = props => (
<ul>
{props.list.map(item => renderItem(props.itemClass, item))}
</ul>
);
But at that point, we should just define a React component instead of faking it with a function. Use React in a predictable manner and to its full potential.
Inline the logic
It's really common to inline JSX inside of a condition or a map callback.
const MyComponent = (props) => (
<ul>
{props.list.map(({ something, thing }) => (
<li className={props.itemClass}>
{something} {thing} {/* other code */}
</li>
))}
</ul>
);
If we find ourselves copy-pasting this same inlined JSX everywhere, it might be time to wrap it up in its own reusable component.
I think in general people avoid defining functions in render but according to this blog post it is not neccesarily a bad practice. The blog post focuses on inline event handler functions being defined in render but I would guess it applies to any function defined in render. Defining functions in render means there is the overhead of redfining them each time render is called but that may not make a noticible performance difference depending on your component.
For the particular example you gave I would reccomend not to define another react component in render. If you do define any functions in render they should be cohesive to what render is doing. Defining another component or adding a bunch of functions inside of render can make in unwieldy and hard to understand what the code is doing.
const RenderItem = (props) => {
return(
<ul id="todo">
{props.items.map((item,i) =>
<li className='list-group-item' data-id={item.id} key={i}>{item.name}
<button className="btn btn-sm btn-primary" onClick={() => props.remove(item.id)}>X</button>
</li>
)}
</ul>
)
};
const TodoItems = React.createClass({
getInitialState() {
return {
items: [
{id:1,name:"Gym"},
{id:2,name:"Jump"},
{id:3,name:"Racing"}
]
}
},
remove(id){
this.setState({
items: this.state.items.filter((el) => id !== el.id)
})
},
render(){
return(
<RenderItem items={this.state.items} remove={this.remove}/>
)
}
})
ReactDOM.render(
<TodoItems />,
document.getElementById('container')
);
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.js"></script>
<script src="https://npmcdn.com/react#latest/dist/react-with-addons.js"></script>
<script src="https://npmcdn.com/react-dom#latest/dist/react-dom.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
I'm confused how stuff work here in react.js, I need presduo code how to passing work. I was from jquery background, it's so straight forward, just get the right dom and do $(this).remove(), while in react I'm confused.
<button className="btn btn-sm btn-primary" onClick={() => props.remove(item.id)}>X</button>
When you click what happened? arrow function for? and where does the remove come from?
React follows unidirectional data binding, which means, the data will flow in a single direction. So, the data is stored in a state. Whenever the state changes the component will re-render. A state can be changed at any point in time. But, mostly it will be changed on any action. In your case the action in onClick(). This will call the function will the id of the element which triggered the onClick(). Now, in that function, you will iterate through the state, identify, remove and set the new state using setState() which will re-render the component with the new data. This render will not have the clicked element as it was removed from the state.
The remove() function is passed on to the child component from the parent component which can be accessed using props. This will be a reference to the function declared in the parent component. During the click, using this reference, the function in the parent component will be called!
In react, components are rendered based on their props and state. props are passed from the parent component, and a component may or may not have internal state.
Using jQuery, you would try to remove it by removing the DOM node directly. However, in react, interacting with the DOM directly is an anti-pattern. Since the current state and props determines what is rendered, you want to change one of those that will cause a re-rendering and display something new.
In your case, it looks like you're trying to modify props directly. props are read only and should not be modified. Since props are passed down from the parent component, you would have to change what's being passed down from the parent. There are a few ways you can do that, such as passing a callback function defined in the parent component as a prop that can make such a change. But for this example, it might be easier/more applicable to use state.
Remember, you want to change the component's state to reflect what you want to see (what you want to render). So let's say you have an array called items in your component's state. And let's say you are
class ListDemo extends React.component {
constructor() {
super()
// initialize state
this.state = {
items: ['thing1', 'thing2', 'thing3']
}
}
onClick(index) {
// newItems is a new array with the element at the given index removed
// this logic doesn't handle edge cases, but that's besides the point
const newItems = [
...this.state.items.slice(0, index),
...this.state.items.slice(index + 1)
]
// here we set this component's state's items to
// the new state with what we want removed.
// it will then re-render the component to reflect the new state
this.setState({
items: newItems
})
}
render() {
return (
<div>
{
// here we render buttons for each item in the component's state's items
// we use `onClick` to make it so that clicking the button
// will remove it from the list
this.state.items.map((item, index) => {
return (
<button
onClick={() => this.onClick(index)}
key={`${item}${index}`}
>
{item}
</button>
)
})
}
</div>
)
}
}
Let's go over what is happening in this example. First, when the component is created, it's initial state is set with an items array that contains 3 elements. When it is first rendered, it will create a button for each element in the items array. Notice that what is rendered is purely determined by the state.
Each button also has a callback that is called when it's clicked (set by onClick). This callback is what will remove that specific element from the items array and then update the component's state to have a new items array without the element that was just removed. This in turn causes a re-rendering, which uses the new items array to display the buttons. This time around, the button we removed by click it is no longer there since it's no longer in this.state.items.
That is one of the key concepts of react - components are rendered based on their props and state, and changing either will update what is displayed.
As for why to use an arrow function, it's because it will automatically bind the this value to the ListDemo component. Similarly, you can use something like onClick={this.onClick.bind(this)}. Doing this is necessary since the value of this when clicking the button isn't guaranteed to what you expect. Reading this might make it more clear.