Skip to content

Instantly share code, notes, and snippets.

@3stacks
Forked from duyenho/doctors.js
Last active March 5, 2018 10:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 3stacks/7b16b0b6e1a1bda284b6ee068d39eb9f to your computer and use it in GitHub Desktop.
Save 3stacks/7b16b0b6e1a1bda284b6ee068d39eb9f to your computer and use it in GitHub Desktop.
performance - how to avoid bindings in react's render function
// 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