简体   繁体   中英

ReactJS server-side rendering, setTimeout issue

On my ReactJS App I used setTimeout to defer some Redux action:

export const doLockSide = (lockObject) => (dispatch) => {
  const timerId = setTimeout(() => {
    dispatch({
      type: CONSTANTS.TOPICS_SET_CURRENT_TOPIC_LOCKED_SIDE,
      payload: { id: lockObject.topicId, side: lockObject.side, locked: false }
    });
  }, lockObject.unlockTimeout);

  dispatch({
    type: CONSTANTS.TOPICS_SET_CURRENT_TOPIC_LOCKED_SIDE,
    payload: { id: lockObject.topicId, side: lockObject.side, timerId, locked: true }
  });
};

The lockObject comes from the server, so this code is a part of async Redux actions chain. It worked fine, but when I tried to make this functionality to be a part of server side rendering process, it broke the App. I understand the difference between Browser and NodeJS runtime environments and the difference between its implementations of setTimeout . Specifically my timerId could not be processed by Node due to it's an object, while my Redux reducer treats it as an integer. But the main problem is that during server side rendering Node fires setTimeout callback on the server side...

The question . I have some redux-based proccess that should be deferred in some cases including the App start. How can I do it satisfying the requirement of server-side rendering?

After some research I was able to apply the following approach.

1) Push the deferred action data into some special storage in case of server-side rendering, and run it "as is" in case of Browser:

import { _postRender } from '../utils/misc';

const doLockSideUI = (dispatch, lockObject) => {
  // the body of previous version of doLockSide inner function
  const timerId = setTimeout(() => {/*...*/}, lockObject.unlockTimeout);
  dispatch(/*...*/);
};

export const doLockSide = (lockObject) => (dispatch) => {
  if(typeof window === 'undefined') { // server-side rendering case
    _postRender.actions.push({
      name: 'doLockSide',
      params: lockObject
    });
  }
  else { // Browser case
    doLockSideUI(dispatch, lockObject);
  }
};

Where utils/misc.js has the following entity:

// to run actions on the Client after server-side rendering
export const _postRender = { actions: [] }; 

2) On the server I've imported that _postRender object form utils/misc.js and pushed it to render parameters when all redux-store data dependencies had been resolved:

const markup = renderToString(/*...*/);
const finalState = store.getState();
const params = { markup, finalState, postRender: { ..._postRender } };
_postRender.actions = []; // need to reset post-render actions
return res.status(status).render('index', params);

_postRender.actions has to be cleaned up, otherwise _postRender.actions.push from p.1 will populate it again and again each time the Client had been reloaded.

3) Then I provided my post-render actions the same way as it is done for preloaded state. In my case it is index.ejs template:

<div id="main"><%- markup %></div>
<script>
  var __PRELOADED_STATE__ = <%- JSON.stringify(finalState) %>;
  var __POST_RENDER__ = <%- JSON.stringify(postRender) %>;
</script>

4) Now I need to call my __POST_RENDER__ actions with given params. For this purpose I updated my root component's did-mount hook and dispatch an additional action which handles the post-render action list:

componentDidMount() {
  console.log('The App has been run successfully');
  if(window.__POST_RENDER__ && window.__POST_RENDER__.actions.length) {
    this.props.dispatch(runAfterRender(window.__POST_RENDER__.actions));
  }
}

Where runAfterRender is a new action that is being imported from ../actions/render :

import { doLockSide } from './topic'

export const runAfterRender = (list) => (dispatch) => {
  list.forEach(action => {
    if(action.name === 'doLockSide') {
      dispatch(doLockSide(action.params));
    }
    // other actions?
  });
};

As you can see, it's just a draft and I was forced to import doLockSide action from p.1 and call it explicitly. I guess there may be a list of possible actions that could be called on the Client after server-side rendering, but this approach already works. I wonder if there is a better way...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM