Yes, we know, there are hundreds of these already and many would point to Redux as their go to library to solve this problem. Please give us a moment however to explain where Tide came from and why we’ve been sticking with it.
We took our first step into the world of React back in 2014 when we used it to rebuild and improve the checkout experience on Tictail. Since then we’ve been big fans of it and have embraced it in almost all of our projects — from building our marketplace that renders both on the server and on the client, to progressively replacing the old Backbone stack that runs the dashboard for all of our brand owners.
A common problem that we encountered early with our React apps as they started to grow was finding a good structure for the code that doesn’t (and shouldn’t) reside within our views. Facebook has come out with their own proposed solution to this with an application architecture that they call Flux.
Flux is an application structure that utilizes a unidirectional data flow to govern how updates in your application state should happen.
The views send actions containing its type (usually defined as a constant) and any additional data through a central dispatcher. Various stores containing the application’s data and business logic subscribe to dispatched actions and bind handlers based on the different action type constants that update the stores accordingly. The dispatcher enforces these updates to be made synchronously, and provides methods that help stores make sure updates are performed in the correct order.
A common practice is to have certain controller-views that are allowed to read from the different stores and pass the data down the tree. Whenever a store updates, the controller-view triggers a re-render of itself and all of its children.
So we started thinking in Flux and wrote some of our own utilities to reduce boilerplate and make it easier to work with.
To remove the need for defining and dealing with actions constants, we introduced naming conventions to automatically bind store handlers to actions. We used promises to handle asynchronous tasks that would dispatch multiple actions during their life-cycles. In essence we wrote a lot of utilities to hide the dispatcher from our application code, while still conforming to the Flux structure.
But why spend all this effort on in making the dispatcher seemingly disappear, when we can make it actually disappear?
This, combined with the idea of using Immutable data to describe our state, was the initial idea that eventually spawned the state management library that we today call Tide. The first version was built in June 2015 and we have been using it in production since then, across all of our React apps.
In Tide, we’ve completely ripped out the dispatcher and the contract that comes with it. The application state is still updated through actions, but in Tide they are just classes with methods that are exposed to the React components. You are free to do whatever you wish inside your actions — update the state once or multiple times, update it asynchronously, or perform tasks like event tracking that are completely unrelated to changing the state. Tide doesn’t really care.
What Tide does care about is how you connect your components to the application state. The entire application state is kept in a single, global, immutable state object. Actions are allowed to read and update it, but views are only given read access. This enforces the same unidirectional data flow described in Flux. But instead of creating one or more controller-views that take care of passing the application state down the tree, Tide lets every single component define what they need from the state.
This is done by wrapping the component class with a special function provided by Tide, which allows the component to specify paths in the global state that the component needs data from. Whenever any of this data changes, the component will automatically re-render with the updated values. Since the state is defined as an Immutable data structure it’s very simple and efficient to check for when data in a view has become stale and needs to be refreshed. Compared to the typical Flux structure, we no longer need to separate our data into multiple stores tied to specific controller-views to avoid unnecessary updates — Tide automatically makes sure we don’t re-render in vain.
Let’s dive into a typical Tide application structure with the global state object, an action class and a component. The snippets below are from our TodoMVC example app that’s available in full on GitHub.
Here’s the application’s state object:
The application state is just a simple, logic-less container of data using an immutable structure. This contains all of the data that will potentially change during the lifetime of the app, even the smaller things like the value of the todo input field. Tide allows us to update this value on every keypress without having to worry about performance. In a traditional Flux structure, you normally wouldn’t do that since that would trigger a lot of unnecessary re-renders (unless you make sure to add shouldComponentUpdate handlers everywhere).
To explain the benefits of keeping this value in our global state, let’s take a look at a trimmed down version of the actions in our todo app:
The actions class has direct access to read and update (mutate) the state. Other than that they are just collections of methods performing business logic for your app.
In the snippet above you’ll see that the addTodo action is free of parameters — it can retrieve everything it needs from the global state. The convenience of this becomes apparent when we imagine a more complex case with a user account form with different input fields for name, email, country, profile picture etc. Once we click the save button we don’t have to try to gather everything together in the views before calling the save action, since the action layer already has access to all the data that it needs.
Another benefit is that the logic of clearing the input after adding the todo item can also be done on the action layer instead of in the view, a further step towards keeping all business logic in one place.
The final code snippet to tie this example together is our input field component:
At the bottom of the component code you’ll see us use the wrap function provided in Tide. This connects us to the global state, which in this case binds the value prop to the todoInputText key in the state. We also get access to all of our actions inside the tide namespace in our props. Other than that it’s pretty straight forward — we call our setTodoInputText action when the input changes, which updates the global state, which in turn triggers a re-render of the component. When the user presses the enter key we just call the addTodo action which takes care of the rest.
The example above illustrates how the state, actions and components work together in a Tide app. Feel free to check out the full example app in our Tide repo on GitHub.
We hope we’ve been able to paint a good picture of Tide and what it tries to do differently from the other Flux-like libraries out there. Tide doesn’t try to set any restrictions on how your actions should behave, which is something that Redux tries to control more with its notion of reducers. There are of course pros and cons with both approaches, but we’ve found Tide to be just the right amount of opinionated for us to be productive with it at the scale we’re at.
What’s so great about Flux is that its just a paradigm and not a framework. Once you start thinking about the unidirectional data flow and how you can separate the concerns of rendering and the business logic, you will likely feel inspired to write your own library for working with that. And what’s even better is that you’ll find that your library won’t need that many lines of code before it starts to be able to fully express these ideas — because the power lies in the mindset and not in the code itself.
The way React lets us forget about the fact that we are dealing with a DOM (that’s inherently a global mutable object) allows us to separate ourselves from the traditional frontend mindset and move towards thinking in terms of general software development. This has made the community mature at a much quicker pace than ever before, and we’re all excited to be a part of it.
Anyway, enough rambling. Please head on over to our Tide GitHub repo and let us know what you think. We’re happy to hear about any bugs that you run into, or features that you’d like to see. Any contributions in the form of pull requests are very welcome as well!