DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • A Guide To Build, Test, and Deploy Your First DApp
  • Production-Grade React Project Structure: From Setup to Scale
  • Building a Full-Stack Resume Screening Application With AI
  • Common Problems in React Native Apps on iOS Platform: Solutions and Tips

Trending

  • Implementing Observability in Distributed Systems Using OpenTelemetry
  • 5 Common Security Pitfalls in Serverless Architectures
  • Chaos Engineering Has a Blind Spot. Agentic AI Lives in It.
  • Every Cache Miss Is a Tiny Tax on Your Performance
  1. DZone
  2. Coding
  3. JavaScript
  4. How to Build a Single Page Application With React, Redux, Router

How to Build a Single Page Application With React, Redux, Router

By 
Kunkka Li user avatar
Kunkka Li
·
Sep. 30, 20 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
8.4K Views

Join the DZone community and get the full member experience.

Join For Free

As technology continues to evolve, single page applications (SPA), along with microservice-based backends become more and more popular. Single page application does not require page reloading during use once it has been loaded the first time, which means it's more user friendly faster than legacy UI made by something like JSP, PHP, ASP, etc.

There are various techniques available that enable the browser to retain a single page even when the application requires server communication. In this article, I'm going to introduce how to make that with React. You need to have a quick look at basic tutorials in regards to npm, react components, babel, webpack. The code is available in Github:

https://github.com/liqili/react-redux-router-webpack-demo

1. Architecture Chart

This is an UI MVC architecture chart. The view is React class component with its own states, constants, actions(events), reducers(event handlers), containers(connect to Redux global store). The model and controller are Redux store, which acts as a global centralized manager, dispatching actions and executing reducers. The state change will in turn result in React components updating.


2. React Components

By leveraging Redux concepts, we can make React components clean and well-organized. As illustrated below, we can see there are actions, constants, containers, reducers, and main class in each component folder.

Application file structure

Let's take login page as an example.

2.1 Actions

Actions are plain JavaScript objects. Actions must have a type property that indicates the type of action being performed. You send them to the store using store.dispatch(). The functions that dispatch actions are called action creators, such as logOut and logIn. For the logIn function, we leverage redux-thunk middleware to perform asynchronous dispatches. This is really useful when you need to do conditional dispatches.

JavaScript
xxxxxxxxxx
1
43
 
1
import actionTypes from './Login.Constants';
2
3
// fake user data
4
const testUser = {
5
    'name': 'juju',
6
    'age': '24',
7
};
8
9
// login
10
export function logIn(opt, callBack) {
11
    return (dispatch) => {
12
        dispatch({
13
            'type': actionTypes.LOGGED_DOING
14
        });
15
        setTimeout(function () {
16
            fetch('https://github.com/', {
17
                mode: 'no-cors'
18
            })
19
                .then((res) => {
20
                    dispatch({
21
                        'type': actionTypes.LOGGED_IN,
22
                        user: testUser
23
                    });
24
                    if (typeof callBack === 'function') {
25
                        callBack();
26
                    }
27
                }).catch((err) => {
28
                    dispatch({
29
                        'type': actionTypes.LOGGED_ERROR,
30
                        error: err
31
                    });
32
                });
33
        }, 3000);
34
35
    }
36
}
37
38
export function logOut() {
39
    return {
40
        type: actionTypes.LOGGED_OUT
41
    }
42
}


2.2 Reducers

The reducer is a pure function that takes the previous state and an action, and returns the next state. The concept comes from map-reduce. Redux also provides a combineReducers helper function to merge separate reducing functions from different components into a single reducing function so that we can pass to createStore.

JavaScript
xxxxxxxxxx
1
44
 
1
import TYPES from './Login.Constants';
2
3
const initialState = {
4
    isLoggedIn: false,
5
    user: {},
6
    status: null,
7
};
8
9
export default function reducer(state = initialState, action) {
10
11
    switch (action.type) {
12
        case TYPES.LOGGED_DOING:
13
            return {
14
                ...state,
15
                status: 'doing'
16
            };
17
18
        case TYPES.LOGGED_IN:
19
            return {
20
                ...state,
21
                isLoggedIn: true,
22
                user: action.user,
23
                status: 'done'
24
            };
25
26
        case TYPES.LOGGED_OUT:
27
            return {
28
                ...state,
29
                isLoggedIn: false,
30
                user: {},
31
                status: null
32
            };
33
        case TYPES.LOGGED_ERROR:
34
            return {
35
                ...state,
36
                isLoggedIn: false,
37
                user: {},
38
                status: null
39
            }
40
41
        default:
42
            return state;
43
    }
44
}


2.3 Containers

Container components are used to connect/subscribe to Redux store, which means it can bind to redux store state change and actions.

JavaScript
xxxxxxxxxx
1
18
 
1
import {
2
    connect
3
} from "react-redux";
4
import {
5
    bindActionCreators
6
} from "redux";
7
import * as rootActions from "../Root/Root.Actions";
8
import * as loginActions from "../Login/Login.Actions";
9
import Login from "./Login";
10
11
export default connect((state) => ({
12
    isLoggedIn: state.userStore.isLoggedIn,
13
    user: state.userStore.user,
14
    status: state.userStore.status,
15
}), (dispatch) => ({
16
    rootActions: bindActionCreators(rootActions, dispatch),
17
    loginActions: bindActionCreators(loginActions, dispatch),
18
}))(Login);


2.4 React Components

In this example, we still use React class components, however, since the latest React version has introduced hooks to functional components, it's no longer recommended to use class components. We can dispatch redux actions just like calling a methods thanks to react-redux connect component (illustrated in 2.3).

JavaScript
xxxxxxxxxx
1
11
 
1
handleLogin() {
2
        if (!this.state.username || !this.state.password) {
3
            return;
4
        }
5
        const opt = {
6
            'name': this.state.username,
7
            'password': this.state.password,
8
        };
9
        this.props.loginActions.logIn(opt, this.onSuccessLogin);
10
    }


3.Redux Store

In the stores.js, we create a global store and register redux middleware, such as logger and thunk.

JavaScript
xxxxxxxxxx
1
40
 
1
//@flow
2
import thunk from 'redux-thunk';
3
import {
4
    createStore,
5
    applyMiddleware,
6
} from 'redux';
7
import {
8
    persistStore,
9
    persistReducer
10
} from 'redux-persist';
11
import storage from 'redux-persist/lib/storage';
12
import reducers from './reducers';
13
14
const logger = store => next => action => {
15
    if (typeof action === 'function') {console.log('dispatching a function');}
16
    else {console.log('dispatching', action);}
17
    const result = next(action);
18
    console.log('next state', store.getState());
19
    return result;
20
}
21
22
const middlewares = [
23
    logger,
24
    thunk
25
];
26
27
const createAppStore = applyMiddleware(...middlewares)(createStore);
28
const persistConfig = {
29
    key: 'root',
30
    storage: storage,
31
    transform: [],
32
};
33
const persistedReducer = persistReducer(persistConfig, reducers);
34
35
export default function configureStore(onComplete: () => void) {
36
    const store = createAppStore(reducers);
37
    persistStore(store, null, onComplete);
38
    return store;
39
}


All React container components need access to the Redux store so they can subscribe to it. It's recommended to use <Provider> to make the store available to all container components in the application without passing it explicitly. You only need to use it once when you render the root component. Please check index.js to see how to pass the Redux store.

JavaScript
xxxxxxxxxx
1
 
1
<Provider store={store}>
2
  <Root/>
3
</Provider>


4. Routers

Routers play a key role in single page application. Its most basic responsibility is to dynamically render some UI(partly) when its path matches the current URL. It needs Link tag to work together with html navigation tags.

JSX
xxxxxxxxxx
1
 
1
<li><Link to={item.path}>{item.name}</Link></li>
JavaScript
xxxxxxxxxx
1
25
 
1
import React, {
2
    Component
3
} from 'react';
4
import Login from './Login/Login.Container';
5
import Home from './Home/Home.Container';
6
import Root from './Root/Root.Container';
7
import {
8
    IndexRoute,
9
    Route,
10
    Router,
11
} from 'react-router';
12
import {
13
    browserHistory,
14
} from 'react-router';
15
export default function Routes() {
16
    return (
17
        <Router history={browserHistory}>
18
            <Route path="/" component={Root}>
19
                <IndexRoute component={Home} ></IndexRoute>
20
                <Route path="home" component={Home}></Route>
21
                <Route path="login" component={Login}></Route>
22
            </Route>
23
        </Router>
24
    );
25
}


5. Service Worker

First of all, you may ask why we need a service worker. Suppose you have an e-commerce web site which is a single page application. You navigate to shopping cart from index page, and you won't have any issues thanks to UI router mechanism. But if you open a new window and paste  URL like "localhost/cart/" to the browser to visit the shopping cart, then it will send the request - "localhost/cart/" to backend to fetch the page. Obviously you will get 404 error. Service worker comes out for this scenario.

In my example, I use webpack sw-precache-webpack-plugin to handle that.

JavaScript
xxxxxxxxxx
1
 
1
plugins: [
2
        new SWPrecacheWebpackPlugin({
3
            cacheId: "react-demo",
4
            filename: "service-worker.js",
5
            navigateFallback: "/index.html",
6
            staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
7
        }),
8
    ]


React (JavaScript library) application Build (game engine)

Opinions expressed by DZone contributors are their own.

Related

  • A Guide To Build, Test, and Deploy Your First DApp
  • Production-Grade React Project Structure: From Setup to Scale
  • Building a Full-Stack Resume Screening Application With AI
  • Common Problems in React Native Apps on iOS Platform: Solutions and Tips

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook