Rendering in react with array.map - reactjs

I have an array of strings which I would like to render as a list, with a colored text. The user can change the color with a button.
For that I have built a component called which receives an array and renders a list with the array's values and a button to change the color:
import React, { Component } from "react";
const renderArray = arr => (arr.map(value => (
<li>
{value}
</li>
)))
class List extends Component {
constructor(props) {
super(props);
this.state = {
color: 'red'
}
}
toggleColor = () => {
if (this.state.color === "red") {
this.setState({color: "blue"});
} else {
this.setState({color: "red"});
}
}
render() {
const style = {
color: this.state.color
};
return (
<div style={style}>
<ul>
{renderArray(this.props.array)}
</ul>
<button onClick={this.toggleColor}>Change color</button>
</div>
);
}
}
export default List;
The List is called with:
<List array={arr} />
And arr:
const arr = ['one', 'two', 'three'];
Fiddle here: Fiddle
But this seems incorrect to me. I rerender the whole array by calling renderArray() each time the color changes. In this case it is not too bad but what if the renderArray() is much more complex?
To my understanding, I need to create a new list only if the array prop changes and this could do in getDerivedStateFromProps (or in componentWillReceiveProps which will be deprecated...):
componentWillReceiveProps(nextProps)
{
const renderedArray = renderArray(nextProps.array);
this.setState({ renderedArray });
}
And then, on render, use this.state.renderedArray to show the list.
But this seems strange, to store a rendered object in the state...
Any suggestions?
Thanks!

1) React uses the concept of virtual DOM to calculate the actual difference in memory and only if it exists, render the difference into DOM
2) You can "help" React by providing a "key", so react will better understand if it's needed to re-render list/item or not
3) Your code componentWillReceiveProps can be considered as a bad practice because you're trying to make a premature optimization. Is repaint slow? Did you measure it?
4) IMHO: renderArray method doesn't make sense and can be inlined into List component

React render the DOM elements efficiently by using a virtual DOM and checks if the update needs to happen or not and hence, it may not be an issue even if you render the list using props. To optimise on it, what you can do is to make use of PureComponent which does a shallow comparison of state and props and doesn't cause a re-render if nothing has changed
import Reactfrom "react";
const renderArray = arr => (arr.map(value => (
<li>
{value}
</li>
)))
class List extends React.PureComponent { // PureComponent
constructor(props) {
super(props);
this.state = {
color: 'red'
}
}
toggleColor = () => {
if (this.state.color === "red") {
this.setState({color: "blue"});
} else {
this.setState({color: "red"});
}
}
render() {
const style = {
color: this.state.color
};
return (
<div style={style}>
<ul>
{renderArray(this.props.array)}
</ul>
<button onClick={this.toggleColor}>Change color</button>
</div>
);
}
}
export default List;

Related

Migration from componentWillReceiveProps to getDerivedStateFromProps

I am learning reactjs and I wrote component with the method componentWillReceiveProps (cWRP) but I read that it is deprecated and it must replace with getDerivedStateFromProps (gDSFP) - https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html.
Please note that the following code has the sole purpose of illustrating my problem and questions. It is not a full code.
App.js file :
import React from 'react';
import './App.css';
import Display from './component.js'
class App extends React.Component {
state={resetCounter:false}
resetCounter= () => this.setState( {resetCounter: true} );
render() {
return (
<div className="App">
<header className="App-header">
<Display resetCounter={this.state.resetCounter}></Display>
<div>
<p></p><p></p>
<button onClick={this.resetCounter}>Reset</button>
</div>
</header>
</div>
);
}
componentDidUpdate () {
if (this.state.resetCounter!==false)
this.setState( {resetCounter: false} );
}
}
export default App;
component.js file
import React from 'react'
class Display extends React.Component {
constructor() {
super();
this.state = this.resetState();
this.state.generalCounter=0;
}
/* method to avoid code duplication in constructor and cWRP
could not be used with getDerivedStateFromProps */
resetState = () => ({resettableCounter: 0,});
componentWillReceiveProps(nextProps) {
if (nextProps.resetCounter===true)
this.setState(this.resetState())
}
render() {
return (
<>
<div>
<div>general counter : {this.state.generalCounter}</div>
<div>resettable counter : {this.state.resettableCounter}</div>
</div>
<div>
<button onClick={this.incCounters}>+</button>
<button onClick={this.decCounters}>-</button>
</div>
</>
)
}
incCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter+1,
generalCounter: this.state.generalCounter+1
}
)
decCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter-1,
generalCounter: this.state.generalCounter-1
}
)
}
export default Display
In the state of the component, there is a resettable part and a non resettable one. A method resetState is used to avoid code duplication in the constructor and in cWRP.
To replace cWRP by gDSFP, I wrote a class method because instance method could NOT be called in gDSFP (this is not usable)
...
constructor() {
super();
this.state = Display.resetState();
this.state.generalCounter=0;
}
static resetState () {
return ({resettableCounter: 0,});
}
static getDerivedStateFromProps(nextProps) {
if (nextProps.resetCounter === true) {
return Display.resetState();
} else {
return null;
}
}
...
With this solution, it is very easy to modify all my components but I am not sure that it is a good mean.
I wonder if I have a misconception and if I should rewrite my components to separate them into Fully controlled components and Fully uncontrolled components with a key ( https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions).
For example, in this case, do I have to write :
One Fully uncontrolled components for the resettable counter
One Fully controlled one for the non resettable counter
A parent component with the +/- buttons to render them.
I ask this question because in some cases, it will be much work, so I want to be sure before continuing.
You would want to keep the gdsfp version in your post if your component depends on some outside props, which you don't have controll over (such as JSON returned or 3rd party render props component, etc).
It looks like you have a full control over what's passed down to the Display. You can pass down an initial resettableCounter value down to Display.
The advantage is two-folds.
Your Display props shows what the Display does - Making it more descriptivie/readable.
It's easier to maintain, as you don't have to massage the data.
For your particular case, Fully uncontrolled component with a key seems to make more sense, as Display should accept the initial value to show, but is responsible for managing the reseetableCounter.
Unless it's absolutely unavoidable, don't create components which control their siblings (or parents). Instead, lift state to a common ancestor:
const Display = ({
generalCounter,
resettableCounter,
incrementCounters,
decrementCounters,
}) => (
<div>
<div>General Counter: {generalCounter}</div>
<div>Resettable Counter: {resettableCounter}</div>
<button onClick={incrementCounters}>Increment</button>
<button onClick={decrementCounters}>Decrement</button>
</div>
);
class DisplayContainer extends React.Component {
state = {
generalCounter: 0,
resettableCounter: 0,
};
incrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter + 1,
resettableCounter: prevState.resettableCounter + 1,
}));
decrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter - 1,
resettableCounter: prevState.resettableCounter - 1,
}));
resetResettableCounter = () => this.setState({
resettableCounter: 0,
});
render() {
return (
<React.Fragment>
<Display
{...this.state}
incrementCounters={this.incrementCounters}
decrementCounters={this.decrementCounters}
/>
<button onClick={this.resetResettableCounter}>
Reset Resettable Counter
</button>
</React.Fragment>
);
}
}
const App = () => (
<div>
<DisplayContainer />
</div>
);
An alternative approach would be something like Redux (which effectively lifts state out of React).

How to get the DOM node from a Class Component ref with the React.createRef() API

I have these two components:
import { findDOMNode } from 'react-dom';
class Items extends Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.selectedItemRef = React.createRef();
}
componentDidMount() {
if (this.props.selectedItem) {
this.scrollToItem();
}
}
componentWillReceiveProps(nextProps) {
if (this.props.selectedItem !== nextProps.selectedItem) {
this.scrollToItem();
}
}
scrollToItem() {
const itemsRef = this.ref.current;
const itemRef = findDOMNode(this.selectedItemRef.current);
// Do scroll stuff here
}
render() {
return (
<div ref={this.ref}>
{this.props.items.map((item, index) => {
const itemProps = {
onClick: () => this.props.setSelectedItem(item.id)
};
if (item.id === this.props.selectedItem) {
itemProps.ref = this.selectedItemRef;
}
return <Item {...itemProps} />;
})}
</div>
);
}
}
Items.propTypes = {
items: PropTypes.array,
selectedItem: PropTypes.number,
setSelectedItem: PropTypes.func
};
and
class Item extends Component {
render() {
return (
<div onClick={() => this.props.onClick()}>item</div>
);
}
}
Item.propTypes = {
onClick: PropTypes.func
};
What is the proper way to get the DOM node of this.selectedItemRef in Items::scrollToItem()?
The React docs discourage the use of findDOMNode(), but is there any other way? Should I create the ref in Item instead? If so, how do I access the ref in Items::componentDidMount()?
Thanks
I think what you want is current e.g. this.selectedItemRef.current
It's documented on an example on this page:
https://reactjs.org/docs/refs-and-the-dom.html
And just to be safe I also tried it out on a js fiddle and it works as expected! https://jsfiddle.net/n5u2wwjg/195724/
If you want to get the DOM node for a React Component I think the preferred way of dealing with this is to get the child component to do the heavy lifting. So if you want to call focus on an input inside a component, for example, you’d get the component to set up the ref and call the method on the component, eg
this.myComponentRef.focusInput()
and then the componentRef would have a method called focusInput that then calls focus on the input.
If you don't want to do this then you can hack around using findDOMNode and I suppose that's why it's discouraged!
(Edited because I realized after answering you already knew about current and wanted to know about react components. Super sorry about that!)

How to scroll to bottom when props changed in react-virtualized?

I have component App with List from react-virtualized library.
And I need on initial render, that my List scroll to bottom.
And I did it, when added scrollToIndex option. But when I add new object in my list array, it does not scroll to my last added object. How can I fix it? And is it good solution to use "forceUpdate()" function?
import { List } from "react-virtualized";
import loremIpsum from 'lorem-ipsum';
const rowCount = 1000;
const listHeight = 600;
const rowHeight = 50;
const rowWidth = 800;
class App extends Component {
constructor() {
super();
this.renderRow = this.renderRow.bind(this);
this.list = Array(rowCount).fill().map((val, idx) => {
return {
id: idx,
name: 'John Doe',
image: 'http://via.placeholder.com/40',
text: loremIpsum({
count: 1,
units: 'sentences',
sentenceLowerBound: 4,
sentenceUpperBound: 8
})
}
});
}
handle = () => {
this.list = [...this.list, { id: 1001, name: "haha", image: '', text: 'hahahahahaha' }];
this.forceUpdate();
this.refs.List.scrollToRow(this.list.length);
};
renderRow({ index, key, style }) {
console.log('____________', this.list.length);
return (
<div key={key} style={style} className="row" >
<div className="image">
<img src={this.list[index].image} alt="" />
</div>
<div onClick={this.handle}>{this.state.a}</div>
<div className="content">
<div>{this.list[index].name}</div>
<div>{this.list[index].text}</div>
</div>
</div>
);
}
render() {
return (
<div className="App">
<div className="list">
<List
ref='List'
width={rowWidth}
height={listHeight}
rowHeight={rowHeight}
rowRenderer={this.renderRow}
rowCount={this.list.length}
overscanRowCount={3}
scrollToIndex={this.list.length}
/>
</div>
</div>
);
}
}
export default App;
You mentioning you need to scroll to the bottom when the list item is changed and to be honest i don't like to use forceUpdate. As mentioned on the React docs:
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().
Luckily, one of React lifecycle method is suitable for this case, it is call componentDidUpdate. But you need to do some refactor of your code. Instead using private field, i suggest to put it on state/props.
This method will invoked immediately after updating props/state occurs. However, This method is not called for the initial render.
What you need to do is, compare the props, is it change or not? Then call this.refs.List.scrollToRow(this.list.length);
Sample code
class App extends Component {
constructor() {
this.state = {
list: [] // put your list data here
}
}
// Check the change of the list, and trigger the scroll
componentDidUpdate(prevProps, prevState) {
const { list } = this.state;
const { list: prevList } = prevState;
if (list.length !== prevList.length) {
this.refs.List.scrollToRow(list.length);
}
}
render() {
// usual business
}
}
more reference for React lifecyle methods:
https://reactjs.org/docs/react-component.html#componentdidupdate

How to pass params to method in JSX [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;
}

ReactJS: How can I change dynamically inserted Component's data

I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,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" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>

Resources