react-unify 💍
Unify state and props, decouple render() and handle state synchronously
Simple Examples
decorators and class field initialization
Withimport * as React from "react";import Render prop state from 'react-unify'; // decoupled stateless component render()@Component @prop amount = 1; // gets this.props.amount, sets defaultProps.amount @state count = 0; // access to this.state.count in a synchronous way { thiscount += thisamount; // this.count is updated synchronously // calls this.setState({count: this.state.count + this.props.amount}) // (this.state.count will be updated asynchronously) }
Optionally extract stateless render
// CounterRender.jsxconst CounterRender = <div> <p>Count: countercount</p> <button => Increment by counteramount </button> </div>;
Without decorators
import * as React from "react";import Render prop state from 'react-unify';import CounterRender from './CounterRender'; Component amount = 1; count = 0; { thiscount += thisamount; } // manually applying decoratorsCounter;;;
Testing
; ;
Live Demos
Installation
npm install react-unify
TypeScript
Enable decorators in tsconfig.json
, class field initialization is enabled by default.
Babel
Enable decorators and class field initialization with plugins.
Unfortunately transform-decorators-legacy does not support replacing class properties with a getter/setter. You need to use my version of it or go without decorators for now.
npm install osi-oswald/babel-plugin-transform-decorators-legacy.git#v1.4.2npm install babel-plugin-transform-class-properties
Add plugins to .babelrc
file, NOTE: Order of Plugins Matters!
create-react-app
Unfortunately decorators are not supported at the moment. You would need to eject and follow the Babel instructions, or go without decorators for now.
Class field initialization is enabled by default.
API
@Render
Assigns this.render
(and MyComponent.Render
) to a stateless render function
with the component instance as input.
@Component /* ... */ // set by @Render { return ; } // set by @Render static Render = MyComponentRender;
@state
Elevate this.state.someState
to this.someState
and access it synchronously.
Will call this.setState()
for you to update this.state.someState
and trigger a rerender.
Changes to this.state.someState
from other sources (manual this.setState()
/ getDerivedStateFromProps()
/ mutating this.state
)
will be synchronized back to this.someState
before shouldComponentUpdate()
or on forceUpdate()
respectively.
Component @state myPrimitive = 'myInitialState'; @state myObject = name: 'Alice' age: 30; @state myArray = 0 1 2; { thismyPrimitive = 'newValue'; } { // update objects in an immutable way thismyObject = ...thismyObject name: 'Bob'; // avoid mutating objects directly // -> will not trigger setState() and therefore not rerender thismyObjectname = 'Bob'; } { // update arrays in an immutable way // see https://vincent.billey.me/pure-javascript-immutable-array/ thismyArray = ...thismyArray 3; // avoid mutating arrays directly // -> will not trigger setState() and therefore not rerender thismyArray; } static { // return new state based on nextProps and prevState return myState: nextPropsmyProp ; } // recommended: use React.PureComponent instead { // use this.myState (or nextState.myState) to get next state // use this.state.myState to get current state return thismyState !== thisstatemyState; } // note: legacy lifecyle { // same as in shouldComponentUpdate() } { // use this.myState (or this.state.myState) to get current state }
@prop
Elevate this.props.someProp
to this.someProp
and
optionally define its default value whenever someProp
is undefined
.
Component @prop myProp; @prop myPropWithDefault = 'myDefaultValue'; // note: legacy lifecyle { // use this.myProp (or this.props.myProp) to get current prop } static { // return new state based on nextProps and prevState return myState: nextPropsmyProp ; } // recommended: use React.PureComponent instead { // use this.myProp (or this.props.myProp) to get current prop return thismyProp !== nextPropsmyProp; } // note: legacy lifecyle { // use this.myProp (or this.props.myProp) to get current prop } { // use this.myProp (or this.props.myProp) to get current prop }
Caveat: When using @prop
to set a default value, always use this.myProp
. this.props.myProp
will not receive the default value set by @prop
until after the first render. (Set MyComponent.defaultProps.myProp
directly if this matters for you.)
@child / @children
Specialized alternative to @prop children
. Extract and name child(ren) from this.props.children
, the result will be cached until next shouldComponentUpdate()
with different nextProps
.
Component // gets React.Children.toArray(this.props.children)[0] @child mySingleChild; // gets React.Children.toArray(this.props.children) @children allMyChildren; // gets React.Children.toArray(this.props.children).find(findChild) @ mySpecialChild; // gets React.Children.toArray(this.props.children).filter(filterChildren) @ mySpecialChildren;