admin管理员组

文章数量:1421263

I have this ponent 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 = "/" + 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 ponent, 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:

I have this ponent 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./" + 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 ponent, 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

Share Improve this question edited Aug 1, 2017 at 16:48 Andrius asked Aug 1, 2017 at 16:20 AndriusAndrius 21.2k47 gold badges154 silver badges264 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 2

is really a good pattern to re-render?

I think no, if you want to render same ponent 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 ponent 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 ponent.

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./" + 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 ponent to the DOM outside of your ponent and let React manage controlling the DOM updates as the ponent 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./" + 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 ponent 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 ing into the ponent have changed. So the sorting should be happening on the higher level ponent that is passing props to this one. You should also not be mutating the props like that.

You could also have this ponent 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./" + 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>
        );
    }
}

本文标签: javascriptReactJSrerender same component with different dataStack Overflow