Using state from your Store in your views
So you've got your application state in your store, that's nice. But what about the rest of your app?
It may feel like we've been spending a lot of time focusing on state and setting up a Redux store. But what I'd like to point out to you is that this focus on state management is in fact what makes Redux so useful. You can focus on your data fetching and state management outside of presentational components. Then your components only have a single concern: render the current state.
We'll get into this further in later chapters, but this idea of maintaining application state separate from the UI enables a lot of great things. Rather than having your app be comprised of a bunch of state sprinkled into, and managed throughout your application, you instead think of state as its own distinct thing, separate from your UI components. Choosing to extract it from components goes a bit counter to current trends in the React community. I'm happily a bit of an outlier in this regard because as you'll see later, keeping those concerns completely separate enables a slew of exciting things.
But at some point we have to connect the two!
Having a perfectly maintained state object doesn't an application make. We need some way to connect our state to the user interface.
For purposes of this chapter I'm going to demonstrate this with React, but it would look nearly identical in Preact. The basic ideas would transfer to other UI libraries as well, which brings up an interesting point: flexibility. Once you've got a well-working Redux store, if you've managed to contain a significant portion of your application logic in Redux-related code (reducers, action-creators, etc.) you could potentially replace your entire view layer with a different technology entirely without throwing out your Redux code! It may seem like I have commitment anxiety concerning development libraries. To be clear, I don't crave this separation in anticipation of switching UI libraries every month. But, having been involved in many refactoring of large code bases, I'm a huge fan of anything that lets you draw clean lines of separation in your app. So, my impetus for this approach is improving flexibility. From my experience, the only constant is change. As an evolutionary scientist would tell you, the most successful systems are those that can adapt.
We must be adaptive, the most successful systems are.
Beau Lotto - Neuroscientist, author, and giver of TED talks
More on this later. But for now, let's look at binding application state to components.
Let's get to it
First. Somewhere, somehow, in some file, you'll create your store. How do you make this available to your components in React / Preact? We need to be able to dispatch things to the store and read state getState()
from the store. So we need a given component to be able to get access to the store
object itself, somehow.
The most straightforward answer would be to create a global singleton. You could create your store and attach it to a global variable: window.store
and access it via window.store
anywhere you need it in your application. But, while that works, it's a bit sloppy and makes things harder if you want to do any server-side rendering.
In the case of React/Preact, there's an alternative to using a global that will still enable an entire tree of nested components access to the store. It's called the "context API," and it exists precisely for this type of thing.
For React specifically, there are official Redux bindings available in an npm package called react-redux
that helps solve this issue. It contains two primary exports:
- a
<Provider>
component - a
connect()
function
Let's look at each of them.
The Provider
Quite arguably this component should have been named: <PutStoreIntoContext store={store}>
because that's all it does. It will take the Redux store
that you give it as a property and will put it into React's context
API so that it can be available to any component rendered inside the <Provider>
that needs access to the store.
But, don't let the name confuse you, you simply have to use it once to wrap your entire application in a <Provider>
component as seen below. The important thing to understand is that it puts the store somewhere where child components can access it:
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import RootComponent from './my/root/component'
import getStore from './a/function/that/returns/our/store'
render(
<Provider store={getStore()}>
<RootComponent />
</Provider>,
document.getElementById('app')
)
With that, we've made it available to our entire component tree. That's all you need to know about Provider.
connect()
Up until just about now, most of the APIs and code we've demonstrated have been relatively pretty. Unfortunately, this is where things start to get a little messy. To be honest, this is my least favorite part of the "official" way to build React / Redux apps. So much so, that I've switched to a different approach that I'll discuss later in the book.
The basic idea of connect()
is quite simple: connect a given component in a way that allows it access to the store. If <Provider>
puts the store into context
, connect()
gets some stuff from the store. It lets you connect relevant state that your component may need from the store and handles re-rendering the component if any of that "connected state" changes. If you're curious, it does this by fishing out a reference to the store
from the context
API and then calling store.subscribe()
on it internally.
So how do we use connect? We have to somehow select pieces of the state that are relevant to this component. It should come as no surprise by now that this too involves writing a plain function. As I've said, nearly all the code you write when working with Redux is a bunch of JavaScript primitives bundled together; this is no exception.
So, let's write a function that takes our application state and selects the part that we care about for this particular component. Note, in the react-redux
documentation, this function is referred to as mapStateToProps
because technically that is what it does. I find it simpler and less confusing to think of it as select
because that's what it does! It selects relevant stuff from the state.
// It selects part of the state that this component needs.
const select = appState => {
// it simply returns an object
return {
results: appState.results,
query: appState.query
}
}
Important things to understand in the example above:
- This function will receive the entire application state object as returned by calling
store.getState()
- You have to return an object.
- Each key of the object you return will become a
prop
that gets passed to the component you're trying to connect. Whatever you name it on the object you return, will be the name of the prop. It does not have to match what it's called in the state object.
At this point, we can pass that plain select function to connect()
. The result is another function, that can be used to wrap a component that needs those values. An example might clarify this a bit.
First, I'm going to be extra verbose to make it simpler to follow along. Next, we'll re-write the same component how I'd do it in a real project.
my-connected-component.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
class MyRegularOldComponent extends React {
render() {
return (
<div>
query: {this.props.query},
<ul>
{this.props.results.map(result => {
return (
<li>
<img src={result.url} alt={result.title} />
{result.title}
</li>
)
})}
</ul>
</div>
)
}
}
// our select function from the previous example
const select = appState => {
return {
results: appState.results,
query: appState.query
}
}
// here we call `connect` and pass it our select function
// which in turn returns a function.
const ourWrapperFunction = connect(select)
// now we pass our component to this new function which
// will return a connected component that can now be
// used by other components.
export default ourWrapperFunction(MyRegularOldComponent)
Now let's show a functionally identical but simplified version. I wanted to demonstrate some of the simplifications because things tend to get a bit verbose here, and we haven't even started adding in dispatch actions or external selectors (more on those later).
my-connected-component.js
import React from 'react'
import { connect } from 'react-redux'
// we can use a simple functional component
// instead, since all we're doing is rendering props.
// We can also use implicit returns both on the outer
// and inside our `results.map` to be a bit more concise
const MyRegularOldComponent = ({ query, results }) => (
<div>
query: {query}
<ul>
{results.map(result => (
<li>
<img src={result.url} alt={result.title} />
{result.title}
</li>
))}
</ul>
</div>
)
// For our select function we're returning an object
// but not everyone knows you can use `()` to implicitly
// return an object from an arrow function.
// So it can be re-written as follows
const select = appState => ({
results: appState.results,
query: appState.query
})
// instead of doing this in two steps, we can pass our
// component to the function returned by `connect` without
// needing to assign it to a variable first.
export default connect(select)(MyRegularOldComponent)
Understanding how connected components update
What causes a connect()
'ed component to re-render anyway? Every time we dispatch an action to the store, each connected component will run its select()
and compare the values of each key in the object that is returned using a strict equality check ===
. This plays nicely with how we do state updates in Redux. We know that every time we change state, we use the immutability rule: if we change it, we replace it. This means that the connect()
function can performantly do what's referred to as a "shallow equal" check on the result it got from the last run of the select()
function. If any of the values are different, the component will re-render.
It's possible, to mix and match props passed from a parent component to a connected component. It's cleaner to avoid this, but it's doable and sometimes necessary. In this scenario, if any of the resulting, combined props are not "shallow equal" the component will re-render.
You may be wondering how connect()
is implemented. As it turns out it creates something called a "higher order component" that wraps your component in another component that uses a shouldComponentUpdate
life-cycle method that will make this shallow comparison and return false
if nothing has changed.
What about dispatching actions?
You can also use connect()
to give your components the ability to dispatch actions. The connect()
function takes a second parameter that is called mapDispatchToProps()
in the official documentation.
So, the first parameter deals with the state. The second parameter deals with actions. This second argument can either be a function or an object. If it's a function, it will get passed the dispatch()
method from the store. I discourage use of it as follows, but I'm going to show it nonetheless, then explain what I feel is the better alternative.
import { bindActionCreators } from 'redux'
import { doClearToDos, addTodo } from './some/file/of/action/creators'
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{
doClearToDos,
addTodo
},
dispatch
)
}
Important things to understand in the example above:
- Recall from the action creators chapter that
bindActionCreators()
takes an object of action creator functions and returns an object with all the same keys, binding the original functions to dispatch. - Just like our
select
function above, whatever the name of thekeys
we return in that object will be the name of the props that get passed to our connected component.
Personally, I strongly discourage the use of passing a function here. I feel that it requires too much futzing around when really, you should just be triggering actions with action creators anyway.
Luckily, instead of passing a function, you can choose to pass an object. So, we can re-write it as follows:
import { doClearToDos, doAddTodo } from './some/file/of/action/creators'
// assume we have a full component here:
const OurComponent = () => {}
// we can build an object of actions
// optionally renaming them to whatever
// prop names we want
const actions = {
clearToDos: doClearToDos,
addTodo: doAddTodo
}
// Alternately, if we don't want to rename the props, we can use object
// shorthand to write it like this
// const actions = { doClearToDos, doAddToDo }
// let's suppose we have nothing from `state` to select in this case
// we can pass null for the first argument.
export default connect(null, actions)(OurComponent)
Putting it all together
Ok, so let's see what this all looks like when we make a connected component that uses both select
and actions
, or as the documentation calls them, mapStateToProps
and mapDispatchToProps
.
import React from 'react'
import { connect } from 'react-redux'
import { doClearQuery } from './some/file/of/action/creators'
// We again use a simple, functional component.
const OurComponent = ({ query, results, clearQuery }) => (
<div>
query: {query}
<button onClick={clearQuery}>Clear</button>
<ul>
{results.map(result => (
<li>
<img src={result.url} alt={result.title} />
{result.title}
</li>
))}
</ul>
</div>
)
// For our select function we're returning an object using
// the implicit return and wrapping it in `()`
const select = appState => ({
results: appState.results,
query: appState.query
})
// We could also use object shorthand here to avoid
// building an object, as long as we're OK with the
// props being named the same thing
const actions = { clearQuery: doClearQuery }
// Now we'll pass both select and actions here
// and return our wrapped component.
export default connect(select, actions)(OurComponent)
Important things to understand in the example above:
OurComponent
could be either a functional component or defined with aclass
: either can beconnect()
'ed.- Consider using
select
andactions
as a way to organize what extra props get passed to our connected component. - This component will not always re-render. It will re-render when either:
- Any parent component that renders this one passes it a different prop
- Any of the values on the object returned by your
select
functions are different.
A big caveat
One tricky thing with passing an object of action creators as described above is that if you:
- Import the action creator functions by name, individually.
- Then use that same name to build an object of actions.
- Then destructure the
props
argument inside your component to create a variable with the name of your action creator.
You've just ended up with something called "variable shadowing," which is potentially very confusing. This is because the variable inside your component is not the same function as the one you imported at the top of your file.
The one inside your component is bound to the store's dispatch()
function! So now you have two different variables named the same thing, in the same file that store completely different values, which can cause a lot of confusion. Due to the potential for bugs, sometimes code linters consider shadowing of a variable to be an error.
Study this example:
import React from 'react'
// we import an action creator by name
import { doClearQuery } from './action-creators'
import { connect } from 'react-redux'
// we destructure the props to grab just the relevant
// action creator.
const MyComponent = ({ doClearQuery }) => {
// DANGER!!! `doClearQuery` in here is _not_
// the same as the `doClearQuery` that was imported
// at the top of the file
return <Button onClick={doClearQuery} />
}
// we use the same name to build an object of actions
const actions = { doClearQuery }
// we connect those actions so they'll become props
export default connect(null, actions)(MyComponent)
There are a few different ways to deal with this issue:
- Live with it. If you're building an app by yourself and you know to look for this, you're probably OK.
Rename them while importing, then again, while building your actions object:
import { doClearQuery as unboundClearQuery } from './action-creators' const actions = { doClearQuery: unboundClearQuery }
Avoid the issue altogether by not re-using names when building your actions object:
import { doClearQuery } from './action-creators' const actions = { clearQuery: doClearQuery }
Ok, so when do I use connect?
Those new to working with React and Redux often either seem scared to over-use connect()
or connect way too many things. I've seen several projects where folks will connect a single top-level component and then pass props all over the place, down, down, down, through the component tree structure. Doing too much "prop passing" makes for an utter and complete mess. It breaks encapsulation and makes it very difficult to keep track of what props are actually needed. Very frequently, some deeply nested component will stop using one of the props we are passing to it, but we'll forget to remove it from the chain of prop-passing. Since it's spread out throughout several files, that kind of error is easy to miss.
On the flip side, if you connect too many components, you can end up with performance issues. Especially if you connect individual components that are rendered in a list. It adds a bunch of unnecessary overhead. Your best bet is somewhere in the middle. You're better off connect()
ing several significant components of your app.
As a rule of thumb (though each app is going to have different requirements) I'll typically connect all the major sections of the app. If there's a persistent navigation bar with a user's name or something, I'll connect that nav component. If my app has a concept of "pages," where the main content section changes as the URL changes, I'll nearly always connect each of these "page" components to the store directly. Doing this often serves as an excellent way to encapsulate related functionality.
Selecting just what you need, nothing more.
From a performance perspective, it's a bit of a mixed bag. On the one hand, since using connect()
will generate a wrapper component with a shouldComponentUpdate()
function, a connected component could spare your app from unnecessarily re-rendering child components. The default in React / Preact is to always render. So, a few well-placed, connected components that only select things that they need can lead to an overall boost to your application performance.
The key thing to understand is that what you select determines whether or not something will be re-rendered. Consider a component whose only purpose was to show a message if there were no results, which you decided to connect()
like so:
import { connect } from 'react-redux'
// returning the array of results
// we plucked off of the application state
const select = state => ({
results: state.results
})
// a component that just shows one of two messages
const HasResultsComponent = ({ results }) => {
if (result.length) {
return <p>Has results!</p>
} else {
return <p>There are no results, sorry.</p>
}
}
export default connect(select)(HasResultsComponent)
Important things to note:
- You selected more than you needed! Inside the component, you're not using the contents of
results
at all! You're just checking whether or not it has a length. - Any time anything changes in
results
, even if was an individual result item in the list that changed, the component would fully re-render. This is wasteful since both before and after, there are still results.
A better approach would be to write a select function like this:
const select = state => ({
hasResults: Boolean(state.results.length)
})
Let's think about this. Remember, each value in the object you return from select
will be shallow compared. In the first example we selected all of state.results
, causing our component to re-render anytime anything happened to results
.
Now, with the second approach, if our app goes from having ten results to having 50 results, nothing that this component cares about has changed! Remember, it only cares about whether there are any results or not! So unless that boolean value changes from true
to false
or vice versa, this component won't be unnecessarily re-rendered.
Hopefully now you see why it's worth being specific. What we return from our select
function effectively determines what aspects of the state that we're "subscribed to." By being smart about what we connect we're not wasting cycles re-rendering things that don't need to be.
Connecting just actions
Another aspect of connect()
worth mentioning is that it's smart enough to avoid doing a whole slew of unnecessary work if all you want to do is give your component access to a few action creators.
If you pass null
as the first argument to connect()
, it will not subscribe to the Redux store at all. So, if all you want to do is inject some bound action creators you can do that using connect()
with a minimal performance impact. This approach can be an excellent way to avoid passing an action creator down to a deeply nested child component. You can connect it directly instead and not lose too much sleep over it.
When not to connect a component
You probably don't want to connect a component that will be rendered many times in a list. The extra overhead of doing all this work for a whole slew of components rendered in a list is a bad idea. You're typically better off connecting whatever component will render the list, and then make the individual list item components simply take props passed from the container.
Connecting a component couples it to your particular store's structure so if you're planning on reusing or distributing a component you'd be better off sharing a non-connected version that merely expects it's data to be directly passed in as props. This way, whoever is using the component can choose to connect it to their store or pass it props through other means.
Wrapping up
As I've said before, the goal of this book is not to replace the official documentation so you should know there are more parameters and options you can pass to connect, but the react-redux
library does fine at documenting those. In my experience, however, you will rarely need anything more than what we just covered here. In fact, I would suggest limiting yourself to this subset of functionality to help you maintain a simpler, more maintainable codebase.
Also, there are some alternative approaches to connecting components, and of course how you go about this would be different if you used a different UI framework.
Chapter recap
- We use a binding library, such as
react-redux
to get state from our store into our components. - Creating a global
store
variable is fragile and a bit messy. But, we need a way to give components access to thestore
we create. We can do this using thecontext
API in React / Preact. - The
<Provider />
component is part of thereact-redux
library and simply puts the store into thecontext
which makes it available to its child components. This component does nothing else. connect()
is how we can get state and dispatch actions from the store to our components.- We write a
select()
function that takes the application state and returns an object of props for the component. - The object that is returned by your select function should only include what the component needs to know. If it's too general, it will re-render even when it's unnecessary.
- We can use the second argument to
select()
to pass an object of action creators we want to bind to the store and turn into props the component can call. - We can use
connect()
to make action creators available to our components with a minimal performance impact.