Hopefully the last part of the unexpected trilogy :D

Let’s get started. We now have webpack set up, styling, react and redux. We need to manage routing and api calls to make our basic setup complete.

Both of these issues are something we will better understand if we add a component, so let’s do it. Inside src/components/ add Menu.js:

import React from 'react';

import PropTypes from 'prop-types'; import AppBar from 'material-ui/AppBar';

import FlatButton from 'material-ui/FlatButton'; const Menu = props => <AppBar

title="Our project"

onTitleClick={props.goHome}

iconElementRight={<FlatButton label="Show stuff" />}

onRightIconButtonClick={props.onClick}

/> Menu.propTypes = {

goHome: PropTypes.func.isRequired

} Menu.defaultProps = {

goHome: () => console.log('going home')

} export default Menu;

And include this in our App.js:

import React from 'react';

import PropTypes from 'prop-types'; import RaisedButton from 'material-ui/RaisedButton'; import Menu from './Menu'; const App = props => <div>

<Menu />

<RaisedButton label={props.buttonText} onClick={props.onClick} />

</div> App.propTypes = {

buttonText: PropTypes.string.isRequired,

onClick: PropTypes.func.isRequired

} App.defaultProps = {

buttonText: 'defaultText',

onClick: () => console.log('default click action')

} export default App;

Npm start and we can see our menu.

What we want is that when a user clicks Show stuff, we send a request to the backend, we receive a json, we reduce that json to a state, and then show that state on another page. To handle API calls, redux uses middlewares, so let’s setup saga:

npm i -S redux-saga

The way middleware works is much the way loaders work in webpack: they know what kind of things they want to process before they give the control back to their owner, they know how to process it, you just need to set it up. Let’s set up the middleware then. We will make a new file named store.js in the src/ folder:

import { createStore, applyMiddleware, compose } from "redux";

import createSagaMiddleware from "redux-saga";

import { reducers } from "./reducers/index";

import { sagas } from "./sagas/index"; let middlewares = []; const sagaMiddleware = createSagaMiddleware();

middlewares.push(sagaMiddleware); let middleware = applyMiddleware(...middlewares); const store = createStore(reducers, middleware);

sagaMiddleware.run(sagas); export { store };

So, we made a middlewares array, and then created the store using the middleware and the reducers. We also use sagas from src/sagas/index, and we’ll get to it after we import this into index.js, so remove

let store = createStore(reducers);

and add:

import {store} from './store';

And then make a new folder in src/ called sagas. This is where we will handle all async requests, thus keeping our reducers pure. Make an index.js:

import { takeLatest, put, call } from 'redux-saga/effects'

import actions from '../actions'

import api from '../api'; function* getStuff() {

try {

const data = yield call(api.getStuff);

yield put({ type: actions.GOT_STUFF, data });

} catch (error) {

console.log('saga fail: ', error);

yield put({ type: actions.GOT_NO_STUFF, error })

}

} export function* sagas() {

yield takeLatest(actions.GET_STUFF, getStuff);

}

We obviously need to add the actions to our actions list:

export default {

BASIC_ACTION: 'BASIC_ACTION',

GET_STUFF: 'GET_STUFF',

GOT_STUFF: 'GOT_STUFF',

GOT_NO_STUFF: 'GOT_NO_STUFF'

}

The * in the function declaration means it’s a generator function. Simply put, this function does not need to run all at once, it can yield some result, and then continue later.

To understand this, check the main sagas() function. In here, we takeLatest GET_STUFF action. Since we are in a middleware, saga will check all passing types. When there is a match, it will process it. TakeLatest means that if there are more concurrent requests only the latest one will be handled, which is great. Check out more options on their official page.

We takeLatest and then pass another generator, getStuff, with three yields. First we yield a call to an api.getStuff function ( we will get to this in a second ). This function sends a request, and our generator passes back control and waits. When we get the data, we yield put it back into redux, passing a type which will be used in a reducer. If it’s not a lot of jumping around, let’s update the reducers/index.js:

import { combineReducers } from "redux";

import basicReducer from './basicReducer';

import stuffReducer from './stuffReducer'; export const reducers = combineReducers({

text: basicReducer,

stuff: stuffReducer

});

And make stuffReducer.js:

import actions from '../actions'; const stuffReducer = (state = [], action) => {

switch(action.type) {

case actions.GOT_STUFF: return action.data

default: return state

}

} export default stuffReducer;

Notice we don’t handle the GET_STUFF, we wait for the saga to send us the data, and only then do we reduce it.

Now just add api/ folder inside src/ folder, and make an api call there:

import axios from 'axios';

import actions from '../actions'; const makeRequest = (urlExtension, data = {}) => axios.post(config.baseUrl + urlExtension, data, {withCredentials: true}); export default {

getStuff: () => makeRequest('getStuff.php')

}

I use axios for these things, so you either have to npm i -S axios or use your own. Anyway, this decouples our state management logic from our async logic, and our async logic from our external communication logic.

Great, now that we have that handled, we a container for our menu component. As we already know, this container will act as a middle man ( yet again something in the middle :D ) and define the actual onClick function. Make src/containers/Menu.js:

import { connect } from 'react-redux';

import actions from '../actions/'; import TheComponent from '../components/Menu'; const mapStateToProps = (state, ownProps) => {

return {

}

} const mapDispatchToProps = (dispatch, ownProps) => {

return {

onClick: () => {

dispatch({type: actions.GET_STUFF})

}

}

} const Menu = connect(

mapStateToProps,

mapDispatchToProps

)(TheComponent) export default Menu;

And import that one into components/App.js

import Menu from '../containers/Menu';

Start the app and click the button. You will see a saga fail in the console, but this is because we don’t have a backend endpoint. This tutorial does not cover that, so let’s just recap what we have so far:

Inside actions/ folder, we keep a list of all possible things that can happen Inside containers/ folder, we define functions to dispatch one of these actions in case something happens in a component The dispatched object now goes through the middleware. If there is no match, it goes to the reducers. If there is a match, a saga handles it usually by calling an external api. In the api/ folder, we can make several files to handle different endpoints, and we just export functions. The saga uses one of these functions to get some data from the server, and then dispatches another action with the data Redux takes back the control, now finding a reducer to handle the new action and update the state tree.

Great, hope this was simple enough :D

But we want the user to be able to click the back button and return to some previous screen, before the stuff was shown somewhere. We need routing for this, and we will import our react-router:

npm i -S react-router@3.0.2 react-router-redux

We choose a lower version of react-router because of some issues the ^4.0.0 has with other libraries, but feel free to investigate with it on your own.

We will let the router handle routing, history etc, but react-router-redux will make the data available inside the state tree. Let’s update the store.js:

import { createStore, applyMiddleware, compose } from "redux";

import createSagaMiddleware from "redux-saga";

import { reducers } from "./reducers/index";

import { sagas } from "./sagas/index"; import { browserHistory } from "react-router";

import { syncHistoryWithStore, routerMiddleware } from "react-router-redux"; let middlewares = []; middlewares.push(routerMiddleware(browserHistory)); const sagaMiddleware = createSagaMiddleware();

middlewares.push(sagaMiddleware); let middleware = applyMiddleware(...middlewares); const store = createStore(reducers, middleware);

const history = syncHistoryWithStore(browserHistory, store);

sagaMiddleware.run(sagas); export { store, history };

We pass another middleware, this one to handle browserHistory, and export this history as well. We will be able to use this history like this:

<Router history={history}>...

After importing it, of course. We also need to add a reducer for this, go to reducers/index.js:

import { combineReducers } from "redux";

import basicReducer from './basicReducer';

import stuffReducer from './stuffReducer';

import { routerReducer } from "react-router-redux"; export const reducers = combineReducers({

routing: routerReducer,

text: basicReducer,

stuff: stuffReducer

});

You can check out the routing object to see what it has to offer.

Rather than making this already long tutorial even longer, I will stop here and avoid explaining react-router.

I hope this was easily understandable. If not, please do post comments about the confusing parts.