Unidirectional data
A cornerstone of any Flux architecture is unidirectional data-flow. The idea being data flows from point A to point B, or from point A to B to C, or from point A to C. It's the direction that's important with unidirectional data-flow, and to a lesser extent, the ordering. So when we say that our architecture uses a unidirectional data-flow, we can say that data never flows from point B to point A. This is an important property of Flux architectures.
As we saw in the previous section, MV* architectures have no discernible direction with their data-flows. In this section, we'll talk though some of the properties that make a unidirectional data-flow worth implementing. We'll begin with a look at the starting points and completion points of our data-flows, and then we'll think about how side-effects can be avoided when data flows in one direction.
From start to finish
If data-flows in only one direction, there has to be both a starting point and a finish point. In other words, we can't just have an endless stream of data, which arbitrarily affects the various components the data-flows through. When data-flows are unidirectional with clearly defined start and finish points, there's no way we can have circular flows. Instead, we have one big data-flow cycle in Flux, as visualized here:
This is obviously an over-simplification of any Flux architecture, but it does serve to illustrate the start and finish points of any given data-flow. What we're looking at is called an update round. A round is atomic in the sense that it's run-to-completion—there's no way to stop an update round from completing (unless an exception is thrown).
JavaScript is a run-to-completion language, meaning that once a block of code starts running, it's going to finish. This is good because it means that once we start updating the UI, there's no way a callback function can interrupt our update. The exception to this is when our own code interrupts the updating process. For example, our store logic that's meant to mutate the state of the store dispatches an action. This would be bad news for our Flux architecture because it would violate the unidirectional data-flow. To prevent this, the dispatcher can actually detect when a dispatch takes place inside of an update round. We'll have more on this in later chapters.
Update rounds are responsible for updating the state of the entire application, not just the parts that have subscribed to a particular type of action. This means that as our application grows, so do our update rounds. Since an update round touches every store, it may start to feel as though the data is flowing sideways through all of our stores. Here's an illustration of the idea:
From the perspective of unidirectional data-flow, it doesn't actually matter how many stores there are. The important thing to remember is that the updates will not be interrupted by other actions being dispatched.
No side-effects
As we saw with MV* architectures, the nice thing about automatic state changes is also their demise. When we program by hidden rules, we're essentially programming by stitching together a bunch of side-effects. This doesn't scale well, mainly due to the fact that it's impossible to hold all these hidden connections in our head at a given point in time. Flux likes to avoid side-effects wherever possible.
Let's think about stores for a moment. These are the arbiters of state in our application. When something changes state, it has the potential to cause another piece of code to run in response. This does indeed happen in Flux. When a store changes state, views may be notified about the change, if they've subscribed to the store. This is the only place where side-effects happen in Flux, which is inevitable since we do need to update the DOM at some point when state changes. But what's different about Flux is how it avoids side-effects when there's data dependencies involved. The typical approach to dealing with data dependencies in user interfaces is to notify the dependent model that something has happened. Think cascading updates, as illustrated here:
When there's a dependency between two stores in Flux, we just need to declare this dependency in the dependent store. What this does is it tells the dispatcher to make sure that the store we depend on is always updated first. Then, the dependent store can just directly use the store data it depends on. This way, all of the updates can still take place within the same update round.