Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ deepForceUpdate(rootInstance);
* Replaces static getters and setters
* Replaces unbound static methods
* Replaces static properties unless they were overwritten by code
* Merges the initial state of new versions with existing component state

## Known Limitations

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"webpack": "1.4.8"
},
"dependencies": {
"lodash": "^4.6.1"
"lodash": "^4.6.1",
"shallowequal": "^0.2.2"
}
}
5 changes: 5 additions & 0 deletions src/createClassProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import find from 'lodash/find';
import createPrototypeProxy from './createPrototypeProxy';
import bindAutoBindMethods from './bindAutoBindMethods';
import deleteUnknownAutoBindMethods from './deleteUnknownAutoBindMethods';
import mergeState from './mergeState';
import supportsProtoAssignment from './supportsProtoAssignment';

const RESERVED_STATICS = [
Expand Down Expand Up @@ -173,6 +174,10 @@ function proxyClass(InitialComponent) {
// We might have added new methods that need to be auto-bound
mountedInstances.forEach(bindAutoBindMethods);
mountedInstances.forEach(deleteUnknownAutoBindMethods);

// Merge the initial state of the next component with
// the initial state of the current component
mountedInstances.forEach(instance => mergeState(instance, CurrentComponent));
}
};

Expand Down
21 changes: 21 additions & 0 deletions src/mergeState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import assign from 'lodash/assign';
import React, { Component } from 'react';
import shallowEqual from 'shallowequal';

export default function mergeState(component, NextComponent) {
if (component instanceof React.Component) {
// Modern components
const nextComponentInstance = new NextComponent(component.props);
const mergedState = assign({}, nextComponentInstance.state, component.state);
if (!shallowEqual(component.state || {}, mergedState)) {
component.setState(mergedState);
}
} else if (component.getInitialState) {
// Classic components
const mergedState = assign({}, component.getInitialState(), component.state);
if (!shallowEqual(component.state || {}, mergedState)) {
component.setState(mergedState);
}
}
}

150 changes: 150 additions & 0 deletions test/merge-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import React, { Component } from 'react';
import createShallowRenderer from './helpers/createShallowRenderer';
import expect from 'expect';
import createProxy from '../src';

const fixtures = {
modern: {
VersionA: class VersionA extends React.Component {
constructor(props) {
super(props);
}

render() {
return <div>VersionA</div>;
}
},

VersionB: class VersionB extends React.Component {
constructor(props) {
super(props);

this.state = {
counter: 1,
};
}

render() {
return <div>VersionB: {this.state.counter}</div>;
}
},

VersionC: class VersionC extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1,
secondCounter: 1,
};
}

render() {
return <div>VersionC: {this.state.counter}{this.state.secondCounter}</div>;
}
},

PropsDependent: class PropsDependent extends React.Component {
constructor(props) {
super(props);

this.state = { counter: props.defaultCounter };
}

render() {
return <div>{this.state.counter}</div>;
}
},
},

classic: {
ClassicA: React.createClass({
render() {
return <div>ClassicA</div>;
}
}),

ClassicB: React.createClass({
getInitialState() {
return { counter: 1 };
},

render() {
return <div>ClassicB: {this.state.counter}</div>;
}
}),
},
}

describe('merging state', () => {
let renderer;
beforeEach(() => {
renderer = createShallowRenderer();
});

describe('modern', () => {
it('should merge initial state', () => {
const { VersionA, VersionB } = fixtures.modern;
const proxy = createProxy(VersionA);
const Proxy = proxy.get();
proxy.update(VersionB);
renderer.render(<Proxy/>);
expect(renderer.getRenderOutput().props.children).toEqual(['VersionB: ', 1]);
});

it('initializes state based on props', () => {
const { VersionA, PropsDependent } = fixtures.modern;
const proxy = createProxy(VersionA);
const Proxy = proxy.get();
renderer.render(<Proxy defaultCounter={3}/>);
proxy.update(PropsDependent);
renderer.render(<Proxy/>);
expect(renderer.getRenderOutput().props.children).toEqual(3);
});

it('does not overwrite existing state', () => {
const { VersionA, VersionB } = fixtures.modern;
const proxy = createProxy(VersionA);
const Proxy = proxy.get();
const instance = renderer.render(<Proxy/>);
instance.setState({counter: 5});
proxy.update(VersionB);
renderer.render(<Proxy/>);
expect(renderer.getRenderOutput().props.children).toEqual(['VersionB: ', 5]);
});

it('supports multiple updates', () => {
const { VersionA, VersionB, VersionC } = fixtures.modern;
const proxy = createProxy(VersionA);
const Proxy = proxy.get();
const instance = renderer.render(<Proxy/>);
proxy.update(VersionB);
renderer.render(<Proxy/>);
proxy.update(VersionC);
renderer.render(<Proxy/>);
expect(renderer.getRenderOutput().props.children).toEqual(['VersionC: ', 1, 1]);
});
});

describe('classic', () => {
it('should merge initial state', () => {
const { ClassicA, ClassicB } = fixtures.classic;
const proxy = createProxy(ClassicA);
const Proxy = proxy.get();
proxy.update(ClassicB);
renderer.render(<Proxy/>);
expect(renderer.getRenderOutput().props.children).toEqual(['ClassicB: ', 1]);
});

it('does not overwrite existing state', () => {
const { ClassicA, ClassicB } = fixtures.classic;
const proxy = createProxy(ClassicA);
const Proxy = proxy.get();
const instance = renderer.render(<Proxy/>);
instance.setState({counter: 5});
proxy.update(ClassicB);
renderer.render(<Proxy/>);
expect(renderer.getRenderOutput().props.children).toEqual(['ClassicB: ', 5]);
});
});
});