1

I am trying to convert the following snippet of code to use functional based components.

class IssueTable extends React.Component {
  constructor() {
    super();
    this.state = { issues: [] };
    setTimeout(() => {
      this.createIssue(sampleIssue);
    }, 2000);
  }

  componentDidMount() {
    this.loadData();
  }

  loadData() {
    setTimeout(() => {
      this.setState({ issues: initialIssues });
    }, 500);
  }

  createIssue(issue) {
    issue.id = this.state.issues.length + 1;
    issue.created = new Date();
    const newIssueList = this.state.issues.slice();
    newIssueList.push(issue);
    this.setState({ issues: newIssueList });
  }

  render() {
    const issueRows = this.state.issues.map(issue =>
      <IssueRow key={issue.id} issue={issue} />
    );

    return (
      <table className="bordered-table">
        <thead>
          <tr>
            <th>ID</th>
            <th>Status</th>
            <th>Owner</th>
            <th>Created</th>
            <th>Effort</th>
            <th>Due Date</th>
            <th>Title</th>
          </tr>
        </thead>
        <tbody>
          {issueRows}
        </tbody>
      </table>
    );
  }
}

While trying to convert it the table somehow keeps rerendering and I get the key not unique error. I used useEffect but I dont know how to use constructor like functionality. How to use functions to achieve it. (issueRows is a component which takes care of the row data in the table). The functional version is shown below:

function IssueTable(){

  const [issue1, setIssues] = React.useState([]);

  React.useEffect(()=>{loadData()},[]);

    setTimeout(() => {
      createIssue(sampleIssue);
    }, 2000);
  


  function loadData() {
    setTimeout(() => {
      setIssues(issues);
    }, 500);
  }


  function createIssue(issue) {
    issue.id = issue1.length+1;
    issue.created = new Date();
    const newIssueList = issue1.slice();
    newIssueList.push(issue);
    setIssues(newIssueList);
    }
    

    
    const issueRows = issue1.map(issue =>
        <IssueRow key={issue.id} issue={issue} />
      )

    return <table className="bordered-table">
    <thead>
      <tr>
        <th>ID</th>
        <th>Status</th>
        <th>Owner</th>
        <th>Created</th>
        <th>Effort</th>
        <th>Due Date</th>
        <th>Title</th>
      </tr>
    </thead>
    <tbody>
      {issueRows}
    </tbody>
  </table>
}

function IssueRow(props){
    return(
        <tr>
            <td>{props.issue.id}</td>
            <td>{props.issue.status}</td>
            <td>{props.issue.owner}</td>
            <td>{props.issue.created.toDateString()}</td>
            <td>{props.issue.effort}</td>
            <td>{props.issue.due?props.issue.due.toDateString():""}</td>
            <td>{props.issue.title}</td>
        </tr>
    );
}
2
  • Put the functional version here too Commented Jul 7, 2020 at 3:17
  • What are sampleIssue and issues? They are never defined in your functional component. Commented Jul 7, 2020 at 7:21

3 Answers 3

2

Check out this working sandbox: https://codesandbox.io/s/confident-taussig-tg3my?file=/src/App.js

One thing to note is you shouldn't base keys off of linear sequences [or indexes] because that can mess up React's tracking. Instead, pass the index value from map into the IssueRow component to track order, and give each issue its own serialized id.

 const issueRows = issues.map((issue, index) => (
    <IssueRow key={issue.id} issue={issue} index={index} />
  ));

Also, the correct way to update state that depends on previous state, is to pass a function rather than an object:

 setIssues(state => [...state, issue]);

You essentially iterate previous state, plus add the new issue.

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

Comments

1

This should be inside another React.useEffect():

setTimeout(() => {
  createIssue(sampleIssue);
}, 2000);

Otherwise, it will run every render in a loop. As a general rule of thumb there should be no side effects outside of useEffect.

1 Comment

Thanks for answering!! I wrapped the setTimeout into a function and then called it inside useEffect. It works without any errors but the problem is it somehow loses access to the previous data it had in the array issue1 and only renders one row. Could you please look into that and tell me how to get the current value instead of it using stale data. I am new to async js and dont know how to approach it
1

Functional component can look like this:

function IssueTable() {
  const [issue1, setIssues] = React.useState([]);
  const createIssue = React.useCallback(
    function createIssue(issue) {
      //passing callback to setIssues
      //  so useCallback does not depend
      //  on issue1 and does not have stale
      //  closure for issue1
      setIssues((issues) => {
        issue.id = issues.length + 1;
        issue.created = new Date();
        const newIssueList = issues.slice();
        newIssueList.push(issue);
        return newIssueList;
      });
    },
    []
  );
  React.useEffect(() => {
    setTimeout(() => {
      createIssue(sampleIssue);
    }, 2000);
  }, [createIssue]);
  React.useEffect(() => {
    setTimeout(() => {
      setIssues(initialIssues);
    }, 500);
  }, []);

  const issueRows = issue1.map((issue) => (
    <IssueRow key={issue.id} issue={issue} />
  ));

  return (
    <table className="bordered-table">
      <thead>
        <tr>
          <th>ID</th>
          <th>Status</th>
          <th>Owner</th>
          <th>Created</th>
          <th>Effort</th>
          <th>Due Date</th>
          <th>Title</th>
        </tr>
      </thead>
      <tbody>{issueRows}</tbody>
    </table>
  );
}

Comments

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.