Category Archives: Frontend

Redux + Typescript: The type of the root state

You can get the type of the state from the root reducer

// the reducer
const appReducer = combineReducers({
    global: globalStateReducer,
    example: exampleReducer

type AppState = ReturnType<typeof appReducer>

However the redux state can be cleared in some scenarios, so it should be undefinable

//See for "undefined redux state" 
type RootState = AppState | undefined;

const rootReducer = (state:RootState, action: Action) => {
    if (action.type === DESTROY_REDUX_STATE) {
        state = undefined;
    return appReducer(state, action)

And mapStateToProps() of a component will look like this:

function mapStateToProps(rootState: RootState) {
    return {
        example: rootState!.example  //note the exclamation mark as rootState can be undefined 

React Component: A pair of componentDidMount() and componentWillUnmount() may cause infinite loop if error is thrown from render

The following will cause an infinite loop. The render() method will be called again and again.

class ToDoItemEditComponentContainer extends React.Component {
    componentDidMount() {        
        this.props.loadItem();  //load item remotely and put it in the local redux store      

    componentWillUnmount() {
        this.props.clearItemLocally(); //remove item from local redux store

    render() {
        throw "some error" 

Here is why: "As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree. " (See here). To be more specific,

  • render() throws an error
  • The react component tree is unmounted
  • componentWillUnmount() is called. And something is cleared from the reduxt store or react state
  • The component tree will be rendered again since some state has been changed

To fix: Implement componentDidCatch() hook in this component or create a global error boundary as shown in here . When there is a render() error, do not call the render() method any more, or do not change any local data in componentWillUnmount()

My practice of fetching data for List/Detail components in React/Redux

The user experience it should have

  • When you go to a detail page from the list page or with a direct URL, the detail page should show the up-to-date data
  • When you go to the list page with a direct URL, the list page should show the up-to-date data
  • When you RETURN to the list page from a detail page, the list page doesn’t have to show the up-to-date data.
  • When you RETURN to the list page from a editing page after MAKING CHANGES, the list page should show the up-to-date data to reflect the changes


  • On the list page
    • When entering the component, only fetch the data if the current data (this.props.listData) is null
    • When leaving the component, don’t clear the data fetched from Redux store. They will be needed when returned from detail pages
  • On a detail page
    • When entering the component, always fetch the data from the backend
    • When leaving the component, always clear the data locally from the redux store, otherwise the data of this item may be shown when you visit the detail page of another item. That’s because data fetching is called after render() .
    • When returning to the list page from here without any change, do nothing about the list in the redux store. And the link of returning should be an SPA link, which doesn’t force a new document creation
    • After data changing, dispatch an action to refresh the list data in the redux store. So when you go back to the list page, the data has been changed to reflect the changes

Code implementation

class ListComponentContainer extends React.Component {
    componentDidMount() {
        if (this.props.itemList == null) {  

    render() {
        if (this.props.itemList) {
            return <ListComponent {...this.props}>;
        } else {
            return null;

class ItemEditComponentContainer extends React.Component {
    componentDidMount() {

    componentWillUnmount() {

    render() {
            if (this.props.currentItem) {
                return <ItemEditComponent  currentitem={this.props.currentItem}/>;
            } else {
                return null;

//and in the method of handling changes
    onSubmit() {
        dispatch(clearItemListLocallyAction());  //1.  getItemsAsyncAction() will also work.  2. This can also be put in the success callback of updateItemAsyncAction()

What happens to the redux store if I refresh the browser?

It will go away, and then be initialised.

If you have this question, it means you have no exact idea of the lifespan of client-side state in a browser.

First of all, a document is created under current tab of the browser when a url is first requested from the server side. The client-side state’s lifespan is dependent on the lifespan of the document.

If your website is not an SPA, then clicking a link will lead to another http request for the server, and a new document instance is created. The client-side state will all disappear.

If your document is an SPA based on some router framework, e.g. react-router, and when you click a router-based link, no new http request will be sent, and the document remains the same one. How is this done? The onClick handlers of these links will first do e.preventDefault(), then call the History API such as history.pushState() to do same-document navigation.

How is this going to affect redux-store in an SPA site?

When you refresh the current page, a new document is created, the existing redux store will go away. After the new document is loaded, the redux store will be initialized according to your redux bootstrapping logic.

When you request a deep-link url directly in the browser, the same as above will happen.

When you click a router-based link, it will be a same-document navigation. The redux store will still be there.

When you click back/forward of the browser, then same as above will happen.

When you click a link that’s not router-based, i.e., a primitive link, the browser will just request a new document from the server side, and the existing redux store will go away

For more details about navigation in an SPA and a non-SPA, read this great article: How Single-Page Applications Work

Load data from backend before rendering a React component – best practice

Where to put the data fetching code?

componentWillMount() ? Not any more. It’s been deprecated.

You have no choice but put it inside componentDidMount() , which is recomendded by React.

However ther is a big issue. componentDidMount() is actually AFTER render(). It "works" because after componentDidMount() finishes its job and changes some state, render() will be called again. So render() will be called twice. The first time of rendering may break

The solution

  • Put the logic inside componentDidMount()
  • Inside render() method, check if the data is ready. If yes, render the component; otherwise, render null
  • When data is loading, show a spin or a loading bar. This way the user won’t be confused by empty content
  • Clean the state inside componentWillUnmount(). If you don’t do this, the data is alwasy "ready" at the step above, so no empty content will be shown
  • Better use a connector component to do all this dirty work

Code Sample

//1,2,3,4,5,6 in the comment is the order that the statements are executed 

class UserProfileComponentContainer extends React.Component { // the container component 
    componentDidMount() {
        this.props.dispatch(getMyProfileAsyncAction(this.props.rootState.token));   //2

        this.props.dispatch(cleanProfileLocallyAction());  //5  (when the user leaves the component) 

    render() {
        if (hasUserProfile(this.props.rootState)) { //data is ready 
            return <UserProfileComponent userProfile={this.props.rootState.userProfile}/>   //4 
        } else {
            return null;  //1, 3, 6 (when the user come back to this component) 

html5 localStorage pitfalls

  • It doesn’t disappear after window closed
  • It doesn’t expire
  • The old standard of it only allow string to be saved. New standard is not like so, but all mainstream browsers still use the old standard.