Last active
May 20, 2018 09:21
-
-
Save tokland/825e25546dffd31f2e223e18b6d78e69 to your computer and use it in GitHub Desktop.
Avoid setState in stateful React components
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
/* | |
React components are quasi-functional objects. The least functional element of the pack | |
is probably `setState`, a method that works only with side-effects. | |
This simple `action` decorator allows you to write pure functions in state transition method. It | |
works with normal values and promises. | |
Setup: | |
$ yarn add --dev babel-plugin-transform-decorators-legacy | |
$ echo '{"presets": ["es2015", "react"], "plugins": ["transform-decorators-legacy"]}' > .babelrc: | |
*/ | |
function isPromise(obj) { | |
return !!obj && typeof obj.then === 'function'; | |
} | |
export function action(target, name, descriptor) { | |
const method = descriptor.value; | |
descriptor.value = function (...args) { | |
const result = method.apply(this, args); | |
if (isPromise(result)) { | |
return result.then(newState => this.setState(newState)); | |
} else { | |
return this.setState(result); | |
} | |
} | |
} |
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
/* Simple counter example. Decorate any method that returns new state, including | |
lifecycle component methods. */ | |
import React from 'react'; | |
import { action } from './action'; | |
class Counter extends React.PureComponent { | |
constructor(props) { | |
super(props); | |
this.state = { value : 0 }; | |
} | |
@action | |
componentDidMount() { | |
return { value: 1 }; | |
} | |
@action | |
increment() { | |
const newValue = this.state.value + 1; | |
return { value: newValue }; | |
} | |
@action | |
async asyncIncrement() { | |
const newValue = await Promise.resolve(this.state.value + 1); | |
return { value: newValue }; | |
} | |
render() { | |
return ( | |
<div> | |
<p>Value: {this.state.value}</p> | |
<button onClick={this.increment.bind(this)}>Increment</button> | |
<button onClick={this.asyncIncrement.bind(this)}>Increment (async)</button> | |
</div> | |
); | |
} | |
} | |
export default Counter; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment