0

I'm developing some SPAs in React and I came across with this issue several times; I eventually solved it in UGLY ways like the one posted in the code below.

I feel like I'm missing something obvious but I really can't figure out a more elegant (or even right) way to accomplish the same result, can you help me?

class Leaderboard extends React.Component {
  constructor(props) {
    super(props);
    this.state={
      lb:{},
      leaderboard:""
    };
    this.leaderboardGet = this.leaderboardGet.bind(this);
    this.leaderboardSet = this.leaderboardSet.bind(this);
    this.lblooper = this.lblooper.bind(this);
    this.lbconstruct = this.lbconstruct.bind(this);
  }


  leaderboardGet(callback){ //server call which returns data
    axiosCall.get('leaderboard.php',{params:{user:this.props.user}})
    .then((response)=>{
      var arr=response.data;
      callback(arr);
    })
    .catch((error)=>{
      console.log(error);
    })
  }
  leaderboardSet(a){ //puts in lb object the results of the server call and calls lb looper
    this.setState({lb: a});
    this.lblooper();
  }
  componentWillMount(){
    this.leaderboardGet(this.leaderboardSet);
  }

  lblooper(){ //the ugliness itself: loops the data in lb object, and pushes it into an "html string" in lblconstruct function 
    Object.entries(this.state.lb).forEach(
      ([key, value]) => this.lbconstruct(`<div class="leaderblock ${value.active}"><div class="leaderscore">${value.pos}) </div><div class="leadername">${value.usrname}</div><div class="leaderscore dx">${value.pts} <i class='fa fa-trophy'></i></div></div>`)
    );
  }
  lbconstruct(s){
    this.setState({leaderboard:this.state.leaderboard+=s});
  }

  render() {
    return (
      <div>
        <div className="leaderboard">
          <div dangerouslySetInnerHTML={{__html:this.state.leaderboard}}/>
        </div>
      </div>
    );
  }
}

Basically, if I have server data which has to be put inside html format for N loops, i couldn't find another way, so I'm wondering where I'm wrong.

1 Answer 1

3

Output your data into react elements in your render function:

class Leaderboard extends React.Component {
  constructor(props) {
    super(props);
    this.state={
      leaderboard: {},
    };
  }

  componentWillMount(){
    axiosCall.get('leaderboard.php', { params: { user:this.props.user } })
      .then(response => this.setState({ leaderboard: response.data }))
      .catch(console.log)
  }

  render() {
    const { leaderboard } = this.state
    return (
      <div>
        <div className="leaderboard">
          // .map returns a new array, which we have populated with the react elements
          { Object.keys(leaderboard).map((key) => {
            const value = leaderboard[key]
            return (
              <div key={key} class={`leaderblock ${value.active}`}>
                <div class="leaderscore">{value.pos}</div>
                <div class="leadername">{value.usrname}</div>
                <div class="leaderscore dx">
                  {value.pts}
                  <i class='fa fa-trophy'></i>
                </div>
              </div>
            )
          }) }
        </div>
      </div>
    );
  }
}

Doing it like this is what allows react to work, it can keep track of what elements are there, and if you add one, it can see the difference and just adds the one element to the end, rather than re-render everything.

Also note that if you are only fetching data once, it may make sense to use a "container" component which fetches your data and passes it in to your "dumb" component as a prop.

Sign up to request clarification or add additional context in comments.

1 Comment

.map((key,value)=> ...) is wrong. .map((key,index)=> ...) is correct. You'll need const value = this.state.leaderboard[key] or similar.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.