Yomguithereal/baobab-react

Monkeys do not get invoked when their source cursors are outside of the cursor of their branch component

Nimelrian opened this issue · 9 comments

Simple example:

import {root, branch} from 'baobab-react/higher-order';
import Baobab from 'baobab';
const monkey = Baobab.monkey;

// Baobab initialization
const tree = new Baobab({
    values: {
        counter: 1,
    },
    branchWithMonkey: {
        clickCounter: 0,
        counterSquared: monkey(
            ['..', 'values', 'counter'],
            counter => counter * counter
        )
    }
});

// React components
class CounterContainer extends Component {
    render() {
        return (
            <div>
                <h2>CounterContainer</h2>
                {this.props.values.counter}
            </div>
        )
    }
}
CounterContainer = branch({
    values: ['values'],
}, CounterContainer)

class MonkeyCounterContainer extends Component {
    constructor(props, context) {
        super(props, context);
        this.handleButtonClick = this.handleButtonClick.bind(this);
    }

    handleButtonClick() {
        const oldCounter = tree.get(['branchWithMonkey', 'clickCounter']);
        tree.set(['branchWithMonkey', 'clickCounter'], oldCounter + 1);
    }

    render() {
        return (
            <div>
                <h2>MonkeyCounterContainer</h2>
                <h3>clickCounter</h3>
                <div>
                    {this.props.values.clickCounter}
                </div>
                <div>
                    <button onClick={this.handleButtonClick}>Increase clickCounter</button>
                </div>
                <h3>counterSquared (monkey)</h3>
                {this.props.values.counterSquared}
            </div>
        )
    }
}
MonkeyCounterContainer = branch({values: ['branchWithMonkey']}, MonkeyCounterContainer)

class App extends Component {
    componentDidMount() {
        setInterval(() => {
            const oldCounter = tree.get(['values', 'counter']);
            tree.set(['values', 'counter'], oldCounter + 1);
        }, 1000)
    }

    render() {
        return (
            <div className="App">
                <CounterContainer/>
                <MonkeyCounterContainer/>
            </div>
        );
    }
}
App = root(tree, App)

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Expected result

I expect the MonkeyCounterContainer to show the updated value of the monkey every second (whenever the interval callback is invoked and updates the monkey's source cursor)

Actual result

When the interval callback is invoked and increases the counter field, the monkey is not invoked. The value inside the MonkeyCounterContainer element still always shows 1, unless the monkey's value is requested explicitly (e.g. by updating another value in the same branch the monkey resides in, see the clickCounter in the example, which gets increased whenever the button is pressed).

Workaround

Changing the branch call of MonkeyCounterContainer like this:

MonkeyCounterContainer = branch({
    counterSquared: ['branchWithMonkey', 'counterSquared'],
    clickCounter: ['branchWithMonkey', 'clickCounter'],
}, MonkeyCounterContainer)

and updating the JSX to use the new props fixes the issue, but can cause a lot of bloat when many variables are passed down to the component rendered in the container (one prop for the whole cursor vs one prop for each field under that cursor).

Hello @Nimelrian. If I recall correctly this is actually an issue with baobab itself. Can you try two things for me?

  1. Does the branchWithMonkey cursor emits an event when the values/counter one is updated?
  2. Does disabling the lazyMonkeys option makes your example work?

Hello @Yomguithereal, thanks for the quick reaction!

  1. The branchWithMonkey cursor does not emit any events when ['values', 'counter'] gets updated
  2. Setting lazyMonkeys to false does not change the result, the value displayed on the browser does not get updated.

Ok, so 1. confirms that it's in fact a bug with Baobab since I guess the branch should emit an update, no? I don't exactly remember why but I think I recall this was not a simple issue.

Yes. It seems that the monkey somehow does not get notified that there was a change to his source cursor(s). I'll try to experiment with my sandbox a bit, maybe I'll find the underlying issue.

I'll try to find some time to experiment. Ping me if I forget.

cc @luccitan you can try to investigate this one if you want.

Looks to me like we are missing an update/write event somewhere here: https://github.com/Yomguithereal/baobab/blob/3266b02135e2c72d13b61309b65a8e7989fde283/src/monkey.js#L194-L257

If I set lazyMonkeys to false, the value gets calculated, but no event is emitted. This means (in the context of baobab-react) that my components will never get a notification when they are listening to a monkey and the monkey calculates a new value.

Edit: This seems to be identical to Yomguithereal/baobab#448

https://github.com/Yomguithereal/baobab/blob/3266b02135e2c72d13b61309b65a8e7989fde283/src/monkey.js#L224-L251

The update function of Monkey calls the update-helper function here. I'm not sure it's a good idea to let the monkey pull the data out of the tree, update it and then write it back into the tree without notifying it about that. I feel like the tree itself should handle the update.

Is there a reason why the Monkey sneaks in the update behind the tree?

There is also something fishy here. I remember it has something to do with recursive monkeys, namely monkeys relying on other monkeys.

@Yomguithereal
I think it's best to close this issue and continue the discussion at Yomguithereal/baobab#448 (comment), since the problem lies within Baobab itself, not the React connectors.