In a complex application, many components will need to manage both data access and internal state. Where it really gets tricky is when we need to know something about components that are nested in the tree or data needs to persist across components or states. For example, we may want to make our router aware of requests for data in certain main content components so that we can add rules to our page transitions and 404 redirects. If we wait until components are rendered, those tasks become much harder, with interactivity and site performance suffering from extra cycles and unexpected behavior. Also, think about shared components. What if components share data on the same page, or if the same component is rendered in different spots in the tree, depending on which page it appears? We want to remove any extra logic or data access patterns used to keep these components in sync by putting as much as we can in the same place.
What is Redux?
Redux is a representation of your app as a tree of component data. An evolution of the flux data flow model popularized by React, it borrows key elements from the Elm architecture and Clojurescript, and is anchored by the idea of a single, atomic source of truth for your app state. Once we have that, we can limit the way we perform data management in components to a data access pattern, allowing us to do things like time-travel debugging and hot module loading. Given a specific state in the store, our app should render that state.
From the docs:
● The whole state of your app is stored in an object tree inside a single store.
● The only way to change the state tree is to emit an action, an object describing what happened.
● To specify how the actions transform the state tree, you write pure reducers.
The State Tree
We’ll be looking at pseudo-Angular code, but you can follow along with a working example from https://github.com/dmachat/redux-ui-router-sample. Starting with a simple data application, we’ll use two components: a user input to add data items and a display component that will display the computed result of our data. We’ll also use a simple router to control the view.
Our Redux store will look like this:
// redux store - atomic app state { data: { items: [], count: 0, }, router: {}, }
The store contains everything we need to fully render our components.
How will the app change?
Now that we know what our state tree looks like, we need to define what types of changes (or mutations) can possibly be made to the application. In Redux, these are called “actions” and should be defined as constants. Starting simply:
We can start to imagine the mutations these actions will trigger in the state tree. More actions can be added following this naming convention.
Connecting the pieces or trigger and update
Once we understand that the Redux store is just a data representation of our app, the thing that remains is to hook it up to our view with what is essentially a pub/sub system. We “connect” the component to subscribe to changes in the store, and we provide functions for the “actions” we can send from the component. These action functions are meant to be declarative: we are sending a message to the store describing how it should update. We don’t send callbacks or functions. Just a simple data object.
In code
An action function that you call from a component should contain only a few pieces of data: the mutation you want to happen on the app state and the data to use in that mutation.
And this is what the component looks like, using $ngRedux for bindings. This assumes submitting a form from the template that includes new data with a click.
Mutate!
The last thing Redux does involves what are called “reducers”. Reducers are the implementation of the instructions sent in the action, or the mutation, the state atom to a new state atom. They are pure functions that perform updates that we described in the action to the state tree. When the state atom updates, our connected components will re-render.
In code
Let’s take a look at the reducer that will add data. Assume data we’ve sent from the component is an object that looks like `{ label, value }`.
Render your data
Finally, to render your data, just connect to it from any component. To render a simple list of data, we’ve input:
tl;dr
Redux follows a uni-directional component data flow, while also making linear, atomic mutations to the app state, which makes it easy to reason about interactions and updates to your application.
app = f(state) \/ instruction = action(mutateThis, withData) \/ newState = reduce(state, instruction) \/ app = f(newState)
More Resources
● Intro to data UI
● Redux
● ng-redux
● Extend redux
● egghead tutorials