You definitely should not mutate this.state , or anything in it, ever. From Process Component API Documents :
NEVER mutate this.state directly, since calling setState () can subsequently replace the mutation you made. Treat this.state as immutable.
setState () does not immediately mutate this.state, but creates a pending state transition. Access to this.state after calling this method can potentially return an existing value.
There is no guarantee that setState calls will work synchronously, and calls can be collected to improve performance.
setState () always causes re-rendering if conditional playback logic is not implemented in shouldComponentUpdate (). If mutable objects are used and the logic cannot be implemented in shouldComponentUpdate (), calling setState () only when the new state is different from the previous state will avoid unnecessary re-rendering.
Even your second example is incorrect here, as you are still mutating this.state . The second parameter is just a callback when the state has been updated. What you want to do is somehow get the new objects and pass them to setState . Not very familiar with Vis.js, this seems rather complicated since it has no operations that return completely new / immutable datasets. There are not even copy or clone operations. It looks like you will need to create a completely new vis.DataSet each time and add data from the original, then add new data, then this.setState( { graphData: newGraphData, ... } )
Ultimately, this is likely to be a “clean” and proper way to do this.
All that is said, and with a big YMMV warning - since there is no obvious way to treat vis.DataSet as immutable, you can probably just go with the first option.
Take the first part of the warning in the docs:
calling setState () can subsequently replace the mutation you made
What will happen in this case? Nothing, since the actual object in your state is changed, this is only one link forever. If React “replaces it,” it will simply replace it with itself. In other words, until you never call setState with a completely new DataSet, it will not be replaced with the unexpected at the unexpected time.
The big problem here will be that calling forceUpdate will force re-rendering, and this can be slow. But basically it sounds like you want it to re-display no matter what, anyway, when this function is called, so ... pragmatically speaking, you're probably good :)