Research on State Preservation in React

Posted by nishmgopla on Fri, 17 May 2019 01:23:19 +0200

When using react with react-router for application, you may encounter such a problem. When I transit from the first page to the second page, and then return, I find that the state of the previous page has disappeared, that is, back to the original state.

This is more obvious when there are multiple TAB pages or multi-criteria screening on the page, and then I have to click on the tab I chose before, re-select the criteria, and then search. Therefore, in this case, the state before preservation appears to be particularly urgent to solve. Here are several methods that I have put into practice to share. At the same time, I hope to discuss with you and see if there is any better way.

Code: github

Solution 1: Subrouting

// normal/routers/Books/Books.js
module.exports = {
    path: 'books',
    getComponent(nextState, cb) {
        require.ensure([], (require) => {
            cb(null, require('./components/Books'))
        })
    },
    getChildRoutes(partialNextState, cb) {
        require.ensure([], (require) => {
            cb(null, [
                require('./book')
            ])    
        })
    }
};

// normal/routers/Books/book.js
module.exports = {
    path: 'book/:id',
    getComponent(nextState, cb) {
        require.ensure([], (require) => {
            cb(null, require('./components/Book'))
        })
    }
};

Configure the nested routing under the book list to see the book details. Specific routing jumps are as follows:

// normal/routers/Books/components/Books.js

onLookDetail(id, book, index) {
    this.setState({ activeIndex: index });
    this.props.router.push({ pathname: `books/book/${id}`, query: book });
}

render() {
    const { children } = this.props;
    // ...
    
    // If there is a word routing component, render the subcomponent
    if (children) {
        return children;
    }
    // ...
}

The results are as follows:

As you can see, when you return from the details page, the activation status of the click can still be saved, but the height of the list scroll can not be saved, as will be discussed below.

Solution 2: Current page pop-up window

It does not occupy routing, but loads details pages directly in the form of pop-up windows on the current page.

// normal/routers/Books/components/Books.js
constructor(props) {
    super(props);
    this.state = {
        activeIndex: -1,
        books: [],
        modal: false
    };
}

onLookDetail(id, book, index) {
    this.setState({ activeIndex: index, modal: true });
}

onDetailBack() {
    this.setState({ modal: false });
}

render() {
    {
      // Judging whether the current bullet window is displayed by modal value in state
      // Actually, it's the code in Book.js.
      modal && (
        <div style={ styles.modal }>
            <Flex direction="column" style={ styles.wrapper }>
                <div style={ styles.header }>
                    <NavBar 
                        mode="dark"
                        leftContent="Return"
                        icon={<Icon type="left" />}
                        onLeftClick={ this.onDetailBack.bind(this) }>
                        Book details
                    </NavBar>
                </div>
                <div style={ styles.content }>
                    <Card>
                        <Card.Header
                            title="Title"
                            thumb="xxx"
                            extra={ <span>{ book.title }</span> }/>
                        <Card.Body>
                            <div>{ book.description }</div>
                        </Card.Body>
                        <Card.Footer 
                            content="footer content" 
                            extra={<div>{ book.price }</div>} />
                    </Card>
                </div>
            </Flex>
        </div>
        )
    }
}

The results are as follows:

It looks very good. It can save both the state and the height of the scroll bar.

Solution 3: Local Storage / Reux Data Warehouse / Parametric Transfer

I summarize these three scenarios as one, because it is actually to save the current state when you leave the list component, and then to restore the site when you go back to the page based on the previously saved state.

// src/routers/Books/components/Books.js

// Declare periodic functions with shouldComponentUpdate to avoid unnecessary rendering
shouldComponentUpdate(nextProps, nextState) {
    return !is(fromJS(this.props.books), fromJS(nextProps.books))
        || !is(fromJS(this.state), fromJS(nextState));
}

// Update the currently selected activeIndex value, synchronize it to redux, and then route the jump
onLookDetail(id, book, index) {
    const { actions } = this.props;
    actions.updateBooks({ activeIndex: index });
    this.props.router.push({ pathname: `book/${id}`, query: book });
}

// On-site recovery from redux
render() {
    const { books } = this.props;
    const list = books.books;
    const activeIndex = books.activeIndex;
    
    // ...
}

// src/reudx/reudcers/books.js
const initialState = {
    books: [],
    activeIndex: -1
};

The results are as follows:

The effect is the same as that of word routing, but there is still the problem that the scroll height can not be saved.

Rolling Height Problem

Now let's talk about how to solve the problem of rolling height, which is a way to restore the scene. Before the page is about to leave, save the scrollTop value, and then return to the page again, restore the scroll height.

// src/reudx/reudcers/books.js
const initialState = {
    books: [],
    activeIndex: -1,
    // Add scrollTop
    scrollTop: 0
};

// src/routers/Books/components/Books.js
componentDidMount() {
    const { actions, books } = this.props;
    const content = this.refs.content;
    const scrollTop = books.scrollTop;
  
    if (scrollTop > 0) {
        content.scrollTo(0, scrollTop);
    }
  
    setTimeout(() => {
        actions.getBooks();
    }, 150);
}

componentWillUnmount() {
    const content = this.refs.content;
    const { actions } = this.props;
    actions.updateBooks({ scrollTop: content.scrollTop });
}

The results are as follows:

Attempt: react-keeper

The search on github shows that this library is similar to a replica of react-router, and on the basis of react-router, it adds a keep-alive function similar to vue-router, which temporarily occupies the pit, and fills the pit after a case has been made.

Topics: Javascript React github Windows Vue