Since the project uses React, React-Router, and Redux for development, it adopts a client-side rendering approach (this issue wouldn’t exist if SSR were used).
Problem
Therefore, each time a page is accessed, an initial request needs to be sent to fetch page data, and then the page is re-rendered. So, the previous flow from request to page load was like this:
Route match -> ComponentWillMount -> render -> ComponentDidMount -> dispatch(init())-> render -> componentDidUpdate
First, route matching occurs. Then, the component is prepared for loading. It render
s using the initial state
from the Reducer
. Then, the ComponentDidMount
event is triggered. Within this event, an Action
is dispatch
ed to execute the page’s initialization request. Upon successful request, the component is re-rendered.
As you can see, displaying the final page requires the component to render twice. One render uses the initialState
data hardcoded in the frontend reducer
, and the other occurs after receiving data from the backend.
This sometimes leads to a flickering screen effect, resulting in a poor user experience.
Our requirement is that when entering a page, it should be rendered directly with the latest data fetched from the backend, and the entire page should only render
once.
So, how can we solve this problem?
Solution
To solve this problem, it’s essential to load the data first, and then mount the page component. Therefore, the timing of data loading becomes crucial.
Drawing inspiration from traditional server-side rendering methods, the most suitable time for this is within the router.
Specifically for our project, this means making the page’s initial request within the onEnter
event of react-router
. Once all data requests are successful, the page is then loaded.
The entire page loading process then becomes like this:
Route match -> dispatch(init()) -> ComponentWillMount -> render -> ComponentDidMount
Specific Code Examples:
HomeAction.js
import { createAction } from "redux-actions";
import { HOME_INDEX } from "../../config/apis.js";
import createAsyncAction from "../../utils/createAsyncAction.js";
import initAPI from "../../utils/initAPI.js";
export const InitActionList = createAsyncAction("home/init");
export const FormChange = "home/formChange";
export const FormFieldChange = "home/formFieldChange";
export function init() {
return initAPI(InitActionList, HOME_INDEX, "get");
}
HomeReducer.js
import {
BillStatus,
CreditStatus,
InstallmentStatus,
} from "../../config/constant";
import { InitActionList } from "./HomeAction.js";
const initState = {
foo: 1,
bar: 10,
};
export default function (state = initState, action) {
const type = action.type;
const payload = action.payload;
const meta = action.meta;
switch (type) {
case InitActionList.start:
return state;
case InitActionList.success:
const currentStatus = getCurrentStatus(payload);
return {
...state,
foo: currentStatus,
};
case InitActionList.failure:
return state;
default:
return state;
}
}
Router.js
import { init as initHome } from "./homeAction";
export default function createRoutes(store) {
function initHome(store) {
return (nextState, replace, next) => {
// dispatch 页面加载的 Action,在数据加载完成后在执行 next() 以挂载组件
store.dispatch(homeInit()).then(() => next());
};
}
return {
component: App,
path: "/",
childRoutes: [
require("./activate"),
{
path: "test",
getComponent(nextState, cb) {
require.ensure(
[],
(require) => {
cb(null, require("../views/Test").default);
},
"Test",
);
},
},
],
indexRoute: {
getComponent(nextState, cb) {
require.ensure(
[],
(require) => {
cb(null, require("../views/Home").default);
},
"Home",
);
},
onEnter: initHome(store),
},
};
}
Index.js
import React from "react";
import { render } from "react-dom";
import { AppContainer } from "react-hot-loader";
import { Provider } from "react-redux";
import { hashHistory, Router } from "react-router";
import { syncHistoryWithStore } from "react-router-redux";
import "react-hot-loader/patch";
import createRoutes from "./routes";
import configureStore from "./store";
import "./style/app.scss";
export const store = configureStore(hashHistory, {});
const history = syncHistoryWithStore(hashHistory, store);
const root = document.getElementById("root");
const routers = createRoutes(store);
const renderRoot = () => {
render(
<AppContainer>
<Provider store={store} key="provider">
<Router routes={routers} history={history} key={Math.random()} />
</Provider>
</AppContainer>,
root,
);
};
if (module.hot) {
module.hot.accept("./routes", () => {
renderRoot();
});
}
renderRoot();
This article was published on April 20, 2017 and last updated on April 20, 2017, 3090 days ago. The content may be outdated.