Prevent unnecessary re-render of React component - javascript

I have a PureComponent that renders another component and implements its onClick callback:
class ColorPicker extends React.PureComponent {
render() {
console.log('ColorPicker being rendered');
const fields = this.props.colors.map((color, idx) => {
const fieldProps = {
key: `${idx}`,
color,
/*onClick: () => { // PROBLEM HERE
this.props.colorPicked(color);
}*/
};
return <ColorField { ...fieldProps}/>;
});
return (
<div className="bla-picker">
<div>{`Refresh seed: ${this.props.seed}`}</div>
{fields}
< /div>
);
}
}
There is a small issue with this component: Whenever the ColorPicker is re-rendered, the nested ColorFields need to be re-rendered, too, because their onClick property changes each time. Using a lambda function will create a new instance of that function whenever the component is rendered.
I usually solve this by moving the implementation of onClick outside of the render method, like this: onClick: this.handleClick. However, I can't do this here, because the onClick handler needs to capture the color variable.
What's the best practice to solve this kind of problem?
Here's a jsfiddle to try it out; and as a snippet:
class ColorField extends React.PureComponent {
render() {
console.log('ColorField being rendered');
const divProps = {
className: 'bla-field',
style: {
backgroundColor: this.props.color
},
onClick: this.props.onClick
};
return <div { ...divProps}/>;
}
}
class ColorPicker extends React.PureComponent {
render() {
console.log('ColorPicker being rendered');
const fields = this.props.colors.map((color, idx) => {
const fieldProps = {
key: `${idx}`,
color,
/*onClick: () => { // PROBLEM HERE
this.props.colorPicked(color);
}*/
};
return <ColorField { ...fieldProps}/>;
});
return (
<div className="bla-picker">
<div>{`Refresh seed: ${this.props.seed}`}</div>
{fields}
< /div>
);
}
}
class Layout extends React.PureComponent {
constructor(props, ctx) {
super(props, ctx);
this.state = {
seed: 1
};
}
render() {
const pickerProps = {
colors: ['#f00', '#0f0', '#00f'],
colorPicked: (color) => {
console.log(`Color picked: ${color}`);
},
seed: this.state.seed
};
return (
<div>
<div
className="bla-button"
onClick = {this.btnClicked}
>
{'Click Me'}
</div>
<ColorPicker { ...pickerProps} />
</div>
);
}
btnClicked = () => {
this.setState({ seed: this.state.seed + 1 });
};
};
ReactDOM.render( <
Layout / > ,
document.getElementById("react")
);
.bla-button {
background-color: #aaa;
padding: 8px;
margin-bottom: 8px;
}
.bla-picker {}
.bla-field {
width: 32px;
height: 32px;
}
<div id="react">
</div>
<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>
As long as onClick remains commented out, only ColorPicker is re-rendered when the seed changes (see output from console.log). As soon as onClick is put in, all the ColorFields are re-rendered, too.

You can implement shouldComponentUpdate in your ColorField component like:
class ColorField extends React.Component {
shouldComponentUpdate(nextProps) {
return this.props.color !== nextProps.color;
}
render(){
const { color, onClick } = this.props;
console.log('Color re-rendered');
return (
<div
className="color"
onClick={onClick}
style={{
backgroundColor: color,
height: '50px',
width: '50px',
}}
/>
)
}
}
Example here
Be attentive as in the first solution we can use just React.Component because we implement shouldComponentUpdate by ourselves.

Related

React Color doesn't let to change the color

I'm using React Color for my project. So I added ChromePicker as in the example into the code.
When the button is clicked, the picker is shown, when it's clicked outside of it - it gets closed. So far so good, it works as expected.
But if I try to change the color, to move the circle or the bars below the gradient there is no action, they are not moving. I don't know why is this happening.
Here is my code:
import React from 'react';
import { Button } from 'semantic-ui-react';
import { ChromePicker } from 'react-color';
export default class Banner extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
displayColorPicker: false,
};
}
handleClick = () => {
this.setState({ displayColorPicker: true });
};
handleClose = () => {
this.setState({ displayColorPicker: false });
};
render() {
const popover = {
position: 'absolute',
zIndex: '2',
};
const cover = {
position: 'fixed',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px',
};
return (
<div>
...//some other code
<div>
<Button onClick={this.handleClick}>Pick Color</Button>
{this.state.displayColorPicker ? (
<div style={popover}>
<div
style={cover}
onClick={this.handleClose}
onKeyDown={this.handleClick}
role="button"
tabIndex="0"
aria-label="Save"
/>
<ChromePicker />
</div>
) : null}
</div>
</div>
);
}
}
This issue might answer your question: https://github.com/casesandberg/react-color/issues/717
Something was changed between version 2.17 and 2.18, you need to either downgrade or make controlled component.
import React from 'react';
import { SketchPicker } from 'react-color';
class Component extends React.Component {
state = {
background: '#fff',
};
handleChangeComplete = (color) => {
this.setState({ background: color.hex });
};
render() {
return (
<SketchPicker
color={ this.state.background }
onChangeComplete={ this.handleChangeComplete }
/>
);
}
}
You should use the onChangeComplete props I suppose, you can find further info here

React update state within a method and passing it

I am trying to update the state in a method start() and then passing the state's value to MyComponent.
My component works ok, and the state updates ok when I'm setting it within the class, but when I'm trying to pass it from start method - it doesn't work. getting "TypeError: this.setState is not a function"
Edited - fixed binding but something still doesn't work.
What can be the problem?
export default class App extends Component{
constructor (props){
super(props);
this.state = {
val: false
}
this.start= this.start.bind(this)
}
start() {
this.setState({ val: true })
}
render(){
return (
<View>
<Button
title='start'
onPress={this.start}>
</Button>
<MyComponent value={this.state.val}> </MyComponent>
</View>
);
}
}
this is MyComponent:
class MyComponent extends Component {
constructor(props){
super(props)
this.state={}
this.state.custum={
backgroundColor: 'red'
}
let intervalid;
if (this.props.value){
setTimeout(() => {
this.setState( {
custum:{
backgroundColor: 'green'
}
})
}, 1000);
setTimeout(() => {
this.setState( {
custum:{
backgroundColor: 'red'
}
})
}, 2000);
}
}
render() {
return (
<View style={[styles.old, this.state.custum]}>
</View>
);
}
}
var styles = StyleSheet.create({
old:{
padding: 5,
height: 80,
width: 80,
borderRadius:160,
},
})
export default MyComponent;
You have to bind the context to your function.
constructor (props){
super(props);
this.state = {
val: false
}
this.start = this.start.bind(this)
}
or just you can an arrow function, without binding
start = () => {
this.setState({ val: true })
}
bind start method to the component other 'this' will be undefined for the start function
export default class App extends Component{
constructor (props){
super(props);
this.state = {
val: false
}
this.start = this.start.bind(this)
}
start() {
this.setState({ val: true })
}
render(){
return (
<View>
<Button
title='start'
onPress={this.start}>
</Button>
<MyComponent value={this.state.val}> </MyComponent>
</View>
);
}
}
You need to make start function to be binded through constructor or ES6 arrow function.
export default class App extends Component{
constructor (props){
super(props);
this.state = {
val: false
}
}
start = () => {
this.setState({ val: true })
}
render(){
return (
<View>
<Button
title='start'
onPress={this.start}>
</Button>
<MyComponent value={this.state.val}> </MyComponent>
</View>
);
}
}
You have to bind the method with this. Just add
this.start = this.start.bind(this)
after this.state in the constructor.
EDIT
And also try to move custom inside state in MyComponent like this:
this.state={
custum: {
backgroundColor: 'red'
}
}
and remove
this.state.custum={
backgroundColor: 'red'
}
As you can't just set state like this.

Cannot set multiple images

I am getting buffered image/jpeg from backend. Parent component does setState every time a new image is received and is passed down to Child component.
Child Component receives every update and can be seen in console logs, but it is only displaying single image.
Parent component:
class App extends Component {
constructor(props) {
super(props);
this.state = {
nodeResponse: ''
}
}
getFileStatusFromNode = (data) => {
this.setState({nodeResponse: data})
}
render() {
let CardView = nodeResponse !== '' &&
<Card key={nodeResponse.fileName} name={nodeResponse.fileName} image={nodeResponse.buffer} />
return (
<div className="App tc">
{ CardView }
</div>
)
}
}
Child component:
class Card extends PureComponent {
constructor({props}) {
super(props);
this.state = {
src: '',
};
}
componentDidMount() {
console.log("Card mounted")
this.setState(prevState => ({
src: [this.props.image, ...prevState.src]
}), () => console.log(this.state.src, this.props.name));
}
render() {
const { name } = this.props;
const { src } = this.state;
return (
<a style={{width: 200, height: 250}} key={name} className={'tc'} >
<div id='images'>
<img style={{width: 175, height: 175}} className='tc' alt='missing' src={`data:image/jpeg;base64, ${src}`}/>
</div>
</a>
)
}
}
NOTE: These images are coming from socket io. I want to display them in real time rather than creating a list first and then display together.
You are only rendering 1 <Card> when defining the <CardView> component.
Assuming nodeResponse.imageFiles is an array of files, you should have something like the following:
class App extends Component {
constructor(props) {
super(props);
this.state = {
nodeResponse: ''
}
}
getFileStatusFromNode = (data) => {
this.setState({nodeResponse: data})
}
render() {
let CardView
if(this.state.nodeResponse !== '') {
CardView = this.state.nodeResponse.imageFiles.map(file => (
<Card
key={image.fileName}
name={image.fileName}
image={image.buffer} />
)
)}
return (
<div className="App tc">
{ CardView }
</div>
)
}
}
Try with
class App extends Component {
constructor(props) {
super(props);
this.state = {
nodeResponse: []
}
}
getFileStatusFromNode = (data) => {
// here you merge the previous data with the new
const nodeResponse = [...this.state.nodeResponse, data];
this.setState({ nodeResponse: nodeResponse });
}
render() {
return (<div className="App tc">
{this.state.nodeResponse.map(n => <Card key={n.fileName} name={n.fileName} image={n.buffer} />)}
</div>);
}
}
and in your child component
class Card extends PureComponent {
render() {
const { src } = this.props;
return (<a style={{width: 200, height: 250}} className="tc">
<div id='images'>
<img style={{width: 175, height: 175}} className='tc' alt='missing' src={`data:image/jpeg;base64, ${src}`}/>
</div>
</a>);
}
}

Toggle div-elements in different component in React

I'm fairly new to React. I'm trying to build a site where you can click navigation item (in this case music genre) and it will list all the songs that belongs to that particular genre.
My app.js looks like this:
import React, { Component } from 'react';
import ListGenres from './ListGenres';
import './App.css';
class App extends Component {
constructor(props) {
super();
this.state = {dataList: props.dataList};
}
render() {
return (
<div>
<div className="App">
<Navigation tracks = {this.state.dataList} />
<ListGenres tracks = {this.state.dataList}/>
</div>
</div>
);
}
}
export default App;
I have a navigation component that looks like this:
import React from 'react';
import HeaderBar from './HeaderBar';
import MenuItem from 'material-ui/MenuItem';
export class Navigation extends React.Component {
constructor(props) {
super();
}
/*
onClickFunction() {
toggle elements in another component
}
*/
render() {
const genres = this.props.tracks.map((elem) => {
return elem.genre;
});
const filtered = genres.filter((elem, index, self) => {
return self.indexOf(elem) === index;
});
const genreLoop = filtered.map((elem, i) => {
return (
<MenuItem
onClick= {this.onClickFunction}
key={ i }><a>{ elem }</a>
</MenuItem>);
});
return (
<div>
{ genreLoop }
</div>
);
}
}
export default Navigation;
My list of items are rendered in another component whick looks like this:
import React from 'react';
import './ListGenres.css';
export class ListGenres extends React.Component {
constructor(props) {
super();
}
render() {
return (
<div className="genreList">
<div className="tracklist-visible tracklist-pop">
<ul>
<h3>Pop</h3>
{ this.tracklist('Pop') }
</ul>
</div>
<div className="tracklist-visible tracklist-metal">
<ul>
<h3>Pop</h3>
{ this.tracklist('Metal') }
</ul>
</div>
</div>
);
}
Is there way to maybe add css-class to tracklist-div when anchor is clicked from Navigation-component? Looks like I can't pass any props from that component since it's "stand-alone"-component?
You need to lift the state up.
Of course, you can solve this with Redux too, but let's keep it simple and only use React.
Lifting State Up
Create a component that will contains both <Navigation /> and <ListGenres /> components.
Keep the state (genre and selectedGenre) in this parent component and pass it down through props.
You also need to create a callback to handle genres changes.
Here's the example:
class App extends Component {
constructor (props) {
super(props)
this.state = {
selectedGenre: null,
genres: [...]
}
}
onGenreChange (genre) {
this.setState({ selectedGenre: genre })
}
render () {
return (
<div>
<Navigation
onGenreChange={genre => this.onGenreChange(genre)}
genres={this.state.genres}
/>
<ListGenres
genres={this.state.genres}
selectedGenre={this.state.genres}
/>
</div>
)
}
}
You didn't supply much code or example on how things should work but as i understand you are looking for a behavior similar to Tabs, where you click a Tab and a corresponding View is presented.
If this is the case, then you need a Parent component that will manage the selected Tabs and render the View respectively.
This is a simple example:
const tabs = ["Pop", "Rock", "Rap", "Electro"];
const View = ({name}) => <h1 className="view">{`This is ${name} music!`}</h1>
class Tab extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { id, onClick } = this.props;
onClick(id);
}
render() {
const { name, id, isSelected } = this.props;
const css = `tab ${isSelected && 'selected'}`;
return (
<div
className={css}
onClick={this.onClick}
>
{name}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 1
}
this.onTabChange = this.onTabChange.bind(this);
}
onTabChange(id) {
this.setState({ selectedTab: id });
}
render() {
const { selectedTab } = this.state;
return (
<div>
{
tabs.map((t, i) => {
return (
<div className="wrapper">
<Tab name={t} id={i + 1} isSelected={selectedTab === i + 1} onClick={this.onTabChange} />
{selectedTab == i + 1 && <View name={t} />}
</div>
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.tab {
display: inline-block;
margin: 0 10px;
width: 60px;
text-align: center;
cursor: pointer;
padding: 5px;
}
.selected {
border: 1px solid #eee;
box-shadow: 0 0 3px 1px #999;
}
.wrapper{
display:inline-block;
}
.view{
position: absolute;
top: 0;
left: 0;
margin-top: 50px;
text-align: center;
}
<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="root"></div>

React: Calling two event handlers onClick and changing style of child component

I have a parent class-based component A and a child functional component B. Inside B I map over a list of names and render them as li elements, which onClick call the onLanguageUpdate handler declared in the parent component, and what this handler does is update the state to reflect the selected name.
Question then:
I need to call a second event handler in the same onClick, this time to change the color of the name the user has clicked on. I added a new property to the state, color, to represent a className that I can then toggle with the handleStyleColorChange handler. But how do I get the li elements in the child component to update their className (or style) based on the result of this handler? If I was doing all of this inside component A's render method, I could do style={language === this.state.selectedLanguage ? {color: 'red'} : null} on the li and call it a day.
// Component A
import React, { Component } from 'react';
import B from './B';
class A extends Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: 'All',
color: 'lang-black-text'
};
}
handleUpdateLanguage = (language) => {
return this.setState({ selectedLanguage: language });
}
handleStyleColorChange = (language) => {
if (language === this.state.selectedLanguage) {
return this.setState({ color: 'lang-red-text' });
} else {
return this.setState({ color: 'lang-black-text' });
}
}
handleClick = (language) => {
this.handleUpdateLanguage(language);
this.handleStyleColorChange(language);
}
render() {
return (
<LanguageList onLanguageUpdate={this.handleClick} />
);
}
}
export default A;
// Component B
import React from 'react';
const B = (props) => {
const languages = ['English', 'Spanish', 'Japanese', 'Italian'];
const languageListFormatted = languages.map(language => {
return (
<li
key={language}
onClick={() => props.onLanguageUpdate(language)}>{language}
</li>
);
});
return (
<ul className="languages">{languageListFormatted}</ul>
);
}
export default B;
You can't manage the color from the parent comp, it needs to be done from the child comp. Then, send the selectedLanguage to the child and you are good.
class A extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: 'All',
color: 'lang-black-text'
};
}
handleUpdateLanguage = (language) => {
return this.setState({ selectedLanguage: language });
}
handleStyleColorChange = (language) => {
if (language === this.state.selectedLanguage) {
return this.setState({ color: 'lang-red-text' });
} else {
return this.setState({ color: 'lang-black-text' });
}
}
handleClick = (language) => {
this.handleUpdateLanguage(language);
this.handleStyleColorChange(language);
}
render() {
return (
<B
onLanguageUpdate={this.handleClick}
selectedLanguage={this.state.selectedLanguage}
/>
);
}
}
const B = (props) => {
const languages = ['English', 'Spanish', 'Japanese', 'Italian'];
const languageListFormatted = languages.map(language => {
return (
<li
key={language}
style={props.selectedLanguage === language ? {background: 'yellow'} : {}}
onClick={() => props.onLanguageUpdate(language)}>{language}
</li>
);
});
return (
<ul className="languages">{languageListFormatted}</ul>
);
}
ReactDOM.render(
<A />,
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>

Categories