16. Using External Data

16.1. Creating A Simple Backend

To persist our data, we will create a backend to fetch our initial data. We will use express to create a simple server. To install type:

yarn add express

Now we will create a simple server in the file server.js

 1const express = require("express");
 2const app = express();
 3const port = 3001;
 4
 5const faq = [
 6  {
 7    question: "What does the Plone Foundation do?",
 8    answer: "The mission of the Plone Foundation is to protect and..."
 9  },
10  {
11    question: "Why does Plone need a Foundation?",
12    answer: "Plone has reached critical mass, with enterprise..."
13  }
14];
15
16app.get("/", (req, res) => {
17  res.header("Access-Control-Allow-Origin", "*");
18  res.json(faq);
19});
20
21app.listen(port, () => console.log(`Listening on port: ${port}`));

Then we can run our newly created server:

node server.js

Now it is time to write our action to fetch the items from the backend in the file actions/index.js:

19export const getFaqItems = () => ({
20  type: "GET_FAQ_ITEMS",
21  request: {
22    op: "get",
23    path: "/"
24  }
25});

16.2. Writing Middleware

Since the action itself doesn't do any API call, we will create middleware to do the job. Redux middleware is a simple method which receives the store, the method to call the next action, and the action itself. The middleware can then decide to do something based on the data in the action. In our case we are looking for a property called request. If that one is available, then we want to do an API call with the provided operation, path, and data, and fire a new action when the data is fetched. We will create a file at middleware/api.js and the implementation will look like this:

 1export default store => next => action => {
 2  const { request, type, ...rest } = action;
 3
 4  if (!request) {
 5    return next(action);
 6  }
 7
 8  next({ ...rest, type: `${type}_PENDING` });
 9
10  const actionPromise = fetch(`http://localhost:3001${request.path}`, {
11    method: request.op,
12    body: request.data && JSON.stringify(request.data)
13  });
14
15  actionPromise.then(response => {
16    response.json().then(data => next({ data, type: `${type}_SUCCESS` }));
17  });
18
19  return actionPromise;
20};

Finally we need to apply our middleware to the store in App.js:

 1import { Provider } from "react-redux";
 2import { createStore, applyMiddleware } from "redux";
 3
 4import rootReducer from "./reducers";
 5import Faq from "./components/Faq";
 6import api from "./middleware/api";
 7
 8import "./App.css";
 9
10const store = createStore(rootReducer, applyMiddleware(api));
11
12const App = () => {
13  return (
14    <Provider store={store}>
15      <Faq />
16    </Provider>
17  );
18};
19
20export default App;

Differences

--- a/src/App.js
+++ b/src/App.js
@@ -1,12 +1,13 @@
 import { Provider } from "react-redux";
-import { createStore } from "redux";
+import { createStore, applyMiddleware } from "redux";

 import rootReducer from "./reducers";
 import Faq from "./components/Faq";
+import api from "./middleware/api";

 import "./App.css";

-const store = createStore(rootReducer);
+const store = createStore(rootReducer, applyMiddleware(api));

 const App = () => {
   return (

The last part is to change our reducer at reducers/faq.js to handle the GET_FAQ_ITEMS_SUCCESS action:

 1const faq = (state = [], action) => {
 2let faq;
 3switch (action.type) {
 4  case "ADD_FAQ_ITEM":
 5    return [
 6      ...state,
 7      {
 8        question: action.question,
 9        answer: action.answer
10      }
11    ];
12  case "EDIT_FAQ_ITEM":
13    faq = [...state];
14    faq[action.index] = {
15      question: action.question,
16      answer: action.answer
17    };
18    return faq;
19  case "DELETE_FAQ_ITEM":
20    faq = [...state];
21    faq.splice(action.index, 1);
22    return faq;
23  case "GET_FAQ_ITEMS_SUCCESS":
24    return action.data;
25  default:
26    return state;
27  }
28};
29
30export default faq;