-
-
Save 3stacks/7b16b0b6e1a1bda284b6ee068d39eb9f to your computer and use it in GitHub Desktop.
performance - how to avoid bindings in react's render function
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// I removed everything bar the component with the binds and hopefully I can just point you in the right direction. | |
// when we look at doctorFormBlocks.map, there's two issues. One being that you're creating a new function instance with your | |
// bind every render (which you correctly identified). But two, your map has an arrow function inside it which also results | |
// a new function instance every render! This would certainly add up. | |
// Below is the class as you had it before. The way I would do it is to just make your map an instance method on | |
// the component. | |
// Before | |
export default class DoctorsPage extends React.Component { | |
constructor(props) { | |
super(props) | |
this.state = { doctorFormBlocks: ['doctor_0'] } | |
} | |
render() { | |
return ( | |
<div className="form__section"> | |
{doctorFormBlocks.map((block, i) => ( | |
<DoctorFormBlock | |
form={form} | |
key={block} | |
blockId={block} | |
index={i + 1} | |
handleRemove={removeFormBlock.bind(this, block)} | |
/> | |
))} | |
<AddRemoveButton | |
typeToAdd="doctor" | |
handleAppend={appendFormBlock.bind(this, 'doctor')} | |
/> | |
</div> | |
) | |
} | |
} | |
// After | |
export default class DoctorsPage extends React.Component { | |
constructor(props) { | |
super(props) | |
this.state = { doctorFormBlocks: ['doctor_0'] } | |
} | |
appendFormBlock = () => { | |
appendFormBlock('doctor'); | |
} | |
renderFormBlock = (block, index) => { | |
// You still have a bind here, but it's pretty unavoidable. This way you have much less of a performance | |
// problem related to function bindings | |
return ( | |
<DoctorFormBlock | |
form={form} | |
key={block} | |
blockId={block} | |
index={index + 1} | |
handleRemove={removeFormBlock.bind(this, block)} | |
/> | |
); | |
} | |
render() { | |
return ( | |
<div className="form__section"> | |
{doctorFormBlocks.map(this.renderFormBlock)} | |
<AddRemoveButton | |
typeToAdd="doctor" | |
// Since you know that this is going to be the 'doctor' section, use an instance method. | |
handleAppend={this.appendFormBlock} | |
/> | |
</div> | |
) | |
} | |
} | |
// I will also say that in some cases this could count as premature optimisation. Think about your goals, | |
// are you trying to ship fast? If so, getting a working prototype out the door is more important | |
// than saving a few function allocations in your render cycle ;) | |
// There's some problems with the below utils though. | |
// Firstly, I wouldn't call them a util. In my mind, a util is given all the information it needs explicitly | |
// and it returns a predictable value. The behaviour of your util shouldn't change depending on where it's | |
// called. and at the moment this util would only work for react components, which makes it hard to test! | |
export function appendFormBlock(type) { | |
// New block ids created from the type, eg. 'dependant_1', 'authPerson_2', etc.. | |
const newBlock = `${type}_${this.state[`${type}FormBlocks`].length}`; | |
this.setState({ | |
[`${type}FormBlocks`]: this.state[`${type}FormBlocks`].concat([newBlock]), | |
}); | |
} | |
export function removeFormBlock(id) { | |
const type = id.split('_')[0]; | |
this.setState({ | |
[`${type}FormBlocks`]: this.state[`${type}FormBlocks`].filter(block => block !== id), | |
}); | |
} | |
// Let's look at `setState`. There's two possible usages for setState | |
// this.setState can take an object OR a function | |
this.setState({ | |
foo: 'bar' | |
}); | |
// After this, state will be whatever it was before, plus the new value of `state.foo` | |
this.setState(state => { | |
return { | |
// Make sure you return everything that was in state, otherwise they will become undefined | |
...state, | |
foo: 'bar' | |
}; | |
}); | |
// The functional way is good if you need to do some calculation or parsing based on a state property | |
this.setState(state => { | |
return { | |
...state, | |
// invert a bool, for instance | |
someProperty: !state.someProperty | |
}; | |
}); | |
// the REALLY COOL way to use these though, is with reducer util functions. | |
function fooReducer(state) { | |
return { | |
...state, | |
foo: 'bar' | |
}; | |
} | |
this.setState(fooReducer); | |
// state will be whatever state was, plus the new foo value | |
// to pre-apply arguments, you'll need to use bind or a util like lodash partial or lodash curry. | |
export function appendFormBlock(type, state) { | |
// New block ids created from the type, eg. 'dependant_1', 'authPerson_2', etc.. | |
const newBlock = `${type}_${this.state[`${type}FormBlocks`].length}`; | |
return { | |
...state, | |
[`${type}FormBlocks`]: this.state[`${type}FormBlocks`].concat([newBlock]) | |
}; | |
} | |
// Don't need to pass this at all! | |
this.setState(appendFormBlock.bind(null, 'doctor')); | |
// OR if you have lodash | |
import partial from 'lodash/partial'; | |
this.setState( | |
partial(appendFormBlock, 'doctor') | |
); | |
// Now, your util functions are reducers and all they do is take optional arguments and a state argument, and | |
// return a predictable object, allowing you to test it and pull it out into other projects. It also makes | |
// it language agnostic and you could use it for any MVC/MVVM frameworks like angular or vue, as long as | |
// you still have the concept of state :) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment