/ JAVASCRIPT, REACT, REDUX

Learning Redux

Currently I am learning redux to help me build a small react application. I am following along with Brad Traversy’s 2018 tutorial on redux found here. I am taking notes while following this tutorial.

I also found this 5 minute tutorial from freeCodeCamp to be a really simple explanation of redux and it helped me understand the basic concepts before jumping into the complicated stuff.

Redux is a state manager, which can be used with react or other frameworks. In react you have state at the level of your components, but redux takes state to the application level, and data flows through your app from the top down.

Theory

The store is where state is kept, and that is sent down to your view, or component.

Inside your component you maybe have events like button clicks or submits. These fire an action creator which dispatches an action to the store.

You also have reducers which are functions that determine what should happen when an action is dispatched to the store. The reducers are what create the new state, which is then sent to the store. The new state replaces the old state. This is sent down to the components and the cycle repeats.

Implementing redux in your app

This is pretty long. I’m not sure what I think of it - it seems like a lot to set up, but perhaps it becomes more intuitive the more you use it. Maybe you can also have a simple boilerplate on hand so you don’t have to type this out every time you make a new app.

1. In your terminal run npm i redux react-redux redux-thunk

react-redux is what connects react and redux together. redux-thunk is where we get our dispatcher function from

2. In your App.js file import the Provider, which is a react component that connects react and redux together. It is from the react-redux library.

3. Inside your return, wrap everything in the Provider component.

4. Pass a “store” prop into the Provider component. The store is where the state is held for the entire application.

Below is what the code looks like up to this point:

import Posts from "./components/Posts";
import "./App.css";
import PostForm from "./components/PostForm";
import { Provider } from "react-redux";

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <PostForm />
        <hr />
        <Posts />
      </div>
    </Provider>
  );
}

export default App;

5. Create the store. To create it use the function createStore(). It takes 3 arguments: your reducer, the initial state, and an enhancer. Make sure you import createStore and applyMiddleware from ‘redux’.

//function can start off looking like this; we haven't created our reducer yet
const store = createStore(() => [], {}, applyMiddleware());

You can have this in your App.js or create a store.js file. It would be a better practice to have a separate store.js file, and import the store into App.js.

Inside your store.js, import createStore and applyMiddleware from ‘redux’, and import thunk from ‘redux-thunk’. Then create variables for initialState, middleware, and the store.

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";

const initialState = {};

const middleware = [thunk];

const store = createStore(
  rootReducer,
  initialState,
  applyMiddleware(...middleware)
);

export default store;

6. Now we need to create our reducer. Create a folder called ‘reducers’ and an index.js file. Put your root reducer in here. This combines all your reducers together and exports them. You need to import combineReducers from ‘redux’, as well as other reducers inside your reducers folder (we haven’t created them yet at this stage).

import { combineReducers } from "redux";
import postReducer from "./postReducer"; //not created yet

export default combineReducers({
  posts: postReducer,
});

7. Create your actual reducers. Create a file inside your reducers folder. The reducers handle actions that come their way.

8. Inside src create a folder called actions, and inside create a file called types.js. For actions we have to create types.

export const FETCH_POSTS = "FETCH_POSTS";
export const NEW_POST = "NEW_POST";

9. Import these types into your reducer file.

10. Create your initial state, which is an object.

11. Create a function, which takes in initial state and an action. Actions come with a type and payload if it includes data. You can use a switch statement. Depending on the type of the action that comes into your reducer, you want to do something.

This is what our reducer file looks like now:

import { FETCH_POSTS, NEW_POST } from "../actions/types";

const initialState = {
  items: [],
  item: {},
};

export default function (state = initialState, action) {
  switch (action.type) {
    default:
      return state;
  }
}

12. Now we’re creating our actions file. Since the app we’re creating here deals with “posts”, my file is called postActions.js and it is inside the actions folder. Inside here we need to import our types. Each action creator is actually a function inside this file. We also need the dispatcher inside these functions, so here is where thunk is used. We use dispatch to send our data. We pass dispatch into our function and do a fetch. This is difficult to explain in words to see code below.

import { FETCH_POSTS, NEW_POST } from "./types";

export const fetchPosts = () => (dispatch) => {
  fetch("https://jsonplaceholder.typicode.com/posts")
    .then((res) => res.json()) //turn response into json
    .then((data) =>
      dispatch({
        //take the data, dispatch to reducer
        type: FETCH_POSTS,
        payload: data,
      })
    );
};

13. Now we go back to our reducer file, and add this type to our switch statement. We want to return the current state and our payload (if there is one).

//inside the reducer file, update switch
import { FETCH_POSTS, NEW_POST } from "../actions/types";

const initialState = {
  items: [],
  item: {},
};

export default function (state = initialState, action) {
  switch (action.type) {
    case FETCH_POSTS:
      return {
        ...state,
        items: action.payload,
      };
    default:
      return state;
  }
}

14. Going away from our reducer and action files, we go to our react components to configure some things there. We have a posts component, which displays the posts from our store. Import connect from ‘react-redux’. This connects our components to the store.

You also need to import any actions you need in your component from your actions folder.

15. Your component needs to be exported with connect, as well as your action functions. We also need to add fetchPosts to our component inside componentDidMount. See below for what our component file looks like after these steps are taken.

import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchPosts } from "../actions/postActions";

class Posts extends Component {
  componentDidMount() {
    this.props.fetchPosts();
  }

  render() {
    const postItems = this.state.posts.map((post) => (
      <div key={post.id}>
        <h3>{post.title}</h3>
        <p>{post.body}</p>
      </div>
    ));
    return (
      <div>
        <h1>Posts</h1>
        {postItems}
      </div>
    );
  }
}

export default connect(null, { fetchPosts })(Posts);

Where is says null we will be mapping our state to our props - in next step.

16. Create a function inside our component called mapStateToProps. This takes our state, and maps it into our component properties. Our reducer adds items to our state. We get the state, and map those items into a posts prop. Now our component has a prop called posts - this.props.posts.

We also have to pass mapStateToProps in our export, where we had null before.

const mapStateToProps = (state) => ({
  posts: state.posts.items,
});

export default connect(mapStateToProps, { fetchPosts })(Posts);

We can use our new prop inside our component. It has our posts in it now, from our state, so we can map through them and get our posts displayed on the page.

17. Also good to add PropTypes. We import PropTypes and create an object listing the types of the props used in this component. We declare fetchPosts as a function, and posts as an array. Here is our component file now.

import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { fetchPosts } from "../actions/postActions";

class Posts extends Component {
  componentDidMount() {
    this.props.fetchPosts();
  }

  render() {
    const postItems = this.props.posts.map((post) => (
      <div key={post.id}>
        <h3>{post.title}</h3>
        <p>{post.body}</p>
      </div>
    ));
    return (
      <div>
        <h1>Posts</h1>
        {postItems}
      </div>
    );
  }
}

Posts.propTypes = {
  fetchPosts: PropTypes.func.isRequired,
  posts: PropTypes.array.isRequired,
};

//this should now give us a this.props.posts, since we mapped items from state to posts properties
const mapStateToProps = (state) => ({
  posts: state.posts.items,
});

export default connect(mapStateToProps, { fetchPosts })(Posts);

Boilerplate done, adding more stuff

Redux is basically all set up at this point, and you can repeat some of these steps when you want to add new actions. You can see that addPost wasn’t implemented yet. That would be the next step.

Steps:

  • go to actions file, add new action
  • go to reducer, add a new case to the switch statement
  • go to component where it is used, bring in connect from react-redux, bring in proptypes, and bring in action
  • implement action where you would be fetching/submitting data
  • create proptypes
  • add connect to export
  • update our mapStateToProps if needed

Conclusion

This ended up being a lot longer than I expected. Redux seems quite complicated and there is a lot of code required just to get started. I can see why having application level state is important in large projects, but it seems like there should be a simpler way.

There’s not much else to say at this stage, as I haven’t spent much time with it yet. I’m sure I’ll come back to this in the future next time I need to implement redux in a project.