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:

  1. a <Provider> component
  2. 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:

  1. This function will receive the entire application state object as returned by calling store.getState()
  2. You have to return an object.
  3. 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:

  1. 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.
  2. Just like our select function above, whatever the name of the keys 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:

  1. OurComponent could be either a functional component or defined with a class: either can be connect()'ed.
  2. Consider using select and actions as a way to organize what extra props get passed to our connected component.
  3. This component will not always re-render. It will re-render when either:
    1. Any parent component that renders this one passes it a different prop
    2. 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:

  1. Import the action creator functions by name, individually.
  2. Then use that same name to build an object of actions.
  3. 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:

  1. Live with it. If you're building an app by yourself and you know to look for this, you're probably OK.
  2. Rename them while importing, then again, while building your actions object:

    import { doClearQuery as unboundClearQuery } from './action-creators'
    
    const actions = {
      doClearQuery: unboundClearQuery
    }
    
  3. 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:

  1. 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.
  2. 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

  1. We use a binding library, such as react-redux to get state from our store into our components.
  2. Creating a global store variable is fragile and a bit messy. But, we need a way to give components access to the store we create. We can do this using the context API in React / Preact.
  3. The <Provider /> component is part of the react-redux library and simply puts the store into the context which makes it available to its child components. This component does nothing else.
  4. connect() is how we can get state and dispatch actions from the store to our components.
  5. We write a select() function that takes the application state and returns an object of props for the component.
  6. 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.
  7. 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.
  8. We can use connect() to make action creators available to our components with a minimal performance impact.

results matching ""

    No results matching ""