Loading Data with Redux

In this article we're going to look at adding Redux to change how our articles are loaded. This will have the advantage of taking the content out of our bundles. And it will require adding some Redux actions and reducers as well as an API endpoint that serves the markdown.

We're going to start where we left off in the last article.
If you need to catch up:

git clone https://github.com/lawwantsin/webpack-course.git
cd webpack-course
git checkout redux
npm install

Missing Image

Drafting Article

Let's move our Article component to another name. We're going to setup a more production ready version of Article, so we'll call this, hot updating component, DraftArtice.js

cd src/components
cp Article.js DraftArticle.js
cd ../..

Quick Redux Store

Let's create a quick redux setup to introduce the concept into our app. We'll set it up first, and then I'll walk through what it does.

In terminal let's create 3 new files in src:

npm i redux react-redux
cd src
touch store.js actions.js reducers.js

In the src/store.js we'll setup a simple Redux store, by importing createStore form redux and testRedcuer from ./reducers.

import { createStore } from "redux"
import { testReducer } from "./reducers"

export default createStore(testReducer, {})

Inside store we're exporting the result of createStore, which takes a reducer and an initialState, an empty object in our case.

In the src/reducers.js let's setup a testReducer, to make sure this is working:

export const testReducer = (state = {}, action) => {
  switch (action.type) {
    case "TEST_ACTION":
      return {
        ...state,
        text: action.text
      }
    default:
      return state
  }
}

And in our action file. Let's create the TEST_ACTION, which will take a string of text, when dispatched.

In src/actions.js:

export const actionTest = text => ({
  type: "TEST_ACTION",
  text
})

Okay. So now we're ready to add this to our app. That will happen in the App.js file. As a parent to the AppContainer, we're going to import a component called the Provider. This takes our store as a prop.

In app.js:

import { Provider } from "react-redux"
import store from "./store"

function render(Component) {
  ReactDOM.hydrate(
    <Provider store={store}>
      <AppContainer>
        <Component />
      </AppContainer>
    </Provider>,
    document.getElementById("react-root")
  )
}

That's pretty much it for the client-side. Now on the server-side, we're going to add the same thing to our render file.

In server/render.js:

import store from "../store"
import { Provider } from "react-redux"

...

<Provider store={store}>
  <StaticRouter location={req.url} context={context}>
    <Routes />
  </StaticRouter>
</Provider>

Let's test it out. We're not passing anything data in at the moment. Let's dispatch an action to see the data flow.

In app.js:

store.dispatch(actionTest("something"))

Now, inside our Article.js, let's connect the store to the component.

// At top
import { connect } from "react-redux"

// At bottom
export default connect(state => ({
  __content: state.text
}))(Article)

Inside let's comment out the MarkdownData stuff.

// const imagePath = require(`../images/${siteConfig.aboutImage}`)
// const posterStyle = {
//   backgroundImage: `url(${MarkdownData.posterImage})`
// }

Then we'll replace Markdown variable in the JSX with props.

<div className="Article">
  <h1>{props.title}</h1>
  <div
    className="content"
    dangerouslySetInnerHTML={{ __html: props.__content }}
  />
</div>

Alright, so now let's npm run dev and we see the text "something" appearing as our content, instead of the MarkdownData. This means Redux is setup correctly.

If you get a syntax error on the ... spread operator. This means babel needs a new plugin to use that syntax.

In .babelrc:

"plugins": [
  "syntax-dynamic-import",
  "universal-import",
  "transform-object-rest-spread"  // Add this
]

And if you don't have it installed.

npm i babel-plugin-transform-object-rest-spread

Redux Chrome Extension

import { createStore } from "redux"
import { testReducer } from "./reducers"

const extension =
  typeof window == "object" &&
  window.__REDUX_DEVTOOLS_EXTENSION__ &&
  window.__REDUX_DEVTOOLS_EXTENSION__()

export default createStore(testReducer, extension)

In Sum

In this article we got setup with Redux and the Redux Dev Tools. We saw how we can include Redux in both the client and the server, so as to prepare us for the next step.

git checkout redux-final

Up Next

We'll build on our store, action and reducer knowledge with asynchronous actions. We'll setup Redux thunk and build a simple express endpoint, from which we'll fetch our data.