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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • A Guide To Build, Test, and Deploy Your First DApp
  • The Ultimate Guide to React Dashboards Part 1: Overview and Analytics
  • Building a Full-Stack Resume Screening Application With AI
  • Common Problems in React Native Apps on iOS Platform: Solutions and Tips

Trending

  • Next Evolution in Integration: Architecting With Intent Using Model Context Protocol
  • Endpoint Security Controls: Designing a Secure Endpoint Architecture, Part 2
  • GitHub Copilot's New AI Coding Agent Saves Developers Time – And Requires Their Oversight
  • AWS to Azure Migration: A Cloudy Journey of Challenges and Triumphs
  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.0K 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
  • The Ultimate Guide to React Dashboards Part 1: Overview and Analytics
  • Building a Full-Stack Resume Screening Application With AI
  • Common Problems in React Native Apps on iOS Platform: Solutions and Tips

Partner Resources

×

Comments
Oops! Something Went Wrong

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

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

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 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!