Anders Bornholm
Anders Bornholm

Reputation: 1336

Circular module dependencies between stores

In my react native app that tracks instrument practice I have three stores:

The stores each manage one model (Session, Goal, Instrument) and getting/updating the server via a REST api.

The SessionStore listens to actions regarding Sessions (obviously): session.add, session.update. But it also listens to changes to the other stores, to be able to update the Sessions if a Goal or Instrument changes name.

Correspondingly the InstrumentStore listens to Instrument actions, but also to Session actions to update statistics on how many sessions uses a particular instrument.

To be able to not have race conditions, the InstrumentStore will act on the action session.add but wait for the SessionStore to handle the action first (to ensure the Session has been updated in the API). To do this I use dispatcher.waitFor with the SessionStore dispatchToken as a semaphore.

The problem: since all stores use each others dispatchTokens they all have to import each other. This is a circular dependency on modules and leads to strange race conditions. Sometimes one of the stores haven't been constructed when it's included by one of the other stores.

Here are my stores: https://github.com/osirisguitar/GuitarJournalApp/tree/feature/flat-ui/js/stores

Am I using the flux pattern the wrong way?

Addition

This is what I want to happen (in sequence):

Session is updated:

  1. Send updated session to API
  2. Refresh SessionStore
  3. Refresh GoalStore
  4. Refresh InstrumentStore

2, 3 and 4 need to wait for 1 to complete, that's why GoalStore and InstrumentStore need the SessionStore dispatch token.

Goal is update:

  1. Send updated goal to API
  2. Refresh GoalStore
  3. Refresh SessionStore

2 and 3 need to wait for 1, this is why SessionStore needs the GoalStore dispatchToken which introduces the circular dependency.

Upvotes: 0

Views: 432

Answers (1)

Jake Haller-Roby
Jake Haller-Roby

Reputation: 6427

You have some duplication going on.

All stores will hear all dispatches. That's the beauty of having a single dispatcher. So when you dispatch a sessions.add or sessions.update action, you're hitting three different Stores, and two of them are doing the exact same thing. That's a no-no.

As a rule, each Store's dispatch token should only be responsible for updating that store. So your Goal and Instrument stores should not be updating the SessionsStore. The .refresh and .emit should be happening within the SessionsStore dispatch token only.


EDIT to answer your edited question.

I think your confusion is because you're not recognizing that the dispatcher.register takes in a function as it's argument, and not an object.

Functions, in JS, do not evaluate their contents on declaration. They are evaluated when executed only.

Simple example;

func = function(){ console.log(testVar) } // No error, even though testVar is undefined
func() // ERROR: testVar is undefined
var testVar = 'hey';
func() // log: 'hey';

dispatcher.register takes a function as it's input, and returns an key (in the format ID_#). That key is generated by the dispatcher itself without running the input function. The input function is simply stored for later and run each time a payload is dispatched.

That means that you don't need the internal variables to be defined until your first dispatch. And because you also don't want to dispatch anything until you've created your stores, this becomes a non-issue.

But it also means that the dispatcher, by default, has a sort-of circular dependency against itself (relying on the return values of it's own functions, as stored in external variables). But that's the design of the dispatcher. Unless you're going to write a new dispatcher, that's just part of the deal.

It's also worth pointing out that if you create a true circular dependency by calling multiple waitFors that deadlock against one another, the dispatcher will correctly throw an error saying as much;

Dispatcher.waitFor(...): Circular dependency detected while waiting for ID_#

Upvotes: 1

Related Questions