ali_wetrill
ali_wetrill

Reputation: 193

Component not re-rendering when nested observable changes

Adding a node to a list and yet a component is not re-rendering. Mobx Chrome Extension dev tools says it's a dependency but for some reason still no reaction!

A button renders 'Add' or 'Remove' based on whether a node is in a list. It doesn't re-render unless I move to another component and then open this component again.

Buttons:

@inject("appStore") @observer
class EntityTab extends Component {
...
    render() {
         return (
            ...
            {/* BUTTONS */}
            { this.props.appStore.repo.canvas.graph.structure.dictionary[id] !== undefined ?
                <div onClick={() => this.props.appStore.repo.canvas.graph.function(id)}>
                     <p>Remove</p>
                </div>
                :
                <div onClick={() => this.props.appStore.currRepo.canvas.otherfunction(id)}>
                    <p>Add</p>
                </div>
            }
            ...
          )
    }
}

The Add button renders, I click on the button which triggers

this.props.appStore.currRepo.canvas.otherfunction(id)

and then we go to this function

@observable graph = new Graph();
...
@action
    otherfunction = (idList) => {
        // Do nothing if no ids are given
        if (idList === null || (Array.isArray(idList) && idList.length === 0)) return;

        // If idList is a single value, wrap it with a list
        if (!Array.isArray(idList)) { idList = [idList] }

        let nodesToAdd = [];
        let linksToAdd = [];

        // Add all new links/nodes to graph
        Promise.all(idList.map((id) => { return this.getNode(id, 1) }))
            .then((responses) => {
                for (let i = 0; i < responses.length; i++) {
                    let res = responses[i];
                    if (res.success) {
                        nodesToAdd.push(...res.data.nodes);
                        linksToAdd.push(...res.data.links);
                    }
                }
                this.graph.addData(nodesToAdd, linksToAdd, idList, this.sidebarVisible);
            });
    };

The getNode function creates new Node objects from the data. For reference, those objects are instantiated as such

export default class Node {

    id = '';
    name = '';
    type = '';

    constructor(r) {
        for (let property in r) {
            // Set Attributes
            this[property] = r[property];
        }
    }
}

anyway, the addToGraphFromIds triggers

this.graph.addData(nodesToAdd, linksToAdd);

and then we go to that function

@action
    addData = (nodes, links) => {
        this.structure.addNodes(nodes);
        this.structure.addLinks(links);
    };

which triggers

this.structure.addNodes(nodes);

which leads to this function

    @observable map = new Map(); 
    @observable map2 = new Map(); 
    @observable dictionary = {}; 
    @observable dictionary2 = {}; 
    @observable allNodes = []; 
    @observable allLinks = []; 

    ...

    @action
    addNodes = (nodes=[]) => {
        if (!nodes || nodes.length === 0) return;
        nodes = utils.toArray(nodes);

        // Only consider each new node if it's not in the graph or a duplicate within the input list
        nodes = _.uniqBy(nodes, (obj) => { return obj.id; });
        const nodesToConsider = _.differenceBy(nodes, this.allNodes, (obj) => { return obj.id; });

        // Add nodes to map
        let currNode;
        for (let i = 0; i < nodesToConsider.length; i++) {
            currNode = nodesToConsider[i];
            this.map.set(currNode.id, new Map());
            this.map2.set(currNode.id, new Map());
            this.dictionary[currNode.id] = currNode;
        }

        // Update internal list of nodes
        this.allNodes = this.allNodes.concat(nodesToConsider);
    };

As we can see in the first codebox,

this.props.appStore.repo.canvas.graph.structure.dictionary[id] !== undefined

Should cause the button to change values as we have added the current node. The nodes appear in the dictionary when I log or use mobx chrome extension dev tools, but I have to switch tabs and then the button will re-render. I've tried using other lines like

this.props.appStore.repo.canvas.graph.structure.allNodes.includes(node)

but that doesn't work either. Am absolutely stuck and need help. I have a feeling it has to do with nested observables, and maybe tagging @observable isn't good enough, but not quite sure. repo and canvas are marked as observables and instantiate a new Repo() object and new Canvas() object, much like new Node() is created in getNodes.

Upvotes: 6

Views: 2273

Answers (2)

Ed C
Ed C

Reputation: 343

Mobx (v4) does not track addition or removal of entries in an observable object unless observable.map is used. Or upgrading to mobx v5 should solve the issue. For your specific issue you can try:

@observable nodeIdToNodeData = {};

//...
  this.nodeIdToNodeData = {...this.nodeIdToNodeData, [currNode.id]: currNode};

Or try to upgrade to mobx v5.

More info here

Upvotes: 2

ali_wetrill
ali_wetrill

Reputation: 193

Looks like Edward solved it, similar to Redux it looks like you have to create a new object with the dictionary rather than modify it. I'm guessing it's because the key currNode.id is already defined (as the value undefined), and we're just modifying it to be currNode. That's my guess, regardless it works now.

Upvotes: 0

Related Questions