Home > Mobile >  ReactJS - useState updates value in top-level component but component doesn't re-render
ReactJS - useState updates value in top-level component but component doesn't re-render

Time:10-11

So I have a component that has a 'posts' variable passed in, which is managed by useState, and inside my App component I have some child components which use that variable:

function App() {

    const [posts, updatePosts] = useState([
        {
            title: "Cut the Grass",
            description: "Get it done before 4pm",
        },
        {
            title: "Purchase LOTR: DVD box set",
            description: "Extended Edition."
        }
    ])

    return (
        <div>
            <UserBar loggedInFlag = {userIsLoggedIn}/>
            <br /><br /><hr /><br />

            <CreateTodo todoList={posts} updatePosts = { () => updatePosts } />
            <input type="submit" value="test" onClick={() => console.log(posts)}/>
            <TodoList items={posts}/>

        </div>
    )
}
export default App;

There is logic inside my CreateTodo component that grabs fields for a new post, adds it to the posts array, and successfully updates the posts variable; I can test this with my "test" button on the second line there, which always prints out the correct todo list to the console, and it contains my new post. So this shows me that the update of posts inside the <CreateTodo> bubbles up to my App component just fine.

However, the <TodoList > component, which uses the same posts variable as above, does not re-render when the variable is updated. That component only displays the todolist (with the new value) when I change some code in the IDE, save, and then the page refreshes with the new list. How do I fix my useState so that whenever the post value is updated, it will automatically rerender the <TodoList > component?

For reference, here is how that component looks.

export default function TodoList({ items}) {
    
    return (
        <div>
            {items.map( (todo, i) => <Todo {...todo} title={todo.title} description={todo.description} key={'post-'   i} />)}
        </div> 
    )

and, here is how the CreateTodo component looks:

import React from 'react'

export default function CreateTodo ({todoList, updatePosts}) {
     return (
          <form onSubmit={e => e.preventDefault()} style={{borderStyle:"solid"}}>

             <div>
                 <br/>
                 <label htmlFor="create-title">Title:</label>
                 <br/>
                 <input type="text" id="create-title" />

                 <br/>
                 <label htmlFor="create-title">Description (optional):</label>
                 <br/>
                 <input type="text" id="create-description" />
                 <br/>

                 <br/>
             </div>

             <input type="submit" value="Create" onClick={() => addTodo({todoList, updatePosts})}/>
         </form>   
          )
 }
 

 function addTodo({todoList, updatePosts}){
    let newTodo = {
        title : document.getElementById("create-title").value,
        description : document.getElementById("create-description").value,
        dateCreated : Date.now(),
        completed : false,
        dateCompleted : null
    }

    todoList.push(newTodo);
    updatePosts(todoList);

    document.getElementById("create-title").value = "";
    document.getElementById("create-description").value = "";

    // alert(`"${newTodo.title}" has been added to list`);
 }

CodePudding user response:

First, you need to pass the updatePosts function directly to your component:

<CreateTodo todoList={posts} updatePosts={updatePosts} />

Second, in your addTodo function, you're mutating the state object directly:

// Don't do this
todoList.push(newTodo);

All you need to do is to call updatePosts with the updated list:

updatePosts([
  ...todoList,
  newTodo,
]);

CodePudding user response:

You need to add () in the updatePosts prop

updatePosts={() => updatePosts()} or just updatePosts={updatePosts}

  • Related