1
votes

I just started to learn to react 2 days ago and I'm having a hard time with react's setstate method, all I know is use revstate parameter if want to change state based on previous state, and callback parameter to be executed right after the state change (please correct me if this wrong), so I just change the array content (which I render it using javascript's array.map) and I wish it renders right after the state is changed, it is changing but delayed, it only render after I do another click but the render method is called for any senpai out there thanks for the help.

Handle click for changing to render content based on index passed on my button "onClick"

class App extends React.Component {
     constructor(props){
        super(props)
        this.state = {
          clickeditem : -1
        }
    this.torender = [
      {
        display : "first",
        content : []
      },
      {
        display : "second",
        content : []
      }
    ]
}

  handleclick = (i) =>{
    this.setState(prevstate=>{
      if (prevstate.clickeditem === -1) {
        return {clickeditem : i}
      } else {
        return prevstate.clickeditem === i ? {clickeditem : -1} : {clickeditem : i}
      }
    },() => {
      return this.state.clickeditem === -1 ? (this.torender[0].content = [], this.torender[1].content = [])
        : (this.state.clickeditem === 0) ? (this.torender[0].content = ["torender-0 content","torender-0 content"],this.torender[1].content = [])
          : (this.state.clickeditem === 1) ? (this.torender[1].content = ["torender-1 content","torender-1 content"],this.torender[0].content = [])
            : null
    })
  } 

  render(){
    return(
      <div>
        <ul>
        {
        this.torender.map((item,index) => {
          return(
            <li key = {index}>
              {item.display}
                <ul>
                  {item.content.map((content,contentindex) => {
                    return(<li key = {contentindex}>{content}</li>)
                  })}
                </ul>  
            </li>
          )
        })
        }
        </ul>
        <button onClick={()=>this.handleclick(0)}>first-button</button>
        <button onClick={()=>this.handleclick(1)}>second-button</button>
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>
2
this.torender isn't part of react state, the component would need to rerender once more to see the mutation you did in the previous render cycle. If you make it part of state, or better, just compute it's value when you are rendering your UI then it should work.Drew Reese

2 Answers

2
votes

Refactor your code and Approach the simpler way

Actually, you shouldn't use the second param callback.

Whenever the state is changed, the life cycle of React Js will re-render it properly (See the image below to clarify in detail ^^!)

There are some things to note:

  • Move the content of each item in torender accordingly --> This is clearer about the initial data as well as it should not be mutated.

  • Default clickeditem is one of the items in torender, for example, the first item.

  • After that, you just control the content to be rendered in this way

    ___________ The condition to call renderContent() method ______________
    {index === this.state.clickeditem && this.renderContent(item)}
    
    _____________renderContent() looks like below_____________
    renderContent = (item) => {
    return (
       <ul>
         {item.content.map((content, contentindex) => {
           return <li key={contentindex}>{content}</li>;
         })}
       </ul>
     );
    };
    

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      clickeditem: 0
    };

    this.torender = [
      {
        display: "first",
        content: ["torender-0 content", "torender-0 content"]
      },
      {
        display: "second",
        content: ["torender-1 content", "torender-1 content"]
      }
    ];
  }

  handleclick = (index) => {
    this.setState({clickeditem: index});
  };

   renderContent = (item) => {
    return (
      <ul>
        {item.content.map((content, contentindex) => {
          return <li key={contentindex}>{content}</li>;
        })}
      </ul>
    );
  };

  render() {
    return (
      <div>
        <ul>
          {this.torender.map((item, index) => {
            return (
              <li key={index}>
                {item.display}
                {index === this.state.clickeditem && this.renderContent(item)}
              </li>
            );
          })}
        </ul>
        <button onClick={() => this.handleclick(0)}>first-button</button>
        <button onClick={() => this.handleclick(1)}>second-button</button>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"> </div>

enter image description here

1
votes

Issue

this.torender isn't part of react state, the component would need to rerender once more to see the mutation you did in the previous render cycle.

Solution

It is best to just compute it's value when you are rendering your UI then it should work as I suspect you meant it to.

  handleclick = (i) =>{
    this.setState(prevstate=>{
      if (prevstate.clickeditem === -1) {
        return { clickeditem: i }
      } else {
        return prevstate.clickeditem === i ? { clickeditem: -1 } : { clickeditem: i }
      }
    })
  } 

  render(){
    const { clickeditem } = this.state;

    let torender = [
      {
        display : "first",
        content : []
      },
      {
        display : "second",
        content : []
      }
    ];

    if (clickeditem === -1) {
      torender[0].content = [];
      torender[1].content = [];
    } else if (clickeditem === 0) {
      torender[0].content = ["torender-0 content","torender-0 content"];
      torender[1].content = [];
    } else if (clickeditem === 1) {
      torender[1].content = ["torender-1 content","torender-1 content"];
      torender[0].content = [];
    } else {
      torender = []; // <-- render nothing
    }

    return(
      <div>
        <ul>
          {torender.map((item,index) => {
            return(
              <li key = {index}>
                {item.display}
                  <ul>
                    {item.content.map((content, contentindex) => (
                      <li key={contentindex}>{content}</li>;
                    ))}
                  </ul>  
              </li>
            )
          })
        }
        </ul>
        <button onClick={()=>this.handleclick(0)}>first-button</button>
        <button onClick={()=>this.handleclick(1)}>second-button</button>
      </div>
    )
  }
}