React/Redux rendering a list that's updating every second - javascript

I have a react component that receives props from the redux store every second. The new state has an array that's different than the last array. To be specific, every second an element is added to the array. For example:
in one state the array is:
[1, 2, 3, 4, 5, 6]
the next state
[1, 2, 3, 4, 5, 6, 7]
My reducer:
return {
...state,
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]
}
Now, in my react component I am subscribing to this state and for every change, the list is re-rendered.
import React, { Component } from 'react';
import MyRow from './MyRow';
class MyList extends Component {
render() {
return (
<div>
{this.props.myList.map((list, index) => (
<MyRow key={list.id} data={list}/>
))}
</div>
);
}
}
function select({ myList }) {
return { myList };
}
export default connect(select)(MyList);
In MyRow.js
import { PureComponent } from 'react';
class MyRow extends PureComponent {
render() {
const data = this.props.data;
return (
<div>
{data.id} - {data.name}
</div>
);
}
}
export default MyRow;
Now, my problem is: It's costly for me to re-render every element that has been already rendered. The MyRow heavily uses styled components and other expensive operations.
This is causing react to re-render the whole list every second when the state is updated. This gets worst if updates come in less than 1 seconds, like 4 updates per second. The react app simply crashes in this case.
Is there any way to only add the newly added item to the list and not re-render the whole list?
Thanks

You're using PureComponent, that do shallow comparison, then your component MyRow should not be rerendered on each new item being added (Please follow my code example below).
Is there any way to only add the newly added item to the list and not re-render the whole list?
According to your question - Yes, using PureComponent should render only 1 time the new item:
Here's what the React's docs says:
If your React component’s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
Code example of PureComponent:
You can check out the code sample, that I did for you.
You will see that the Item component is always rendered only 1 time, because we use React.PureComponent. To prove my statement, each time the Item is rendered, I added current time of rendering. From the example you will see that the Item Rendered at: time is always the same, because it's rendered only 1 time.
const itemsReducer = (state = [], action) => {
if (action.type === 'ADD_ITEM') return [ ...state, action.payload]
return state
}
const addItem = item => ({
type: 'ADD_ITEM',
payload: item
})
class Item extends React.PureComponent {
render () {
// As you can see here, the `Item` is always rendered only 1 time,
// because we use `React.PureComponent`.
// You can check that the `Item` `Rendered at:` time is always the same.
// If we do it with `React.Component`,
// then the `Item` will be rerendered on each List update.
return <div>{ this.props.name }, Rendered at: { Date.now() }</div>
}
}
class List extends React.Component {
constructor (props) {
super(props)
this.state = { intervalId: null }
this.addItem = this.addItem.bind(this)
}
componentDidMount () {
// Add new item on each 1 second,
// and keep its `id`, in order to clear the interval later
const intervalId = setInterval(this.addItem, 1000)
this.setState({ intervalId })
}
componentWillUnmount () {
// Use intervalId from the state to clear the interval
clearInterval(this.state.intervalId)
}
addItem () {
const id = Date.now()
this.props.addItem({ id, name: `Item - ${id}` })
}
renderItems () {
return this.props.items.map(item => <Item key={item.id} {...item} />)
}
render () {
return <div>{this.renderItems()}</div>
}
}
const mapDispatchToProps = { addItem }
const mapStateToProps = state => ({ items: state })
const ListContainer = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(List)
const Store = Redux.createStore(itemsReducer)
const Provider = ReactRedux.Provider
ReactDOM.render(
<Provider store={Store}>
<ListContainer />
</Provider>,
document.getElementById('container')
)
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
Solutions:
If the performance problem is caused by MyRow rerending, please find out what's the reason of rerending, because it should not happen, because of PureComponent usage.
You can try to simplify your reducer, in order to check / debug, is the reducer causing the problem. For instance, just add the new item to the list (without doing anything else as filtrations, slice, etc): myList: [ ...state.myList, payload ]
Please make sure you always pass the same key to your item component <MyRow key={list.id} data={list} />. If the key or data props are changed, then the component will be rerendered.
Here are some other libraries, these stand for efficient rendering of lists. I'm sure they will give us some alternatives or insights:
react-virtualized - React components for efficiently rendering large lists and tabular data
react-infinite - A browser-ready efficient scrolling container based on UITableView

PureComponent will shallowly compare the props and state. So my guess here is that the items are somehow new objects than the previous passed props, thus the rerendering.
I would advice, in general, to only pass primitive values in pure components :
class MyList extends Component {
render() {
return (
<div>
{this.props.myList.map((item, index) => (
<MyRow key={item.id} id={item.id} name={data.name} />
//or it's alternative
<MyRow key={item.id} {...item} />
))}
</div>
);
}
}
//...
class MyRow extends PureComponent {
render() {
const {id, name} = this.props;
return (
<div>
{id} - {name}
</div>
);
}
}

The problem really exists in the reducer.
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id).slice(0, -1) ]
What is the logic implemented using slice(0,-1)?
It is the culprit here.
From your question I understood the next state after [1,2,3] will be [1,2,3,4].
But your code will be giving [4,1,2], then [5,4,1] then [6,5,4].
Now all the elements in the state are new, not in the initial state. See state is not just getting appended it is completely changing.
Please see if you are getting the desired result by avoiding slice.
myList: [ payload, ...state.myList.filter(item => payload.id !== item.id)]

There is quite an easy solution for this. React VDOM is just a diffing algorithm. The only piece missing with your JSX is something called key which is like an id that the diffing algo uses and renders the particular element. Just tag the element with a KEY something like this https://reactjs.org/docs/lists-and-keys.html#keys
<li key={number.toString()}>
{number} </li>

it looks like you are creating a new array each time in the reducer in which all array indices need to be re-calculated. have you tried appending the new node to the end of the list instead of prepending?

Related

React functional component rerenders despite use of React.memo()

I'm fairly new to React and I'm having some trouble understanding exactly why an unchanging component is getting rerendered, even though I'm using the React.memo higher-order component.
I have a sidebar which contains a number of row elements. Rows contain data that's used in other components; all components share the 'selection' status of the rows. In the sidebar, I change the styling to show the selection state of every element.
Everything behaves as expected, but performance scales poorly as the list gets longer. I think part of this is due to React re-rendering every row element in the sidebar list, including ones whose selection state has not changed. I thought I could prevent this re-rendering by using React.memo, but it doesn't seem to make a difference.
Here is the code for each list entry:
import React from 'react';
// The only props that might change value are the labels string and
// the styles rowStyle and labelStyle, which caller populates
// with 'selected' or 'unselected' styles based on row state
const Row = React.memo(({
rowId, labels = "", rowStyle = {}, labelStyle = {},
onClicked // callback from grandparent, which updates selections (w/ modifier keys)
}) => {
console.log(`Rendering row ${rowId}`) // to report when rows rerender
return (
<div
key={rowId}
style={rowStyle}
onClick={(event) => onClicked(rowId, event)}
>
<span>{rowId}</span>
<span style={labelStyle}>{ labels }</span>
</div>
);
})
export default Row;
This component is called from a parent which represents the entire sidebar list. In order to minimize the amount of needless function calls (and make very clear that there's nothing with any side effects happening within the individual rows), I build a list of tuples for each row that has its id, style, labels, and label-style.
The contents of the list are passed to the Row component, and most of the time should be identical between calls (thus triggering memoization and avoiding the rerender), but don't seem to be.
import React from 'react';
import Row from '../pluginComponents/Row';
import Styles from './common/Styles'; // to ensure the references aren't changing
// onClicked is passed in from the parent component and handles changing the selections
const ListDiv = React.memo(({ rowIds, onClicked, rowLabels, styling, selections }) => {
const tuples = rowIds.reduce((priors, rowId) => {
return {
...priors,
[rowId]: {
'style': Styles.unselectedStyle,
'labelStyle': Styles.unselectedLabelStyle,
'labels': ((rowLabels[rowId] || {}).labels || []).join(", ")
}
}
}, {});
Object.keys(selections).forEach((rowId) => {
if (!tuples[rowId]) return;
tuples[rowId]['style'] = Styles.selectedStyle;
tuples[rowId]['labelStyle'] = Styles.selectedLabelStyle;
});
return (
<div style={styling}>
{rowIds.map((rowId) => (
<Row
key={rowId}
rowId={rowId}
labels={tuples[rowId]['labels']}
rowStyle={tuples[rowId]['style']}
labelStyle={tuples[rowId]['labelStyle']}
onClicked={onClicked}
/>
))}
</div>
)
})
const RowList = ({ list, selections = {}, onClicked, labels={}, styling }) => {
if (!list) return (<div>Empty list</div>);
return (
<div>
<ListDiv
rowIds={list}
onClicked={onClicked}
rowLabels={labels}
styling={styling}
selections={selections}
/>
</div>
);
}
export default RowList;
which is itself called from a grandparent class that manages all the state:
const Grandparent = (props) => {
...
return (
...
<div>
{
(status !== 'complete') ? (
<div><CircularProgress /></div>
) : (
<RowList list={data.list}
selections={selections} // tracked with useState
onClicked={handleClicked} // calls some functions defined in this component
labels={data.labels || {}}
styling={foo}
/>
)
}
...
);
...
Why are my ought-to-be-memoized entries of the Row component getting rerendered, and what can I do to fix it?
The onClicked function in the Grandparent could be getting recreated on each render, so making your row component re-render as well.
The solution is to use React.useCallback in the Grandparent.
const handleClicked = React.useCallback(() => {
...
}, [a, b])
Where a and b are dependencies that if change will require a re-render.
React useCallback docs

Which of these strategies is the best way to reset a component's state when the props change

I have a very simple component with a text field and a button:
It takes a list as input and allows the user to cycle through the list.
The component has the following code:
import * as React from "react";
import {Button} from "#material-ui/core";
interface Props {
names: string[]
}
interface State {
currentNameIndex: number
}
export class NameCarousel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentNameIndex: 0}
}
render() {
const name = this.props.names[this.state.currentNameIndex].toUpperCase()
return (
<div>
{name}
<Button onClick={this.nextName.bind(this)}>Next</Button>
</div>
)
}
private nextName(): void {
this.setState( (state, props) => {
return {
currentNameIndex: (state.currentNameIndex + 1) % props.names.length
}
})
}
}
This component works great, except I have not handled the case when the state changes. When the
state changes, I would like to reset the currentNameIndex to zero.
What is the best way to do this?
Options I have conciderred:
Using componentDidUpdate
This solution is ackward, because componentDidUpdate runs after render, so I need to add a clause
in the render method to "do nothing" while the component is in an invalid state, if I am not careful,
I can cause a null-pointer-exception.
I have included an implementation of this below.
Using getDerivedStateFromProps
The getDerivedStateFromProps method is static and the signature only gives you access to the
current state and next props. This is a problem because you cannot tell if the props have changed. As
a result, this forces you to copy the props into the state so that you can check if they are the same.
Making the component "fully controlled"
I don't want to do this. This component should privately own what the currently selected index is.
Making the component "fully uncontrolled with a key"
I am considering this approach, but don't like how it causes the parent to need to understand the
implementation details of the child.
Link
Misc
I have spent a great deal of time reading You Probably Don't Need Derived State
but am largely unhappy with the solutions proposed there.
I know that variations of this question have been asked multiple times, but I don't feel like any of the answers weigh the possible solutions. Some examples of duplicates:
How to reset state in a component on prop change
Update component state when props change
Updating state on props change in React Form
Appendix
Solution using componetDidUpdate (see description above)
import * as React from "react";
import {Button} from "#material-ui/core";
interface Props {
names: string[]
}
interface State {
currentNameIndex: number
}
export class NameCarousel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentNameIndex: 0}
}
render() {
if(this.state.currentNameIndex >= this.props.names.length){
return "Cannot render the component - after compoonentDidUpdate runs, everything will be fixed"
}
const name = this.props.names[this.state.currentNameIndex].toUpperCase()
return (
<div>
{name}
<Button onClick={this.nextName.bind(this)}>Next</Button>
</div>
)
}
private nextName(): void {
this.setState( (state, props) => {
return {
currentNameIndex: (state.currentNameIndex + 1) % props.names.length
}
})
}
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>): void {
if(prevProps.names !== this.props.names){
this.setState({
currentNameIndex: 0
})
}
}
}
Solution using getDerivedStateFromProps:
import * as React from "react";
import {Button} from "#material-ui/core";
interface Props {
names: string[]
}
interface State {
currentNameIndex: number
copyOfProps?: Props
}
export class NameCarousel extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { currentNameIndex: 0}
}
render() {
const name = this.props.names[this.state.currentNameIndex].toUpperCase()
return (
<div>
{name}
<Button onClick={this.nextName.bind(this)}>Next</Button>
</div>
)
}
static getDerivedStateFromProps(props: Props, state: State): Partial<State> {
if( state.copyOfProps && props.names !== state.copyOfProps.names){
return {
currentNameIndex: 0,
copyOfProps: props
}
}
return {
copyOfProps: props
}
}
private nextName(): void {
this.setState( (state, props) => {
return {
currentNameIndex: (state.currentNameIndex + 1) % props.names.length
}
})
}
}
As i said in the comments, i'm not a fan of these solutions.
Components should not care what the parent is doing or what is the current state of the parent, they should simply take in props and output some JSX, this way they are truly reusable, composable and isolated which also makes testing a lot easier.
We can make the NamesCarousel component hold the names of the carousel together with the functionality of the carousel and the current visible name and make a Name component which does only one thing, display the name that comes in through props
To reset the selectedIndex when the items are changing add a useEffect with items as a dependency, although if you just add items to the end of the array you can ignore this part
const Name = ({ name }) => <span>{name.toUpperCase()}</span>;
const NamesCarousel = ({ names }) => {
const [selectedIndex, setSelectedIndex] = useState(0);
useEffect(() => {
setSelectedIndex(0)
}, [names])// when names changes reset selectedIndex
const next = () => {
setSelectedIndex(prevIndex => prevIndex + 1);
};
const prev = () => {
setSelectedIndex(prevIndex => prevIndex - 1);
};
return (
<div>
<button onClick={prev} disabled={selectedIndex === 0}>
Prev
</button>
<Name name={names[selectedIndex]} />
<button onClick={next} disabled={selectedIndex === names.length - 1}>
Next
</button>
</div>
);
};
Now this is fine but is the NamesCarousel reusable? no, the Name component is but the Carousel is coupled with the Name component.
So what can we do to make it truly reusable and see the benefits of designing component in isolation?
We can take advantage of the render props pattern.
Lets make a generic Carousel component which will take a generic list of items and invoke the children function passing in the selected item
const Carousel = ({ items, children }) => {
const [selectedIndex, setSelectedIndex] = useState(0);
useEffect(() => {
setSelectedIndex(0)
}, [items])// when items changes reset selectedIndex
const next = () => {
setSelectedIndex(prevIndex => prevIndex + 1);
};
const prev = () => {
setSelectedIndex(prevIndex => prevIndex - 1);
};
return (
<div>
<button onClick={prev} disabled={selectedIndex === 0}>
Prev
</button>
{children(items[selectedIndex])}
<button onClick={next} disabled={selectedIndex === items.length - 1}>
Next
</button>
</div>
);
};
Now what this pattern actually gives us?
It gives us the ability to render the Carousel component like this
// items can be an array of any shape you like
// and the children of the component will be a function
// that will return the select item
<Carousel items={["Hi", "There", "Buddy"]}>
{name => <Name name={name} />} // You can render any component here
</Carousel>
Now they are both isolated and truly reusable, you can pass items as an array of images, videos, or even users.
You can take it further and give the carousel the number of items you want to display as props and invoke the child function with an array of items
return (
<div>
{children(items.slice(selectedIndex, selectedIndex + props.numOfItems))}
</div>
)
// And now you will get an array of 2 names when you render the component
<Carousel items={["Hi", "There", "Buddy"]} numOfItems={2}>
{names => names.map(name => <Name key={name} name={name} />)}
</Carousel>
Can you use a functional component? Might simplify things a bit.
import React, { useState, useEffect } from "react";
import { Button } from "#material-ui/core";
interface Props {
names: string[];
}
export const NameCarousel: React.FC<Props> = ({ names }) => {
const [currentNameIndex, setCurrentNameIndex] = useState(0);
const name = names[currentNameIndex].toUpperCase();
useEffect(() => {
setCurrentNameIndex(0);
}, names);
const handleButtonClick = () => {
setCurrentIndex((currentNameIndex + 1) % names.length);
}
return (
<div>
{name}
<Button onClick={handleButtonClick}>Next</Button>
</div>
)
};
useEffect is similar to componentDidUpdate where it will take an array of dependencies (state and prop variables) as the second argument. When those variables change, the function in the first argument is executed. Simple as that. You can do additional logic checks inside of the function body to set variables (e.g., setCurrentNameIndex).
Just be careful if you have a dependency in the second argument that gets changed inside the function, then you will have infinite rerenders.
Check out the useEffect docs, but you'll probably never want to use a class component again after getting used to hooks.
You ask what is the best option, the best option is to make it a Controlled component.
The component is too low in the hierarchy to know how to handle it's properties changing - what if the list changed but only slightly (perhaps adding a new name) - the calling component might want to keep the original position.
In all cases I can think about we are better off if the parent component can decide how the component should behave when provided a new list.
It's also likely that such a component is part of a bigger whole and needs to pass the current selection to it's parent - perhaps as part of a form.
If you are really adamant on not making it a controlled component, there are other options:
Instead of an index you can keep the entire name (or an id component) in the state - and if that name no longer exists in the names list, return the first in the list. This is a slightly different behavior than your original requirements and might be a performance issue for a really really really long list, but it's very clean.
If you are ok with hooks, than useEffect as Asaf Aviv suggested is a very clean way to do it.
The "canonical" way to do it with classes seems to be getDerivedStateFromProps - and yes that means keeping a reference to the name list in the state and comparing it. It can look a bit better if you write it something like this:
static getDerivedStateFromProps(props: Props, state: State = {}): Partial<State> {
if( state.names !== props.names){
return {
currentNameIndex: 0,
names: props.names
}
}
return null; // you can return null to signify no change.
}
(you should probably use state.names in the render method as well if you choose this route)
But really - controlled component is the way to go, you'll probably do it sooner or later anyway when demands change and the parent needs to know the selected item.

How to handle inputs added by button in React [duplicate]

We should avoid method binding inside render because during re-rendering it will create the new methods instead of using the old one, that will affect the performance.
So for the scenarios like this:
<input onChange = { this._handleChange.bind(this) } ...../>
We can bind _handleChange method either in constructor:
this._handleChange = this._handleChange.bind(this);
Or we can use property initializer syntax:
_handleChange = () => {....}
Now lets consider the case where we want to pass some extra parameter, lets say in a simple todo app, onclick of item i need to delete the item from array, for that i need to pass either the item index or the todo name in each onClick method:
todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)
For now just assume that todo names are unique.
As per DOC:
The problem with this syntax is that a different callback is created
each time the component renders.
Question:
How to avoid this way of binding inside render method or what are the alternatives of this?
Kindly provide any reference or example, thanks.
First: A simple solution will be to create a component for the content inside a map function and pass the values as props and when you call the function from the child component you can pass the value to the function passed down as props.
Parent
deleteTodo = (val) => {
console.log(val)
}
todos.map(el =>
<MyComponent val={el} onClick={this.deleteTodo}/>
)
MyComponent
class MyComponent extends React.Component {
deleteTodo = () => {
this.props.onClick(this.props.val);
}
render() {
return <div onClick={this.deleteTodo}> {this.props.val} </div>
}
}
Sample snippet
class Parent extends React.Component {
_deleteTodo = (val) => {
console.log(val)
}
render() {
var todos = ['a', 'b', 'c'];
return (
<div>{todos.map(el =>
<MyComponent key={el} val={el} onClick={this._deleteTodo}/>
)}</div>
)
}
}
class MyComponent extends React.Component {
_deleteTodo = () => {
console.log('here'); this.props.onClick(this.props.val);
}
render() {
return <div onClick={this._deleteTodo}> {this.props.val} </div>
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
EDIT:
Second: The other approach to it would be to use memoize and return a function
constructor() {
super();
this._deleteTodoListener = _.memoize(
this._deleteTodo, (element) => {
return element.hashCode();
}
)
}
_deleteTodo = (element) => {
//delete handling here
}
and using it like
todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)
P.S. However this is not a best solution and will still result in
multiple functions being created but is still an improvement over the
initial case.
Third: However a more appropriate solution to this will be to add an attribute to the topmost div and get the value from event like
_deleteTodo = (e) => {
console.log(e.currentTarget.getAttribute('data-value'));
}
todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)
However, in this case the attributes are converted to string using toString method and hence and object will be converted to [Object Object] and and array like ["1" , "2", "3"] as "1, 2, 3"
How to avoid this way of binding inside render method or what are the
alternatives of this?
If you care about re-rendering then shouldComponentUpdate and PureComponent are your friends and they will help you optimize rendering.
You have to extract "Child" component from the "Parent" and pass always the same props and implement shouldComponentUpdate or use PureComponent. What we want is a case when we remove a child, other children shouldn't be re-rendered.
Example
import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';
class Product extends PureComponent {
render() {
const { id, name, onDelete } = this.props;
console.log(`<Product id=${id} /> render()`);
return (
<li>
{id} - {name}
<button onClick={() => onDelete(id)}>Delete</button>
</li>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: [
{ id: 1, name: 'Foo' },
{ id: 2, name: 'Bar' },
],
};
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(productId) {
this.setState(prevState => ({
products: prevState.products.filter(product => product.id !== productId),
}));
}
render() {
console.log(`<App /> render()`);
return (
<div>
<h1>Products</h1>
<ul>
{
this.state.products.map(product => (
<Product
key={product.id}
onDelete={this.handleDelete}
{...product}
/>
))
}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Demo: https://codesandbox.io/s/99nZGlyZ
Expected behaviour
<App /> render()
<Product id=1... render()
<Product id=2... render()
When we remove <Product id=2 ... only <App /> is re-rendered.
render()
To see those messages in demo, open the dev tools console.
The same technique is used and described in article: React is Slow, React is Fast: Optimizing React Apps in Practice by François Zaninotto.
Documentation encourages to use data-attributes and access them from within evt.target.dataset:
_deleteTodo = (evt) => {
const elementToDelete = evt.target.dataset.el;
this.setState(prevState => ({
todos: prevState.todos.filter(el => el !== elementToDelete)
}))
}
// and from render:
todos.map(
el => <div key={el} data-el={el} onClick={this._deleteTodo}> {el} </div>
)
Also note that this makes sense only when you have performance issues:
Is it OK to use arrow functions in render methods?
Generally speaking, yes, it is OK, and it is often the easiest way to
pass parameters to callback functions.
If you do have performance issues, by all means, optimize!
This answer https://stackoverflow.com/a/45053753/2808062 is definitely exhaustive, but I'd say fighting excessive re-renders instead of just re-creating the tiny callback would bring you more performance improvements. That's normally achieved by implementing a proper shouldComponentUpdate in the child component.
Even if the props are exactly the same, the following code will still re-render children unless they prevent it in their own shouldComponentUpdate (they might inherit it from PureComponent):
handleChildClick = itemId => {}
render() {
return this.props.array.map(itemData => <Child onClick={this.handleChildClick} data={itemData})
}
Proof: https://jsfiddle.net/69z2wepo/92281/.
So, in order to avoid re-renders, the child component has to implement shouldComponentUpdate anyway. Now, the only reasonable implementation is completely ignoring onClick regardless of whether it has changed:
shouldComponentUpdate(nextProps) {
return this.props.array !== nextProps.array;
}

React Redux table update row

I'm new to react and redux.
I have a container which initialize a table component with a list of items, and onclick function.
In the table component I have checkbox for each row. When I click the checkbox I want to select the row (change its style and add selected property to its element model).
When I click on the checkbox I call the onclick property function, then find the item on the list by its id, and change its selected property. The view is not refreshing.
I understand that a component is a "stupid" component that only binds the props and rendering.
What am I doing wrong?
// People container
<Table items={this.props.people} columns={this._columns} onRowSelect={this.selectRow} />
this.selectRow(id){
const selectedLead =_.find(this.props.leads.docs, (lead)=>{
return lead._id == id;
})
selectedLead.selected = !selectedLead.selected;
}
// Table Component - inside render()
{this.props.items.map((item, idx) => {
console.log(item.selected);
return <div style={styles.row(item.selected)}>etc...</div>
})}
Thanks :)
A React Component has props and state.
The difference is, that the Component will never change it props. But it can change it's state. This is why a Component will provide you the setState(...) Method, but no setProps(...) Method.
With that said, your approach to change the selected field in this.props is fundamentally not correct. (There also seems to be another problem in your code where you change the selected field in this.props.leads, but provide this.props.people to the table instead of this.props.leads)
Let me give you a basic example as to how I would solve your problem in Pure React (without a state library like Redux):
const Row = ({ item, onClick }) => (
<tr style={styles.row(item.selected)} onClick={() => onClick(item.id)}>...</tr>
)
const Table = ({ items, onRowClick }) => (
<table>
{items.map(item => <Row item={item} onClick={onRowClick} />)}
</table>
)
class PeopleTable extends React.PureComponent {
constructor(props) {
super(props)
this.state = { people: props.people }
}
componentWillReceiveProps(nextProps) {
if (nextProps.people !== this.state.people) {
this.setState({ people: nextProps.people })
}
}
setItemSelectedState(id) {
this.setState((prevState) => {
const people = prevState.people.map(item => ({
...item,
selected: item.id === id ? !item.selected : item.selected,
})
return { people }
})
}
handleRowClick = (id) => this.setItemSelectedState(id)
render() {
return (<Table items={people} onRowClick={this.handleRowClick} />)
}
}
The things to notice here are:
Row and Table are stateless components. They only take props and return jsx. Sometimes they are also referred to as presentational components.
PeopleTable keeps track of the selected state of each item. This is why it needs state and must be a class.
Because we can't change a components props, we have to keep a reference to props.people in this.state.
componentWillReceiveProps makes sure that if our components receives another list of people, the state is updated accordingly.
setItemSelectedState goes to the root of your problem. Instead of search and update of the item (like in your this.selectRow(id) method), we create a complete new list of people with map and call setState. setState will trigger a rerender of the component and because we created a new people list, we can use the !== check in componentWillReceiveProps to check if people has changed.
I hope this answer was helpful to your question.

Encountered two children with the same key

I'm really new to react and redux development. I have a list component that is connected to a container. I want to update a list on scroll but i get:
Encountered two children with the same key
My component:
import React, { Component, PropTypes } from 'react'
import Track from './Track'
import styles from './TrackList.css'
const altImg = require('../images/sc.jpg');
export default class TrackList extends Component {
static propTypes = {
tracks: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired
}).isRequired).isRequired,
onTrackListScroll: PropTypes.func.isRequired
}
render() {
const { tracks, onTrackListScroll } = this.props;
return (
<div>
<ul className='tracks'
onScroll={(e)=>{
console.log('SCROLL!!',e)
onTrackListScroll()
}}>
{tracks.map(track =>
<Track
key={track.id}
{...track}
//onClick={() => onTrackClick(track.id)}
text={track.title}
imgSrc={!track.artwork_url ? altImg : track.artwork_url}
/>
)}
</ul>
</div>
)
}
}
reducer that update a state is :
const toSearchResultOb = (tracks) => {
return {
tracks: tracks
}
}
case 'UPDATE_SEARCH_RESULT':
return Object.assign({}, state,
toSearchResultOb(state.tracks.concat(action.tracks)))
What is correct way to update component onScroll with redux?
You're getting this error because keys between component siblings need to be unique. You probably have duplicate track.id in your tracks array.
Here's an easy fix:
{tracks.map(track, i =>
<Track
key={i}
{...track}
//onClick={() => onTrackClick(track.id)}
text={track.title}
imgSrc={!track.artwork_url ? altImg : track.artwork_url}
/>
)}
If you have a look at the documentation of map() on MDN, you'll see this:
callback Function that produces an element of the new Array, taking
three arguments:
currentValue The current element being processed in
the array.
index The index of the current element being processed in
the array.
So in the example above, i is the index of the current element. This index increments on each iteration which guarantees unique keys within that map(). Now you don't have to worry about what track.id is.

Categories