Someone please explain me this last paragraph of the React documentation with a simple example, it's about React.PureComponent , all the examples I've seen are advanced and I'm just starting to know this concept and I can't see exactly what it refers to. Precisely the point that children should also be "pure".
To the best of my knowledge, I believe that this statement is over-stated, because if the father does not re-render himself, then neither do the children, or, is there something that escapes me and I cannot visualize it? That is why I need a simple example, I already looked at all the similar questions here but they are advanced examples and do not cover what I am looking for.
"Furthermore, React.PureComponent's shouldComponentUpdate() skips prop updates for the whole component subtree. Make sure all the children components are also "pure""
I made something to play with.
I think the important part to realize, is that there is only one difference, it is the shouldComponentUpdate implementation on PureComponent.
I am also confused about Make sure all the children components are also “pure”. From my understanding, it is meant as a warning, as a guideline, not as an absolute don't. When you put a parent as PureComponent, all the childrens become impacted. But someone not used to shouldComponentUpdate (PureComponent) may be surprised by childrens behavior (not updating) when mixing both in the tree.
Take for example in the second snippet this.state.quotes[0].text = "StackOverflow save the day";. When using Component on both <List/> and <ListElement/>, any setState would update the view. But when using PureComponent on only <List/>, someone may expect <ListElement/> to update, but it won't.
Imagine that, that instead of writing <List/> as pure, and <ListElement/> as unpure, what they warn NOT to do. You would have written both as pure. Would you even think of doing something like this.state.quotes[0].text = "StackOverflow save the day"; ? No you would start with this.state.quotes[0] = {...}. And then you would take a look at which component handle the data above, the quotes array, the component handling the array, is it pure ? Yes it is, then write this.state = [{...}] instead. You do this "recreating references/deep copy" until you find the first non-pure component. If you followed their advice, at the first non-pure component, you know there won't be any other stale reference blocking your rendering.
If using deep comparison, you would never have that issue. I am using hooks components, they are much more friendly. I recommend you skip class component and use hooks. In hooks to implement shouldComponentUpdate you use React.memo. Since YOU decide how you compare props, it becomes quite obvious that if you compare previousList === newList (shallow comparison), you will miss any update/add/delete on the list. And naturally you will do a deep comparison (use deep-equal lib or (avoid) JSON.stringify(previousList) === JSON.stringify(newList). Note that React.Memo is better for another reason, it only handle props comparison, not both state and props comparison as PureComponent.
I don't think you need every children to use shallow comparison, if the component render fast enough, using shouldComponentUpdate (or React.memo) can make it worse (remember it is a performance optimization).
This bellow demonstrate the issues we can get with shallow props comparison (first snippet) and shallow state comparison (second snippet):
import { Component, PureComponent } from 'react';
interface Quote {
author: string;
text: string;
}
export class Playground extends Component {
state = {
quotes: [
{
author: 'Ambroise',
text: 'I like React.'
}
]
}
onClickOK () {
this.setState({
quotes: [
{
author: 'Ariel',
text: 'Prefer VueJS.'
}
]
});
}
// https://reactjs.org/docs/react-api.html#reactpurecomponent
// React.PureComponent implements it with a shallow prop and state comparison
// "prop comparison"
onClickIssue() {
if (window['dontMix']) {
alert("Please don't mix on click issues. Reload page.")
return;
}
window['dontMix'] = true;
// This won't work if <List/> is a PureComponent
// Note that we ALSO CHANGED the props for <ListElement/> here, different object, differents strings,
// but <ListElement/> will NOT re-render, regardless of <ListElement/> being pure or not.
const quotesUpdated = this.state.quotes;
quotesUpdated.pop();
quotesUpdated.push({
author: 'Thomas',
text: 'Prefer Angular.'
});
// Shallow props comparison, <List/> will compare array references
// `quotesUpdated === this.state.quotes` and because it is true,
// it will not re-render, or any of the childrens.
//
// The quote object is different, so <ListElement/>
// would be expected to re-render.
this.setState({
quotes: quotesUpdated
});
}
onClickIssue2() {
if (window['dontMix']) {
alert("Please don't mix on click issues. Reload page.")
return;
}
window['dontMix'] = true;
// This won't work if <ListElement/> is a PureComponent since the object in first index stay the same.
// Since we recreate the array, <List/> will always re-render regardless of <List/> being pure.
this.state.quotes[0].author = "Thomas";
this.state.quotes[0].text = "Prefer Angular.";
this.setState({
quotes: [
this.state.quotes[0],
]
});
}
render() {
return (
<section>
<button onClick={this.onClickOK.bind(this)}>Get updated</button>
<button onClick={this.onClickIssue.bind(this)}>Problem</button>
<button onClick={this.onClickIssue2.bind(this)}>Problem2</button>
<List quotes={this.state.quotes}/>
</section>
);
}
}
// Change PureComponent to Component, no problem with shallow comparison anymore.
class List extends PureComponent<{ quotes: Quote[]; }>{
render() {
return (
<ul>
{
this.props.quotes.map(
(e, i) => <ListElement key={i} quote={e}/>
)
}
</ul>
);
}
}
class ListElement extends PureComponent<{quote: Quote}> {
render() {
return (
<li>
<h3>{this.props.quote.author}</h3>
<p>{this.props.quote.text}</p>
</li>
);
}
}
A second snippet, focusing on the state shallow comparison, and also <ListElement/> start as a Component this time instead of a PureComponent.
import { Component, PureComponent } from 'react';
interface Quote {
author: string;
text: string;
}
export class Playground2 extends Component {
state = {
quotes: [
{
author: 'Ambroise',
text: 'I like React.'
}
]
};
render() {
return (
<section>
<List quotes={this.state.quotes}/>
</section>
);
}
}
// Note: Careful not to confuse state.quotes and props.quote
class List extends PureComponent<{ quotes: Quote[]; }> {
state = {
quotes: this.props.quotes
};
// https://reactjs.org/docs/react-api.html#reactpurecomponent
// React.PureComponent implements it with a shallow prop and state comparison
// "state comparison"
fail() {
for (let i = 0; i < this.state.quotes.length; i++) {
this.state.quotes.pop();
}
this.setState({
quotes: this.state.quotes
});
}
success() {
// This will never work, because `previousState === newState` (both are this.state)
// this.state.quotes = [];
// this.setState(this.state);
this.setState({
quotes: []
});
}
// It work if you change this class to Component
changeChild() {
// if you clicked another button reload the page to get initial state.
if (this.state.quotes.length === 0) {
alert("You clicked another button. Please reload page to test this one.");
}
// previously "I like React."
this.state.quotes[0].text = "StackOverflow save the day";
// NOTICE: Choose only one setState bellow, comment the others.
// Won't work
// this.setState({
// quotes: this.state.quotes
// });
// This will never work, because `previousState === newState` (both are this.state)
// this.state.quotes = [this.state.quotes[0]];
// this.setState(this.state);
// Won't work
this.setState(this.state);
// This will work when <List/> is a PureComponent and <ListElement/> a Component,
// both this.state and this.state.quotes are different
// this.setState({
// quotes: [this.state.quotes[0]]
// });
}
render() {
return (
<div>
<button onClick={this.fail.bind(this)}>Empty the list (FAIL if PureComponent)</button>
<button onClick={this.success.bind(this)}>Empty the list (SUCCESS)</button>
<button onClick={this.changeChild.bind(this)}>Change child (FAIL if PureComponent)</button>
<ul>
{
this.state.quotes.map(
(e, i) => <ListElement key={i} quote={e}/>
)
}
</ul>
</div>
);
}
}
// ATTENTION: This one start as a Component this time, instead of a PureComponent
class ListElement extends Component<{quote: Quote}, {author: string}> {
state = {
author: this.props.quote.author,
};
// Yep this work.
takeOwnership() {
this.setState({
author: "Constantin"
})
}
render() {
return (
<li>
<button onClick={this.takeOwnership.bind(this)}>Take ownership !</button>
<h3>{this.state.author}</h3>
<p>{this.props.quote.text}</p>
</li>
);
}
}
TLDR; I also find it confusing, it seems more like and advice, I recommend not putting too much attention on it, and to use hooks components, where you have more fined grained control, over props/state comparison and shallow/deep comparison.
Related
First of all, I'm really new into React, so forgive my lack of knowledge about the subject.
As far as I know, when you setState a new value, it renders again the view (or parts of it that needs re-render).
I've got something like this, and I would like to know if it's a good practice or not, how could I solve this kind of issues to improve, etc.
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
key: value
}
this.functionRender = this.functionRender.bind(this)
this.changeValue = this.changeValue.bind(this)
}
functionRender = () => {
if(someParams !== null) {
return <AnotherComponent param={this.state.key} />
}
else {
return "<span>Loading</span>"
}
}
changeValue = (newValue) => {
this.setState({
key: newValue
})
}
render() {
return (<div>... {this.functionRender()} ... <span onClick={() => this.changeValue(otherValue)}>Click me</span></div>)
}
}
Another component
class AnotherComponent extends Component {
constructor (props) {
super(props)
}
render () {
return (
if (this.props.param === someOptions) {
return <div>Options 1</div>
} else {
return <div>Options 2</div>
}
)
}
}
The intention of the code is that when I click on the span it will change the key of the state, and then the component <AnotherComponent /> should change because of its parameter.
I assured that when I make the setState, on the callback I throw a console log with the new value, and it's setted correctly, but the AnotherComponent doesn't updates, because depending on the param given it shows one thing or another.
Maybe I need to use some lifecycle of the MyComponent?
Edit
I found that the param that AnotherComponent is receiving it does not changes, it's always the same one.
I would suggest that you'll first test it in the parent using a simple console.log on your changeValue function:
changeValue = (newValue) => {
console.log('newValue before', newValue);
this.setState({
key: newValue
}, ()=> console.log('newValue after', this.state.key))
}
setState can accept a callback that will be invoked after the state actually changed (remember that setState is async).
Since we can't see the entire component it's hard to understand what actually goes on there.
I suspect that the newValue parameter is always the same but i can't be sure.
It seems like you're missing the props in AnotherComponent's constructor. it should be:
constructor (props) {
super(props) // here
}
Try replacing the if statement with:
{this.props.param === someOptions? <div>Options 1</div>: <div>Options 2</div>}
also add this function to see if the new props actually get to the component:
componentWillReceiveProps(newProps){
console.log(newProps);
}
and check for the type of param and someOptions since you're (rightfully) using the === comparison.
First, fat arrow ( => ) autobind methods so you do not need to bind it in the constructor, second re-renders occur if you change the key of the component.
Ref: https://reactjs.org/docs/lists-and-keys.html#keys
Currently I get my data from an API in a JSON-format when running my saga. The fetching process begins, when the component did mount. That means the component renders two times.
Now, when the data is available as props. I can use it in order to render it.
My approach to this is like following, I have got a:
Constructor with the initial state
I fetch data in "componentDidMount"
I got a function that takes the JSON properties from props and puts it into new variables
I run this function in my render() function, when the props contain the fetched data
The Problem in this approach: Once the component runs the function where the data becomes "structured", the render-function loops and then after some time, the values of the properties get displayed with a warning message in the console.
My Questions:
How to prevent the looping when render() runs once?
How can I design this, so that particular properties of the fetched object merge into a new object and how to
I hope I described the most important things about my issue. Here is the code:
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
deviceInfo: {
name: "Initial Name",
batLevel: "78%",
}
}
}
componentDidMount() {
this.props.requestApiData();
}
updateDeviceInfoWithState (){
const devices = (this.props.data.data);
if(devices){
const newDeviceInfo = this.state.deviceInfo;
newDeviceInfo.name = devices[0].shadow.desired.payload.refAppData.name;
newDeviceInfo.batLevel = devices[0].shadow.reported.payload.refAppData.batteryState.level;
this.setState({
deviceInfo: newDeviceInfo,
});
}
}
render() {
this.updateDeviceInfoWithState()
return (
<div className='container'>
<p> {this.state.deviceInfo.name} </p>
<p> {this.state.deviceInfo.batLevel} </p>
</div>
)
}...
Updating the state in the render method is not a good practice, since it might cause an infinite loop.
In your case state is redundant, since you only take the data from props, or replace it with defaults. Instead of using the state return the name and batLevel in the updateDeviceInfoWithState method, and use it in the render method.
Example (not tested):
class Dashboard extends React.Component {
componentDidMount() {
this.props.requestApiData();
}
updateDeviceInfoWithState (){
const devices = this.props.data.data;
if(devices){
const device = devices[0].shadow;
return {
name: device.desired.payload.refAppData.name,
batLevel: device.reported.payload.refAppData.batteryState.level
};
}
return {
name: "Initial Name",
batLevel: "78%",
};
}
render() {
const { name, batLevel } = this.updateDeviceInfoWithState();
return (
<div className='container'>
<p> {name} </p>
<p> {batLevel} </p>
</div>
);
}...
Note 1: If you want to decouple your component from the state, it's better to enforce simple properties as input for the data. For example, this component needs as properties the name and batLevel. It doesn't need to be aware of the array of devices, shadow, payload, etc... You can prepare the data when you receive it in the saga, or use a redux selector in mapStateToProps.
Note 2: If you really need the data in your state, you can use the getDerivedStateFromProps life-cycle method (React 16.3), or update the state in the componentWillReceiveProps if you use an older version.
For this case you can use ComponentWillRecieveProps method like this
componentWillRecieveProps(nextProps) {
// Condition as per ur requirement.
If(this.props.data != nextProps.data) {
this.updateDeviceInfoWithState(nextProps)
}
}
This method will only run whenever ur component props are changed.
I have a react component, which has properties and state. Some fields of state contain input data (uplifted from input control), but there is also fields in the state that must be Calculated based on current State and Props:
The question: what is the best way to update calculated fields of the state (based on other fields of state and props)?
Ugly way to do it:
componentDidUpdate(){
this.setState({calculatedField:calculate(this.props,this.state)}))
}
In this case I get infinite loop of updates or in the best case (if I use PureComponent) double rendering invocation.
The best solution I found so far (but still ugly):
Is to create a calculated object in state, which contains calculated fields and updated in componentWillUpdate avoiding setState:
componentWillUpdate(nextProps,nextState){
nextState.calculated.field1=f(nextProps,nextState)
}
class ParentComponent extends React.Component {
constructor(props, ctx) {
super(props,ctx)
this.state={A:"2"}
}
render() {
console.log("rendering ParentComponent")
return <div>
<label>A=<input value={this.state.A} onChange={e=>{this.setState({A:e.target.value})}} /></label> (stored in state of Parent component)
<ChildComponent A={this.state.A} />
</div>
}
}
class ChildComponent extends React.PureComponent {
constructor(props,ctx) {
super(props,ctx);
this.state={
B:"3",
Calculated:{}
}
}
render() {
console.log("rendering ChildComponent")
return <div>
<label>B=<input value={this.state.B} onChange={e=>{this.setState({B:e.target.value})}} /></label> (stored in state of Child component state)
<div>
f(A,B)=<b>{this.state.Calculated.result||""}</b>(stored in state of Child component)
<button onClick={e=>{ this.setState({Calculated:{result:new Date().toTimeString()}}) }}>Set manual value</button>
</div>
</div>
}
componentWillUpdate(nextProps, nextState) {
this.state.Calculated.result = getCalculatedResult(nextProps.A, nextState.B)
}
componentWillReceiveProps(nextProps) {
this.state.Calculated.result = getCalculatedResult(nextProps.A, this.state.B)
}
componentWillMount() {
this.state.Calculated.result = getCalculatedResult(this.props.A, this.state.B)
}
}
function getCalculatedResult(a,b) {
const aNum = Number(a)||0
const bNum = Number(b)||0;
const result = (aNum*bNum).toString();
return result;
}
ReactDOM.render(<ParentComponent/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>
This is also ugly solution and React does not recommended to mutate state avoiding setState. So what is right solution for that?
NOTE:
In my real application I cannot recalculate f(a,b) every single time during rendering, because it's actually complex object, so I need to cache it somehow and the best way is in the state.
If you are using React 16.8.0 and above, you can use React hooks API. I think it's useMemo() hook you might need. For example:
import React, { useMemo } from 'react'
const MyComponent = ({ ...props }) => {
const calculatedValue = useMemo(
() => {
// Do expensive calculation and return.
},
[a, b]
)
return (
<div>
{ calculatedValue }
</div>
)
}
For more details, refer to the React documentation
I wouldn't advice you to store your calculated value inside your state. My approach would be more like this:
import PropTypes from 'prop-types';
import React from 'react';
class Component extends React.Component {
static defaultProps = { value: 0 };
static propTypes = { value: PropTypes.number };
state = { a: 0, b: 0 };
result = () => this.state.a + this.state.b + this.props.value;
updateA = e => this.setState({ a: +e.target.value });
updateB = e => this.setState({ b: +e.target.value });
render() {
return (
<div>
A: <input onChange={this.updateA} value={this.state.a} />
B: <input onChange={this.updateB} value={this.state.b} />
Result: {this.result()}
</div>
);
}
}
The problem with storing the calculation inside your state is, that the calculation can be mutated by multiple sources. If you use my solution, there is no way, that anything can overwrite the calculation WITHOUT using the correct function to calculate them.
You can save calculated result in this.calculated instead of this.state. It is dependent data. All data which causes update and render is already in state and props.
class Component extends React.Component {
constructor(props) {
super(props)
state = {
b: 0
}
}
updateThis = (event) => {
this.setState({ b: event.target.value });
}
componentWillUpdate(nextProps,nextState){
this.calculated.field1=f(nextProps.a, nextState.b)
}
render() {
return (
<form>
A = <input onChange={this.props.updateParent} value={this.props.a} /> <br>
B = <input onChange={this.updateThis} value={this.state.b} /> <br>
f(A,B) = {this.calculated.field1} <br>
</form>
);
}
}
class ParentComponent extends React.Component {
constructor(props) {
super(props)
state = {
a: 0
}
}
render() {
return (
<Component
updateParent={event=>this.setState({a: event.target.value})}
a={this.state.a}
/>
}
}
}
You're first attempt is the right way to solve this problem. However, you need to add a check to see if state has actually changed:
componentDidUpdate(prevProps, prevState){
if(prevState.field !== this.state.field){
this.setState({calculatedField:calculate(this.props,this.state)}))
}
}
shouldComponentUpdate(nextProps, nextState) {
return this.state.calculatedField !== nextState.calculatedField
}
You need to check the pieces of state and props that you use in your calculate method and make sure they have changed before updating state again. This will prevent the infinite loop.
It looks like the "state" is the place to store everything (even computed values) you'll need to use on the render function, but usually we have the problem you describe.
Since React 16.3 a new approach for this situation has been given in the way of the static getDerivedStateFromProps (nextProps, prevState) "lifecycle hook".
You should update at least to this version if you haven't, and follow the advice given by the React Team on their blog.
Here is the official documentation for this functionality.
The issue here is that this function is invoked before every render, and being "static" you cannot access the current instance previous props, which is usually needed to decide if the computed value must be generated again or not (I suppose this is your case, as you have stated your computation process is heavy). In this case, the React team suggests to store in the state the values of the related props, so they can be compared with the new ones:
if (nextProps.propToCompute !== prevState.propToComputePrevValue) {
return {
computedValue: Compute(nextProp.propToCompute),
propToComputePrevValue: nextProps.propToCompute
};
}
return null;
Do not include redundant information in your state.
A simplified example is having firstName and lastName in your state. If we want to display the full name in your render method, you would simply do:
render() {
return <span>{`${this.state.firstName} ${this.state.lastName}`}</span>
}
I like this example because it's easy to see that adding a fullName in our state, that just holds ${this.state.firstName} ${this.state.lastName} is unnecessary. We do string concatenation every time our component renders, and we're okay with that because it's a cheap operation.
In your example, your calculation is cheap so you should do it in the render method as well.
I have the following scenario:
1) There is a parent component "ModuleListContainer".
2) A module (in the module list, also a child component, but not interesting in this context) gets selected when hovering over it a module item in the list.
3) When hovering over a module, a menu should be shown in the corner of the module.
4) The whole parent component should NOT be updated when a module is selected, since it can be quite a long list of modules, that is why I set shouldComponentUpdate = false when updating which module should be selected.
5) The menu is loaded when the parent component loads, and only its position is updated when hovering over a module.
This is the parent component (simplified)...
class ModuleListContainer extends Component {
constructor(props) {
super(props);
this.state = {
selectingModule: false,
currentlySelectedModule: nextProps.currentModule
}
}
shouldComponentUpdate(nextProps, nextState) {
if (nextState.selectingModule === true) {
this.setState({
selectingModule: false,
currentlySelectedModule: null
})
return false;
}
return true;
}
mouseEnterModule = (e, moduleItem) => {
const menu = document.getElementById('StickyMenu');
const menuPosition = calculateModuleMenuPosition(e.currentTarget);
if (moduleItem.ModuleId !== this.props.currentModuleId) {
this.props.actions.selectModule(moduleItem);
this.setState({
selectingModule: true
});
}
menu.style.top = menuPosition.topPos + 'px';
menu.style.left = menuPosition.leftPos + 'px';
}
render() {
return (
<div>
<section id="module-listing">
{/* ... list of mapped modules with mouseEnterModule event */}
</section>
<ModuleMenu {... this.props} currentlySelectedModule={this.state.currentlySelectedModule} />
</div>
);
}
}
This is the menu component (simplified)...
class ModuleMenu extends Component {
constructor(props) {
super(props);
this.state = {
currentModule: this.props.currentlySelectedModule
};
}
clickMenuButton = () => {
console.log('CURRENT MODULE', this.state.currentModule);
}
render() {
return (
<div id="StickyMenu">
<button type="button" onClick={this.clickMenuButton}>
<span className="fa fa-pencil"></span>
</button>
</div>
);
}
}
When, in my menu component, I try to console.log the current module from the state, I keep getting null.
My question is if this is because...
I have set the shouldComponentUpdate to false and the menu's state does not get updated?
Or could it be because I do not re-render the whole component?
Or is it because I load the menu together with the parent component
and it does not get re-rendered when a module is selected?
Or is it possibly a combination of some of the above?
The react docs (https://reactjs.org/docs/react-component.html) says:
Returning false does not prevent child components from re-rendering
when their state changes.
Therefore, I am hoping that it is none of the above since I really don't want to have to re-render the entire component when selecting a module.
Your children state doesn't change in this case, you're only changing the state of the parent. What you should probably do is split the render method of your component into two components:
render() {
return (
<div>
<NoUpdateComponent someProps={this.props.someProps}/>
<ModuleMenu {... this.props} currentlySelectedModule={this.state.currentlySelectedModule} />
</div>
);
}
And then in your first costly component, use the shouldComponentUpdate method to prevent it from re rendering
I think that in your code there are other problems you need to solve before looking for a practical solution, starting from the use you make of shouldComponentUpdate().
Official doc says that:
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.
If you perform a setState() call inside the shouldComponentUpdate() function it might work but essentially you are telling React to start a new render cycle before knowing if in this execution it should render or not.
Also keep in mind that setState() is not guaranteed to be executed immediately:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Moreover (not very clear from the code, so I guess) you are separating Component and DOM object for ModuleMenu: its representation must be guided by state or props, here instead you are using HTMLElement.style.x syntax to set its representation properties.
I'd restructure ModuleListContainer to store in its state an object that represents ModuleMenu properties, that will be passed to ModuleMenu component as props, something like this:
moduleMenu {
currentModuleId: ... ,
top: ... ,
left: ...
}
And set the state in mouseEnterModule handler:
mouseEnterModule = (e, moduleItem) => {
const menuPosition = calculateModuleMenuPosition(e.currentTarget);
if (moduleItem.ModuleId !== this.props.currentModuleId) {
this.props.actions.selectModule(moduleItem);
this.setState({
moduleMenu: {
currentModuleId: moduleItem.ModuleId,
left: menuPosition.leftPos + 'px',
top: menuPosition.topPos + 'px'
}
});
}
}
Then ModuleMenu can get the new position like this:
<div id="StickyMenu">
style={{
left: this.props.left,
top: this.props.top
}}
>
...
</div>
Of course you can still use shouldComponentUpdate() to determine which modules in the list should be updated but this time returning just a boolean after a comparison of (once again, I guess) ids of items; avoiding too many calls to setState() method.
Hope this may help you!
I have some problem with splice() method in my React.js app.
So, this is an example app. Deletion not works now. What's wrong here? Part of code:
class CardList extends React.Component {
static propTypes = {
students: React.PropTypes.array.isRequired
};
// ADD DELETE FUNCTION
deletePerson(person) {
this.props.students.splice(this.props.students.indexOf(person), 1)
this.setState()
}
render() {
let that = this
return <div id='list'>
{this.props.students.map((person) => {
return <Card
onClick={that.deletePerson.bind(null, person)}
name={person.name}>
</Card>
})}
</div>
}
}
class Card extends React.Component {
render() {
return <div className='card'>
<p>{this.props.name}</p>
{/* ADD DELETE BUTTON */}
<button onClick={this.props.onClick}>Delete</button>
</div>
}
}
http://codepen.io/azat-io/pen/Vaxyjv
Your problem is that when you call
onClick={that.deletePerson.bind(null, person)}
You bind this value to null. So inside of your deletePerson function this is null instead of actual component. You should change it to
onClick={that.deletePerson.bind(this, person)}
And everything would work as expected =)
Changing the bind value to this will definitely cause the call to this.setState() to work, thus triggering the re-render, however I strongly recommend against the approach you've taken.
Props are supposed to be immutable. Instead use internal state and replace with new values rather than mutate them. To do this, set the state of your component in the constructor by doing something like:
constructor(props) {
super(props)
this.state = {
students: ...this.props.students
}
}
And now when you need to delete a person:
deletePerson(person) {
// notice the use of slice vs splice
var newStudents = this.props.students.slice(this.props.students.indexOf(person), 1)
this.setState({ students: newStudents })
}
And finally use this.state.students in your render method instead.
The reasoning behind this is that props are passed directly from the parent container component so modifying them wouldn't really make sense. To make more sense of my own code, I tend to pass in the prop named initialStudents and set my state to students: ...initialStudents to ensure I make the distinction between my prop variable and my state variable.