React Router v4. Need to prevent component re-rendering (trying to build instagram-like image grid)

Viewed 4055

I've created a React app which as basically 3 routes:

  • root/items: Which displays the list of products
  • root/items/:itemId: Which displays the list of products
  • root/admin: Which displays the admin panel

What I need:

On routes root/items and root/items/:itemId, I'd like to create an experience very similar to what Instagram does: https://www.instagram.com/p/BjXyK9WAEjH/?taken-by=amazon

I'd like the user click on any product to trigger a route change to root/items/:itemId and have a modal window appear (to display more info about the product) WITHOUT THE LIST OF PRODUCT BEING RE-RENDERED.

What currently happens:

  1. Each time the user clicks on a product
  2. The route change, which re-renders the list of products
  3. and shakes the page, which troubles the modal display
  4. and creates a bad user experience

Does anybody have an idea? Thank you very much.

1 Answers

You could either:

1. Use the 2 routes to render the same component

both root/items and root/items/:itemId render the same ProductList component, you should check in the render function if the id in the route exists, and render conditionally the modal with the info. You have to check for the info in your server in componentDidMount() or shouldComponentUpdate(), have a look at how to implement those in the official docs.

  • root/items: will display items
  • root/items/:itemId: same component, but renders a modal with the info.

OR

2. Don't use routes, conditional render of another component

You can have a custom component (ProductInfo) that receives the id of the product as props and renders the info of the product. In the componentDidMount() function of ProductInfo you can query your server to get the info and display it. Now, in your product list component (ProductList) you can do conditional rendering of ProductInfo with the id passed in the props.

ProductInfo

// your imports here
class ProductInfo as Component {
    constructor(){
        this.state = {info:{}}
    }

    componentDidMount(){
        // load info and set info in state using this.props.id
    }

    render(){
        // check if info is set and render all the info however you like
        return(){
            <div>{JSON.stringify( this.state.info )}</div>
        }
    }
}

ProductList

//imports here
//import a Modal, or implement however you like, with bootstrap for example
import ProductInfo from './ProductInfo';

class ProductList as Component {
    constructor(){
        this.state = {infoId: -1}
    }

    changeId(e){
        // get the id from the pressed button (or link or whatever component you are using)
        // set the id in the state, remember to clean the state after the modal has been closed: set it back to -1.
    }

    render(){
        // check if id is set
        let {infoId} = this.state;
        let renderModal = infoId > -1;
        return(){
            <div>
                {renderModal &&
                    <Modal>
                        <ProductInfo id={infoId}/>
                    </Modal>
                }
                <ul>
                    <li>
                        <button
                            type={'button'}
                            name={'id1'}
                            onChange={(e) => this.changeId(e)}>
                            Info 1
                        </button>
                    </li>
                    <li>
                        <button
                            type={'button'}
                            name={'id2'}
                            onChange={(e) => this.changeId(e)}>
                            Info 2
                        </button>
                    </li>
                    <li>
                        <button
                            type={'button'}
                            name={'id3'}
                            onChange={(e) => this.changeId(e)}>
                            Info 3
                        </button>
                    </li>
                </ul>
            </div>
        }
    }
}

This is a simple example, there are many ways of doing this in better ways but I think this answers your question.

Prevent re-renders of components

If you are still having re rendering after these suggestions, you might be rendering every time the info is loaded. To prevent this, keep the info in the state and do a conditional rendering only when the info is loaded.

import api from './api'; // your custom api component to get data

class ProductList as Component {
    constructor(){
        this.state = {list:[], dataLoaded: false, infoId:-1}
    }

    ...

    componentDidMount(){
        let myList = api.getList(); // basically call any function that gets your data
        this.setState({
            list:myList , dataLoaded: true
        });
    }

    changeId(e){
        // same as previous example
    }

    render(){
        // only render list if data is loaded using conditional rendering
        // 
        let {dataLoaded, list, infoId} = this.state;
        let renderModal = infoId> -1;

        return(
            <div>
                {renderModal &&
                    <Modal>
                        <ProductInfo id={infoId}/>
                    </Modal>
                }
                {dataLoaded &&
                    <ul>
                        <li>

                        // render your list data here using the variable list from the state

                        <li>
                    </ul>
                }
            </div>
        );
    }
}

This will prevent React on re-rendering the list even if you show the modal.

Related