diff --git a/packages/focal/src/react/react.ts b/packages/focal/src/react/react.ts index bddaa9f..2d25803 100644 --- a/packages/focal/src/react/react.ts +++ b/packages/focal/src/react/react.ts @@ -312,7 +312,9 @@ class FakeComponent

{ public props: LiftWrapperProps

) {} - setState(newState: LiftWrapperState) { + setState(state: (LiftWrapperState | ((state: LiftWrapperState) => LiftWrapperState))) { + const newState = typeof state === 'function' ? state(this.state) : state + if ('subscription' in newState) this.state.subscription = newState.subscription if ('renderCache' in newState) @@ -390,8 +392,9 @@ class RenderOne

implements Subscription { const { component, props } = liftedComponent.props const renderCache = render(component, props, [value]) - if (!structEq(liftedComponent.state.renderCache, renderCache)) - liftedComponent.setState({ renderCache }) + liftedComponent.setState(state => + !structEq(state.renderCache, renderCache) ? { renderCache } : {} + ) } private _handleCompleted() { @@ -486,8 +489,9 @@ class RenderMany

implements Subscription { const { component, props } = liftedComponent.props const renderCache = render(component, props, this._values) - if (!structEq(liftedComponent.state.renderCache, renderCache)) - liftedComponent.setState({ renderCache }) + liftedComponent.setState(state => + !structEq(state.renderCache, renderCache) ? { renderCache } : {} + ) } private _handleCompleted(idx: number) { diff --git a/packages/test/src/app.tsx b/packages/test/src/app.tsx index e0b9e2e..5d61ff1 100644 --- a/packages/test/src/app.tsx +++ b/packages/test/src/app.tsx @@ -11,7 +11,8 @@ declare var require: (path: string) => any const tests: { name: string, test: TestComponent }[] = [ 'lifted-bug', - 'render-bug' + 'render-bug', + 'setstate-bug' ].map(name => { return { name, test: require(`./${name}/index.tsx`).default as TestComponent } }) diff --git a/packages/test/src/setstate-bug/index.tsx b/packages/test/src/setstate-bug/index.tsx new file mode 100644 index 0000000..67d7d29 --- /dev/null +++ b/packages/test/src/setstate-bug/index.tsx @@ -0,0 +1,74 @@ +import * as React from 'react' +import { Atom, F } from '@grammarly/focal' +import { Observable, Subject } from 'rxjs' + +class Test extends React.Component<{ trigger: Observable }> { + list = Atom.create(['a', 'b', 'c']) + + componentWillMount() { + this.props.trigger.subscribe(list => { + this.list.set(['a', 'b']) + this.list.set(['a', 'b', 'c']) + }) + } + + render() { + return ( + <> +

Focal

+ + {this.list.view(list => { + return list.map(l =>
  • {l}
  • ) + })} +
    + + ) + } +} + +class Test2 extends React.Component<{ trigger: Observable }, { list: string[] }> { + constructor(props: any) { + super(props) + this.state = { + list: ['a', 'b', 'c'] + } + } + + componentDidMount() { + this.props.trigger.subscribe(() => { + this.setState({ list: ['a', 'b'] }) + this.setState({ list: ['a', 'b', 'c'] }) + }) + } + + render() { + const { list } = this.state + return ( + <> +

    React

    + + + ) + } +} + +const trigger = new Subject() + +function onClick() { + trigger.next() +} + +const App = () => ( +
    + + + +
    +) + +export default { + Component: App, + defaultState: 0 +}