Skip to content

Instantly share code, notes, and snippets.

@trueadm
Last active November 11, 2017 17:21
Show Gist options
  • Save trueadm/4880c34dbf8af57a768ff5addb641550 to your computer and use it in GitHub Desktop.
Save trueadm/4880c34dbf8af57a768ff5addb641550 to your computer and use it in GitHub Desktop.
// How we do this now
class CounterComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
this._divRef = null;
this.increment = this.increment.bind(this);
}
increment() {
this.setState(state => ({
counter: state.counter + 1,
}));
}
componentDidUpdate() {
this._divRef.color = this.props.color;
}
render() {
return (
<div ref={divRef => this._divRef = divRef} onClick={this.increment}>
Count: {#state.counter}
</div>
);
}
}
// How we could do this with React.Provider
// inspiration from Redux + Reason React
function counterReducer(action, state) {
switch (action) {
case "INCREMENT": {
return React.updateState({...state, {counter: counter + 1}});
}
}
}
// a normal functional component
function CounterComponent(props) {
return (
<React.Provider
initialState={() => ({
counter: 0,
divRef: React.createRef(),
})}
reducer={counterReducer}
didUpdate={state => {
state.divRef.color = props.color;
}}
>
{(state, reduce) =>
<div ref={state.divRef} onClick={() => reduce("INCREMENT"))}>
Count: {state.counter}
</div>
}
</React.Provider>
);
}
// An alternative instead of the above
const CounterProvider = React.createProvider((action, state) => {
switch (action) {
case "INCREMENT": {
return React.updateState({...state, {counter: counter + 1}});
}
}
});
// a normal functional component
function CounterComponent(props) {
return (
<CounterProvider
initialState={() => ({
counter: 0,
divRef: React.createRef(),
})}
didUpdate={state => {
state.divRef.color = props.color;
}}
>
{(state, reduce) =>
<div ref={state.divRef} onClick={() => reduce("INCREMENT"))}>
Count: {state.counter}
</div>
}
</CounterProvider>
);
}
// An alternative instead of the above
const counterState = React.createStateReducer((action, state) => {
switch (action) {
case "INCREMENT": {
return React.updateState({...state, {counter: counter + 1}});
}
}
});
// a normal functional component
function CounterComponent(props) {
return counterState({
initialState: () => ({
counter: 0,
divRef: React.createRef(),
}),
didUpdate: state => {
state.divRef.color = props.color;
},
render(state, reduce) {
return (
<div ref={state.divRef} onClick={() => reduce("INCREMENT"))}>
Count: {state.counter}
</div>
);
}
});
}
// add a new component API, but suffers the same issues before in regards to folding
const CounterComponent = React.createReducerComponent({
initialState: (props) => ({
counter: 0,
divRef: React.createRef(),
}),
reducer: (action, state) => {
switch (action) {
case "INCREMENT": {
return React.updateState({...state, {counter: counter + 1}});
}
}
},
didUpdate: (props, state) => {
state.divRef.color = props.color;
},
render: (props, state, reduce, context) => (
<div ref={state.divRef} onClick={() => reduce("INCREMENT"))}>
Count: {state.counter}
</div>
),
});
@trueadm
Copy link
Author

trueadm commented Nov 10, 2017

Ignore the naming of Provider in the above examples, it could be called something else with a better, more descriptive, less confusing name. handling-state3.js allows for the reconciler to identify the same Provider is being used, which might be necessary to better handle lifecycle events. There are no "instance variables" in the above, instead refs are now on state.

The above examples allow for much better folding support via Prepack, even in nested branches, as the lifecycle events and state no longer needs to be "merged" to the root component and can instead live within the render tree.

@trueadm
Copy link
Author

trueadm commented Nov 10, 2017

So handling-stage5.js might look very much like how components in React are today, so what do handling-stage2.js, handling-stage3.js or handling-stage4.js offer? Well, when it comes to component folding, it makes a big difference.

Component folding works by recursively progressing through React component trees, starting with a "root" component. The "root" component's props object is completely unknown at that point. Having a completely unknown object makes it much harder to do ahead of time optimizations as we have to assume everything is abstract until we find something that tells us something is "concrete". Concrete values allow us to statically resolve code ahead of time. Each time we come to a child component of the root that we can't fold (we call this a "bail out") we have to start a new tree, using the component that we couldn't fold as a root. That means, the props of this component start of as unknown objects again.

With handling-stage2, handling-stage3.js and handling-stage4, Providers do not have a render, but rather their children operate in the closure of the parent – allowing us to continue using the evaluated props of the parent – meaning far less bailing out will occur.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment