Charles
Charles

Reputation: 3774

Multiple uses of single store on same page

I have a store that performs a fetch to get graph data from my server using asyncAction from mobx-utils.

It looks like this:

class GraphStore {
    @observable
    public loading: boolean;
    @observable
    public datapoints: any[];

    @asyncAction
    *fetch(id: string) {
         const datapoints = yield fetch('/api/datapoints');
         this.loading = false;
         this.datapoints = datapoints;
    }
}

In my component I use it like so:

@inject(STORE_GRAPH)
class Graph {
componentWillMount() {
    const graphStore= this.props[STORE_GRAPH] as GraphStore;
    const { id }  = this.props;

    graphStore.fetch(id);
}
render(){
    const graphStore= this.props[STORE_GRAPH] as GraphStore;
    if(graphStore.loading)
        return <h2>Loading</h2>

    return (
       <Chart datapoints={graphStore.datapoints}/>
    );
}

This works great but I don't know what to do when I want to expand this to display 2 graphs on the same page? Basically I want to have a parent component like so:

render() {
    return (
        <Graph id="foo"/>
        <Graph id="bar"/>
    );
}

Based on this code, the same graph store is being injected into both components, causing 2 fetches to go out and both graphs end up with the same datapoints - whichever one comes in last.

Whats the proper way to do this? Am I just thinking about this the wrong way?

Upvotes: 0

Views: 81

Answers (1)

hampusohlsson
hampusohlsson

Reputation: 10219

There are many ways to do it, but I would utilize the object oriented nature of MobX, and create one data store, that is instantiated and passed down as a provider to all components. You can see this as your "local db" if you will.

Then just add methods on that data store to fetch and create different instances of Graphs.

Here's some example code (no typescript)

// stores/data.js

import Graph from './Graph';

class DataStore {

  @observable graphs = observable.map();

  @action getGraphById(id) {
    if (!this.graphs.has(id)) {
      this.graphs.set(id, new Graph(id))
    }
    return this.graphs.get(id);
  }

}

export default new DataStore();

Then create an instantiable Graph object

// stores/Graph.js

export default class Graph {
  @observable id;
  @observable loading = false;
  @observable datapoints = [];

  constructor(id) {
    this.id = id;
    if (!this.hasData) {
      this.fetch();
    }
  }

  @computed get hasData() {
    return this.datapoints.length;
  }

  @action async fetch() {
    this.loading = true;
    const datapoints = await fetch(`/api/datapoints/${this.id}`);
    this.loading = false;
    this.datapoints = datapoints;
  }
}

In your component tree you'd pass down the dataStore through the provider

import dataStore from './stores/data'

<Provider stores={{ data: dataStore }}>
  <Graph id="foo" />
  <Graph id="bar" />
</Provider>

Then just use the id prop in the component to initiate the fetch

@inject('data')
@observer
class Graph extends Component {

  @observable graph;

  componentWillMount() {
      const { id, data }  = this.props;
      this.graph = data.getGraphById(id);
  }

  render() {
    if (this.graph.loading) {
      return <h2>Loading</h2>
    }
    return (
       <Chart datapoints={this.graph.datapoints} />
    );
  }
}

Upvotes: 1

Related Questions