skip to Main Content

I’ve seen some solutions for other frameworks but none apply to me. I have two main issues here.

1- I have to click the button twice to get it to fire. Granted I am using the click even’t to mutate the state, change the row color, AND also update a list on the bottom of the table. I don’t think none of these are running asynchronously. So maybe that’s the problem and I would like to know how to go around that.

2- Second problem is related to the first. I passed the dependency in the useEffect function so that the list on the bottom is updated as soon as the click event changes the state. But it’s throwing an infinite loop, as if the state was constantly changing. A console log doesn’t reflect the state forever changing. So, I’m stooped.

Some basically I have an array (API in real world), and I’m filtering out the learned and unlearned questions

const questions = [
  { 
    question: "Question 1",
    answer: "My answer 1",
    learned: true
  },
  {
    question: "Question 2",
    answer: "My answer 2",
    learned: false
  },
  {
    question: "Question 3",
    answer: "My answer 3",
    learned: true
  },
  {
    question: "Question 4",
    answer: "My answer 4",
    learned: true
  },
  {
    question: "Question 5",
    answer: "My answer 5",
    learned: false
  },
  {
    question: "Question 6",
    answer: "My answer 6",
    learned: true
  }
]

const learned = questions.filter(question => question.learned === true)
const unlearned = questions.filter(question => question.learned === false)

Inside my component I have the state, the function to populate a list, useEffect, and a click event function that contains the callback to populate the list, as well as toggling the UI from learned to unlearned, which of course, it updates the list accordingly

const [unlearned, setUnlearned] = useState([]);

const populateUnlearned = () => {
  setUnlearned(questions.filter(question => question.learned === false))
}

useEffect(() => {
  populateUnlearned()
},[]) //unlearned here causes the list to populate fine but an infinite loop

const handleLearned = (index) => {  
  populateUnlearned()
  setIsLearned(
    !isLearned, 
    questions.filter(question => question.learned === false), 
    questions[index].learned = !isLearned
  )
}

And the rest is just html stuff. I have a sandbox here for you to mess with showing the problem.

I appreciate any help. Thanks in advance

2

Answers


  1. Use

    questions[index].learned = !questions[index].learned
    

    instead of

    questions[index].learned = !isLearned
    

    You can’t use single boolean to hold the current state of learned on each row. You don’t even need a separate state for that, you are already holding it in questions[index].learned, you just have to invert it

    It is not the React way of doing so, but it will work, you just have to be sure that there is a rerender after setting the new value to questions[index].learned

    And setIsLearned does not accept that many parameters, it accepts only 1 to change its value

    And not everything have to be state. You don’t need unlearned state as well. You already have the array questions, you can filter it while rendering. If it is an expensive operation, instead of moving it to a separate state, you can use useMemo

    You can remove isLearned and unlearned states and move questions to its own state

    const unlearned = questions.filter(question => question.learned === false) 
    

    This is just enough, no need of state

    Login or Signup to reply.
  2. here are a few updates I would recommend,

    1. State variables

    In Vocabulary.js, you attempt to modify the entries in questions. However, this variable is not a piece of state. Keep in mind that only variables created based on useState (and useReducer) can be updated within your component

    1. Avoid performing updates based on index

    here you can find information that explains in detail why you should not be using the index

    // rename questions to original_questions
    const original_questions = [
     {
      ...
    
    // compute them within the component
    //const learned = questions.filter((question) => question.learned === true);
    //const unlearned = questions.filter((question) => question.learned === false);
    
    const Vocabulary = () => {
      const [page, setPage] = useState(0);
      const [rowsPerPage, setRowsPerPage] = useState(5);
    
      // unneeded since each entry in "questions" keeps track that flag
      // const [isLearned, setIsLearned] = useState(false);
    
      // questions get initiated as a piece of state
      // based on the original_questions (this can be a prop)
      const [questions, setQuestions] = useState(original_questions);
      const [unlearned, setUnlearned] = useState([]);
    
      // effects computes unlearned
      // whenever questions state changes
      useEffect(() => {
        populateUnlearned();
      }, [questions]);
    
      // other functions...
      
    
      // instead of index,
      // handleLearned expects the row data 
      // (ideally row should have a unique id e.g row.id)
      // the update uses the `question` property to uniquely identify
      // the question to update 
      const handleLearned = (row) => {
        setQuestions((currentQuestions) =>
          currentQuestions.map((entry) => {
            if (row.question === entry.question) { 
              entry.learned = !entry.learned;
            }
            return entry;
          })
        );
        //populateUnlearned();
        //setIsLearned(
        //  !isLearned,
        //  questions.filter((question) => question.learned === false),
        //  (questions[index].learned = !isLearned)
        //);
        // setUnlearned(questions.filter(question => question.learned === false))
        // questions[index].learned = !isLearned
      };
    
    
      // more code ...
      // button onClick callback now gets the row instead of index
      <StyledTableCell align="center">
                      <Button
                        onClick={() => handleLearned(row)} 
                        variant="contained"
                        color={row.learned ? "error" : "success"}
                        size="small"
                        sx={{ mr: 2 }}
                      >
                        {row.learned ? "No" : "Yes"}
                      </Button>
                    </StyledTableCell>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search