简体   繁体   中英

ReactJS - rerender same component with different data?

I have this component class (and some related methods):

const sortData = (name, data) => {
    return data.sort((a, b) => {return b[name] - a[name]});
};

class LeaderTable extends React.Component {
    renderByRecent() {
        let data = sortData('recent', this.props.list);
        ReactDOM.render(
            <LeaderTable list={data}/>,
            document.getElementById('board'))
    }
    render() {
        return (
            <table className="table table-bordered">
                <thead>
                    <tr>
                        <td><strong>#</strong></td>
                        <td><strong>Camper Name</strong></td>
                        <td id="recent" onClick={() => this.renderByRecent()} className="text-center">Points in past 30 days</td>
                        <td id="alltime" onClick={() => this.render()} className="text-center">All time points</td>
                    </tr>
                </thead>
                <tbody>
                    {this.props.list.map(function(item, index) {
                        let url = "https://www.freecodecamp.com/" + item.username;
                        return (
                            <tr key={index}>
                                <td>{index}</td>
                                <td>
                                    <a href={url}>
                                        <img src={item.img} className="logo"/> {item.username}
                                    </a>
                                </td>
                                <td className="text-center">{item.recent}</td>
                                <td className="text-center">{item.alltime}</td>
                            </tr>
                        );
                    })}
                </tbody>
            </table>
        );
    }
}

Now first render happens when page loads. It is simply called in javascript file like this:

ReactDOM.render(
    <LeaderTable list={campersData}/>,
    document.getElementById('board'))

This one is working fine.

What I need now is to rerender same component, but with different data (actually just with different order of that data).

If you look into renderByRecent method, here I "rerender" by passing same data sorted in different way (and is is called using onClick on td with id='"recent" . But I do not know if this is really a good pattern to rerender.

Also I want to rerender back original data if td with id="alltime" is clicked. This part seems to not work (It does call render method every time I press respective td , but nothing changes). I guess I can't recal render method and hope to rerender it?

What kind of pattern is usually done with react if you have some similar case like this?

Update

Posted my original code on codepen, for easier investigation: https://codepen.io/andriusl/pen/YxWXzg

is really a good pattern to re-render?

I think no, if you want to render same component with different data manage that by using state variable don't use ReactDOM.render again.

Either use a state variable that will save the key name by which you want to sort the data then during ui creating check that and sort the data, Or you can store the props data in state variable and modify that data.

Issue with syntax onClick={() => this.render()} :

As per DOC :

The problem with this syntax is that a different callback is created each time the component renders, so better to bind the method in the constructor.

Issue with this.render():

Calling render method is not a good idea, always do setState react will automatically re-render the component.

You can write the code like this:

const sortData = (name, data) => {
    return data.sort((a, b) =>  b[name] - a[name]);
};

class LeaderTable extends React.Component {
    constructor(){
        super();
        this.state = {
            sortBy: ''
        }
    }

    _renderList(){
        let data = this.props.list.slice(0);

        if(this.state.sortBy){
            data = sortData(this.state.sortBy, data);
        }

        return data.map(function(item, index) {
            let url = "https://www.freecodecamp.com/" + item.username;
            return (
                <tr key={index}>
                    <td>{index}</td>
                    <td>
                        <a href={url}>
                            <img src={item.img} className="logo"/> {item.username}
                        </a>
                    </td>
                    <td className="text-center">{item.recent}</td>
                    <td className="text-center">{item.alltime}</td>
                </tr>
            );
        });
    }

    renderByRecent() {
        this.setState({
            sortBy: 'recent'
        });
    }

    renderOriginalList(){
       this.setState({
            sortBy: ''
       });
    }

    render() {
        return (
            <table className="table table-bordered">
                <thead>
                    <tr>
                        <td><strong>#</strong></td>
                        <td><strong>Camper Name</strong></td>
                        <td id="recent" onClick={() => this.renderByRecent()} className="text-center">Points in past 30 days</td>
                        <td id="alltime" onClick={() => this.renderOriginalList()} className="text-center">All time points</td>
                    </tr>
                </thead>
                <tbody>
                    {this._renderList()}
                </tbody>
            </table>
        );
    }
}

You should only have one main render method. Mount the React component to the DOM outside of your component and let React manage controlling the DOM updates as the component state changes.

corrected pin codepen

class LeaderTable extends React.Component {
    constructor() {
        super();

        this.state = {
            sort: false
        };

        this.renderByRecent = this.renderByRecent.bind(this); // bind this context to method
    }

    renderByRecent() {
        let data = this.props.list.slice();

        if (this.state.sort) {
            data.sort((a, b) => {
                return b.recent - a.recent;
            });
        }



        return data.map(function(item, index) {
            let url = "https://www.freecodecamp.com/" + item.username;
            return (
                <tr key={index}>
                    <td>
                        {index}
                    </td>
                    <td>
                        <a href={url}>
                            <img src={item.img} className="logo" />{" "}
                            {item.username}
                        </a>
                    </td>
                    <td className="text-center">
                        {item.recent}
                    </td>
                    <td className="text-center">
                        {item.alltime}
                    </td>
                </tr>
            );
        });
    }

    render() {
        return (
            <table className="table table-bordered">
                <thead>
                    <tr>
                        <td>
                            <strong>#</strong>
                        </td>
                        <td>
                            <strong>Camper Name</strong>
                        </td>
                        <td
                            id="recent"
                            onClick={() => this.setState({ sort: true })}
                            className="text-center"
                        >
                            Points in past 30 days
                        </td>
                        <td
                            id="alltime"
                            onClick={() => this.setState({ sort: false })}
                            className="text-center"
                        >
                            All time points
                        </td>
                    </tr>
                </thead>
                <tbody>
                    {this.renderByRecent()}
                </tbody>
            </table>
        );
    }
}

ReactDOM.render(<LeaderTable list={data} />, document.getElementById("board"));

I have a few suggestions, first of all .sort() works in-place meaning that you are mutating the data you are trying to sort. For this reason I prefer to do a .slice() first then .sort() . This will return a new array sorted according to any sorting function you pass in, not mutating data is a good functional programming practice. Regarding a good approach, it depends on how you manage your state, but in general the need to force an update (from my experience) indicates you should reconsider your logic, I would only force an update as last resort.

If you normally manage your state inside the component I would save the data into a state variable, then have different methods that sort that data according to my needs. To give you a brief example:

...
constructor(props) {
  super(props);
  this.state = { listData: this.props.list };

  this.sortBy = this.sortBy.bind(this);
}

sortBy(field) {
  const { listData } = this.state;
  const sortedList = listData
    .slice()
    .sort((a,b) => b[field] - a[field]); 

  this.setState({ listData: sortedList });
}


render() {
  return (
    <div>
      {this.state.listData.map(listItem => <MyComponent ...listItem />)}
      <button onClick={() => this.sortBy('recent')}>Sort by recent</button>
      <button onClick={() => this.sortBy('alltime)}>Sort by all time</button>
    </div>
  )
}
...

EDIT

Although you already accepted an answer, check out this implementation , which I find easier to read and maintain. It also provides a more reusable sorting approach.

React will automatically re-render if the props coming into the component have changed. So the sorting should be happening on the higher level component that is passing props to this one. You should also not be mutating the props like that.

You could also have this component manage the list in its own state like so:

const sortData = (name, data) => {
    return data.sort((a, b) => {return b[name] - a[name]});
};

class LeaderTable extends React.Component {
    constructor(props) {
        super(props)
        this.state={
            list: [{ name: 'Jerry'}, {name: 'Tabitha'}]
        }
    }
    renderByRecent() {
        // spread the array so it doesn't mutate state
        this.setState({
           list: sortData('name', [...this.state.list])
        })
    }
    render() {
        return (
            <table className="table table-bordered">
                <thead>
                    <tr>
                        <td><strong>#</strong></td>
                        <td><strong>Camper Name</strong></td>
                        <td id="recent" onClick={() => this.renderByRecent()} className="text-center">Points in past 30 days</td>
                        <td id="alltime" onClick={() => this.render()} className="text-center">All time points</td>
                    </tr>
                </thead>
                <tbody>
                    {this.state.list.map(function(item, index) {
                        let url = "https://www.freecodecamp.com/" + item.username;
                        return (
                            <tr key={index}>
                                <td>{index}</td>
                                <td>
                                    <a href={url}>
                                        <img src={item.img} className="logo"/> {item.username}
                                    </a>
                                </td>
                                <td className="text-center">{item.recent}</td>
                                <td className="text-center">{item.alltime}</td>
                            </tr>
                        );
                    })}
                </tbody>
            </table>
        );
    }
}

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