Let's take some trivial react component that have to render:
1) text field (managed one, connected to state)
2) list of tasks (map through array connected to state too)
3) button, that adds new item to array from text field, when clicked
https://stackblitz.com/edit/react-glpzqn?embed=1&file=index.js
when text field is updated(textChange event) => then state is updated => that force render(on each key press in text field).
it's unwanted render, I only want to render when button is clicked, item is added to array and finally new list is rendered on a screen.
so I want to force render only on item added to the list, and not when text is changed.
some solutions that I can reveal are:
1) take input to another component so text change will not affect list rendering.
2) change input field to unmanaged and retrieve text manually when button clicked.
I'm guessing if there is some more elegant solution without changing a component?
may be by using some HOC or same?
The simplest solution is using shouldComponentUpdate. Not on the entire component, but on the components you want not to render if props not relevant to them are changing:
class List extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.items !== this.props.items
}
render() {
// will only fire when the tasks array has changed
console.log('list rendered')
return this.props.items.map(item => (
<div key={item}>{item}</div>
))
}
}
This is just a shallow check too, which you get for free with PureComponent
class List extends PureComponent {
render() {
return this.props.items.map(item => (
<div key={item}>{item}</div>
))
}
}
All together:
import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';
class List extends PureComponent {
render() {
console.log('list rendered')
return this.props.items.map(item => (
<div key={item}>{item}</div>
))
}
}
class App extends Component {
// do not need constructor if already using class arrows
state = {
input: "",
tasks: []
};
addTask = () => {
this.setState({
tasks: [...this.state.tasks, this.state.input],
input: ""
});
}
setValue = event => {
this.setState({
input: event.target.value
});
}
render() {
return (
<div>
<List items={this.state.tasks} />
<input onChange={this.setValue} value={this.state.input} />
<button onClick={this.addTask}>Add</button>
</div>
);
}
}
render(<App />, document.getElementById('root'));
stackblitz solution
Just replace onChange with onBlur like below, the below change would only render as soon you leave the input.
<input onBlur={this.setValue} />
Related
I have a parent component housing two children components(AddPersonForm and PeopleList). When I submit a name via the AddPersonForm, I expect it to be rendered in the PeopleList component, but it doesn't.
Here is my AddPersonForm:
class AddPersonForm extends React.Component {
state = {
person: ""
}
handleChange = (e) => this.setState({person: e.target.value});
handleSubmit = (e) => {
if(this.state.person != '') {
this.props.parentMethod(this.state.person);
this.setState({person: ""});
}
e.preventDefault();
}
render() {
return (
<form onSubmit={this. handleSubmit}>
<input type="text" placeholder="Add new contact" onChange={this.handleChange} value={this.state.person} />
<button type="submit">Add</button>
</form>
);
}
My PeopleList component:
class PeopleList extends React.Component {
constructor(props) {
super(props);
const arr = this.props.data;
this.state = {
listItems: arr.map((val, index) => <li key={index}>{val}</li> );
}
}
render() {
return <ul>{this.state.listItems}</ul>;
}
}
Now the parent component, ContactManager:
class ContactManager extends React.Component {
state = {
contacts: this.props.data
}
addPerson = (name) => {
this.setState({contacts: [... this.state.contacts, name]});
render() {
return (
<div>
<AddPersonForm parentMethod={this. addPerson}×/>
<PeopleList data={this.state.contacts} />
</div>
);
Please what I'm I doing wrong, or not doing?
The issue is in your PeopleList component. The state object which renders your list is created in the constructor when the component mounts, but you have no way of updating it when it recieves new values. It will always give you the initial value.
You could introduce a lifecycle method, componentDidUpdate, which would allow you to compare the previous props to the new props when they arrive, and update the state accordingly. I would recommend you not do this for two reasons:
Storing props directly in a components state is not good practice. You are just creating a copy of the state in the component above and that creates opportunities for confusion and stale values when one of them updates. Ideally, each piece of data should live in only one place.
If all PeopleList is doing is rendering your data, then it doesn't need any state at all. It can act as a display component that maps your props in place and doesn't have to worry about updating itself or managing its own data. This would actually make it a good candidate for conversion into a functional component.
class PeopleList extends React.Component {
render() {
return (
<ul>
{this.props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
}
You are initializing PeopleList with props when its created and mounted but then you are not using new values of props for updating it.
To fix your issue use current value of prop when rendering:
class PeopleList extends React.Component {
render() {
return <ul>{ this.props.data.map((val, index) => <li key={index}>{val}</li>) }</ul>;
}
}
I'm new to React, and I'm trying to figure out how to adjust what appears in render based on a click event. My component receives two props "front" and "back". I want the component to display this.props.front upon rendering and change to this.props.back when the div is clicked. I'm having trouble figuring out how to accomplish this in my handleClick function.
Any help would be appreciated!
import React, { Component } from 'react';
class Card extends Component {
handleClick = event => {
}
render() {
return (
<div className="Card" onClick={this.handleClick}>
<h1>{this.props.front}</h1>
</div>
);
}
}
export default Card;
You could add a state to this component which is a boolean that toggles itself
class Card extends Component {
constructor(props) {
this.state = {
showFront: true
}
}...
And than use your handleClick method to switch the state back and forth
handleClick = (e) => {
this.setState({showFront: !this.state.showFront})
}
And in your render function you could put a conditional to show
render() {
return (
<div className="Card" onClick={this.handleClick}>
{
this.state.showFront
? <h1>{this.props.front}</h1>
: <h1>{this.props.back}</h1>
}
</div>
);
}
A comment to this answer was made but was deleted - i think it's a subject worth touching.
the comment said you should use the setState(updater()) and not pass an object.
it's true that when the app becomes more complex, you have several state updates together and data states may not be what you believe they are at that moment, updater function is apropriate (setState is async and could batch calls this is why we have the function that flushes all and helps us maintain state integrity comparing old states with new ones.
but for this answer and the complexity of the question an updater isn't necessary and the code should work just fine (and it gets to the point of using state and toggling which is the right way of doing what was asked).
you can use the updater function any time you please - even for the most simplest state change. And like said here, maybe it is best practice to just always use it :)
for more reference
React.Compoment setState & Updater function
In react you trigger render by changing the state of component. If this component needs to recieve props "front" and "back" then parent component should have saved in state if the state is "front" or "back" and pass down to component callback function to handle change. Something like:
import React, { Component } from 'react';
class ParentCard extends Component {
state = { isFront: true };
handleClick = event => {
this.setState({isFront: !this.state.isFront})
}
render = () => {
const { front } = this.state;
return (
<Card front={front} onClick={this.handleClick} />
);
};
export default ParentCard;
Also you can make Card component "pure" just by creating it as function which returns JSX.
import React from 'react';
const Card = ( { isFront, onClick } ) => {
return (
<div className="Card" onClick={onClick}>
<h1>{isFront ? `text if is front` : `text if it is not`}</h1>
</div>
);
}
}
export default Card;
Hope it helps :)
I'd say in that case you want to use state rather than props here, particularly when the state you want to change is being dictated by the component itself.
class Card extends Component {
state = {
mode: 'front' // default state to front
}
handleClick = () => this.setState({ mode: 'back' })
render() {
return (
<div className="Card" onClick={this.handleClick}>
<h1>{this.props.mode}</h1>
</div>
);
}
}
export default Card;
If this is really a toggle then of course you can use a Boolean flag instead, but you get the idea.
This component itself is currently not set up as a stateless functional component so if thats what you also wanted to achieve. Youll want to make these changes as well as pass props of a boolean in your stateful component.
import React from 'react';
const Card = (props) => {
return (
<div className="Card" onClick={props.handleClick}>
{props.showFront
?
<h1>props.front</h1>
:
<h1>props.back</h1>
}
</div>
);
}
export default Card;
you'll want to utilize the previous state to toggle your state because it could cause issues later down the road with batching, so your stateful component should look something like:
import React, {Component} from "React";
class StatefulCard extends Component {
state = {
showFront: true // default state to front
}
handleClick = () => {
this.setState(prevState => {
return {
showFront: !prevState.showFront
}
}
}
render() {
return (
<div>
<Card
handleClick={this.handleClick}
showFront={this.state.showFront}
/>
</div>
);
}
}
export default Card;
I've have an component "A" with a button. When the user press the button I'm showing a modal(react-responsive-modal) with bunch of filed and an update button. When the user presses the update button on the modal I want to reload the component "A" with the updated data.
I tried redirecting using this.props.history.push('dashboard/componentA') but it didn't work. Then I tried redirecting to the dashboard and again redirecting to the component like this
this.props.history.push('/dashboard');
this.props.history.push('/dashboard/componentA');
It worked but I'm not seeing any loader that I've used on 'componentWillMount' and the component just freezes up. I couldn't scroll up or down.
Try not to use the browser history as a way to update react (as much as you can). React is designed to re-render components when the props or state for that component change. As an example, this should trigger an update in ComponentA without needing to update the browser's history:
class ComponentA extends Component {
handleModalClick = (event) => {
this.setState({
componentData: event.data,
});
}
render() {
return (
<ReactModal onClick={this.handleClick} />
)
}
}
EDIT: Updated to show a data fetching parent component:
class DataFetcher extends Component {
saveAndFetchData = (saveData) => {
FetchDataPromise(saveData).then((updatedData) => {
this.setState({ data: updatedData });
}
}
render() {
const { data } = this.state;
return (
<div>
<ComponentA data={data} />
<ReactModalComponent handleClick={saveAndFetchData} />
</div>
);
}
}
class ComponentA extends Component {
render() {
const { data } = this.props;
return (
<div>
...render data...
</div>
)
}
}
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;
So I know that you can access a component's children with this.props.children:
<MyComponent>
<span>Bob</span>
<span>Sally</span>
</MyComponent>
Which is great if I'm interested in Bob and Sally, but what if I want to interact with the components that make up MyComponent (i.e. Subcomp1 and Subcomp2 shown below)?
render: function() {
return (
<div className="my-comp">
<Subcomp1 />
<Subcomp2 />
</div>
);
},
Use Case
I'm trying to create a higher order component that manages the tab index (roving tab index: https://www.w3.org/TR/wai-aria-practices/#kbd_roving_tabindex) of the wrapped component's sub-components, so it would be great if I could get a ref to the wrapped component and filter it's subcomponents by type.
So far the only approach that seems possible is to have each component store a ref for each of it's subcomponents, but this is tedious and kind of defeats the purpose of an HOC. Is there a generic way to access these sub-components?
A rough example of what I'm trying to do:
var HOC = (ComposedComponent) => {
return React.createClass({
componentDidMount: function() {
const subComponents = this.composedComponent.subComponents; // Something like this would be nice
const menuItems = subComponents.filter(() => {
// figure out a way to identify components of a certain type
});
this.applyRovingTabIndex(menuItems);
},
render: function() {
return (
<ComposedComponent
ref={(c) => { this.composedComponent = c }}
{...this.props} />
);
}
});
};
The tabIndex manipulation need not be done in the HOC, rather it can be done in the Parent component that renders all the HOCs. Because all you need is to determine which sub component is clicked and adjust the selected state on the Parent component. This selected state can then be propagated back to the sub components who compare their index with selected index and assign tabIndex accordingly.
You can send the respective props to determine whether the current ComposedComponent is selected or not by passing an onClick event handler all the way. Then in your sub component you can access tabIndex using this.props.tabIndex and render your parent div as
<div tabIndex={this.props.tabIndex}> </div>
The code below is almost like pseudo code to give an idea. If you feel that this does not solve your requirement you can try out a Tab example worked out by an awesome developer at this link CODEPEN EXAMPLE
const HOC = (ComposedComponent) => {
return class extends React.Component {
render (
<ComposedComponent
tabIndex={this.props.selected === this.props.index ? "0" : "-1"}
{...this.props}
/>
)
}
}
class Parent extends React.Component {
state = {
selected: 0
}
// Set the current selection based on the currentSelection argument
// that is bound to the function as it is sent along to Props
adjustTabIndices = (currentSelection) => (event) => {
this.setState({selection: currentSelection})
}
render {
return (
<div>
{
// These are your various MenuItem components that
// you want to compose using HOC
[MenuItem1, MenuItem2, MenuItem3].map(index => {
const MenuItem = HOC(MenuItem1);
return (
<MenuItem
key={index}
onClick={this.adjustTabIndices(index)}
selection={this.state.selected}
index={index}
/>
)
})
}
</div>
)
}
}