DEV Community

tang
tang

Posted on • Updated on

Redux for Buddies 1: Reading State/Store

Okay, so I finished a project that made use of React and Redux. I'm pretty much a beginner at React and this is the first time I've used Redux, so if you're in a similar place as me, this writeup may be helpful. I'm going to assume that you understand the basics of React, and are able to handle basic tasks such as rendering components, creating and managing stateful components, and accessing and passing props between components.

First, let's start by talking about Redux in generalized, abstracted terms. Redux is a state management library. Where our React app might maintain 'state' in a few different places, or have to pass state down to the components that need it, Redux keeps all of our state in a centralized store, which can be accessed by our React components.

This is fairly simple in theory. You can imagine it working in a similar way to a global object, one which would maintain all of the information we needed to determine the status of our app. Components can 'see' that state and determine what they need to do, based on the information stored inside of it. Some would be able to modify the state as well. Imagine submitting a form or clicking a button and the page changing in response.

However, there is a lot of configuration that needs to be done in order to accomplish this, and much of it is done in fairly specific ways.

Here's what the general loop looks like:

store (state) -defines-> frontend -triggers-> event handler -sends data/signal (action) to-> reducer -updates-> store (state)

store

Store is basically an object that contains our state. As such, there are two basic operations we need to do with it:

  1. read from it
  2. write to it

We don't quite have enough time or space to cover doing both, so we'll talk about how to read from our store.

However, as a prerequisite to doing either of those things, we have to have some way that our React app can communicate with the store. React Redux provides us with this in the form of a provider and containers.

The provider is an element that is built in to our react-redux library. we import it from react-redux and wrap our entire app in it. It will go in the render function that hangs our entire app on the DOM, like so:

render(
<Provider store={store}> 
    <App /> 
</ Provider>, 
document.getElementById('App'))

Provider is, thus, making it possible for our app to access the store that's passed in as a 'store' property. This 'store' is a data structure created by using createStore, and giving it our reducers and (optionally) the output of a function called 'composeWithDevTools' (which allows Chrome's Redux devtools to interface with the store).

As I said before, a lot of the trouble with learning Redux is just getting all of the configuration done and ensuring that all of the parts are communicating properly with one another. In fact, there's still one more piece that we need in order to read and write from our store: Our containers.

containers are not special in themselves, but in the way that we're thinking about/using react-redux, they are similar to our provider. Basically, they are the components where we're choosing to create a connection to the store, via the react-redux connect function.

There are pretty much two things we need to do in a container in order to read a value from our store.

  1. create mapStateToProps
  2. invoke our connect

mapStateToProps

This is a function whose purpose is to act as a callback to react-redux's connect function. Conceptually, it defines the way that connect will make the state from our store available as props on our component. It returns an object whose keys are the names of pieces of data that will be made available on the this.props object on the container.

For instance, let's say our store looks like this: {'userCount':5}.

mapStateToProps might look something like this:

const mapStateToProps = store => ({
    users: store.users.userCount
})

Through the magic of connect (again, a built-in react-redux function), we would be able to do this:

console.log(this.state.users)

and have it log 5 to our console.

A simple container might look something like this:

import React, { Component } from 'react';
import { connect } from 'react-redux';
const mapStateToProps = store => ({
    users: store.users.userCount // store looks like: {'userCount':5}
})

const mapDispatchToProps = dispatch =>({
    //we'll fill this in and explain it later!
})

class DisplayUsers extends Component{
    constructor(props){
        super(props);
    }
}
render(){
    <p>{this.props.users}</p>
}
export default connect(mapStateToProps, mapDispatchToProps)(DisplayUsers)

So, if we display this component, the thing that should render out is a single p tag, with a value that we've read from our store! (specifically, store.users.userCount)

At this point you may have noticed that we're defining mapStateToProps, but never invoke it, and it may be unclear where exactly the returned value of the object might go or what it might do. The answer is that little function invocation at the bottom, connect.

Connect

Connect is what makes our whole container work. It's a built-in method that we're importing from the react-redux library, and it takes two callbacks and returns a function, which we're then immediately invoking with our entire component as an argument.

What's returned out of it is a version of our component that has the values from our mapStateToProps available on its this.props object. That's what's being exported from this file, and that's what we're seeing when we import and render our component.

This may seem confusing, but this is the reason that we're able to have <p>{this.props.users}</p> in our component and actually have it render the value from our store despite having never defined this.props.users, and having never passed users as a prop to our component.

Essentially, we write the DisplayUsers component as if it already had access to the values that we're mapping in mapStateToProps, because we know that, after invoking connect, we're going to be dealing with a version of our DisplayUsers component that does have access to the mapped values that mapStateToProps handles.

Note that, once the desired bits of our state are mapped on to props on our container component, we can also pass them down to other components as we would any property.

import React, { Component } from 'react';
import { connect } from 'react-redux';
const mapStateToProps = store => ({
    users: store.users.userCount // store looks like: {'userCount':5}
})

const mapDispatchToProps = dispatch =>({
    //we'll fill this in and explain it later!
})

class DisplayUsers extends Component{
    constructor(props){
        super(props);
    }
}
render(){
    <OtherComponent users={this.props.users} /> // imagine we had defined and imported some React component called OtherComponent
}
export default connect(mapStateToProps, mapDispatchToProps)(DisplayUsers)

In summary:

  1. create a function (mapDispatchToProps) that specifies the data that we want to access from our store ('usercount'), and the label(s) that they'll have on the props object ('users', accessible at this.props.users)
  2. create a component (DisplayUsers) as you would any other React component, except that you're free to reference labels from mapDispatchToProps as if they were keys on your props object
  3. call connect using your mapping functions and the name of your component as arguments, knowing that it will connect the mapped values with your component's properties by accessing the store. (connect(mapStateToProps,mapDispatchToProps)(DisplayUsers))

I swear I'll explain mapDispatchToProps in the next post. As a teaser, you can know that it's similar to mapStateToProps, but will provide us with the necessary functionality to cause a change in our state/store, with a few intermediary steps.

Top comments (0)