JimmyD
JimmyD

Reputation: 18

React setState of useState hook successfully updating state, but not re-rendering component

I'm trying to update my old Todo List React app so that it uses hooks, and can't figure this out for the life of me. setTodos is successfully updating the state on load as shown in React developer tools, but the component doesn't re-render and a blank list of todos is displayed on the screen. Only when I add a Todo to the list does it re-render and all of the todos show up. Here's what I've got:

const App = () => {

// Todos are the only state in App
const [ todos, setTodos ] = useState([]);

// Fetches Todos from Firestore database on load
useEffect(() => {
    const initialState = [];
    dbTodos.get().then((snapshot) => {
        snapshot.forEach((doc) => {
            const currentTodo = doc.data();
            currentTodo['id'] = doc.id;
            initialState.push(currentTodo);
        });
    });
    setTodos(initialState);
    // eslint-disable-next-line
}, []);

// Add Todo
const addTodo = (title) => {
    const newTodo = {
        title     : title,
        completed : false
    };
    dbTodos.add(newTodo).then((doc) => {
        newTodo['id'] = doc.id;
        setTodos([ ...todos, newTodo ]);
    });
};

Here is the code I used before implementing hooks:

componentDidMount() {
    dbTodos.get().then((snapshot) => {
        snapshot.forEach((doc) => {
            const currentTodo = doc.data();
            currentTodo['id'] = doc.id;
            setState({ ...state, todos: [ ...state.todos, currentTodo ] });
        });
    });
};

Upvotes: 0

Views: 1236

Answers (3)

Kirill Skomarovskiy
Kirill Skomarovskiy

Reputation: 1575

In useEffect you create empty array. Make a request. set empty array to state. When request was fulfilled you make changes in the array by ref. Therefore you see you array in dev tool and react does'n rerender you component.

const App = () => {

// Todos are the only state in App
const [ todos, setTodos ] = useState([]);

// Fetches Todos from Firestore database on load
useEffect(() => {
    dbTodos.get().then((snapshot) => {
        setTodos(snapshot.map((doc) => ({
          id: doc.id,
          ...doc.data(),
        })));
    });
}, []);

// Add Todo
const addTodo = (title) => {
    const newTodo = {
        title     : title,
        completed : false
    };
    dbTodos.add(newTodo).then((doc) => {
        newTodo['id'] = doc.id;
        setTodos([ ...todos, newTodo ]);
    });
};

Upvotes: 0

Ferin Patel
Ferin Patel

Reputation: 3998

When you setState in useEffect then you have to add setState in dependency. Instead of doing that use another arrow function and call it from useEffect.

use the code below

const App = () => {

// Todos are the only state in App
const [ todos, setTodos ] = useState([]);

// Fetches Todos from Firestore database on load
const fetchData = () => {
    const initialState = [];
    dbTodos.get().then((snapshot) => {
        snapshot.forEach((doc) => {
            const currentTodo = doc.data();
            currentTodo['id'] = doc.id;
            initialState.push(currentTodo);
        });
    });
    setTodos(initialState);
}

useEffect(() => {
  fetchData();
}, []);

// Add Todo
const addTodo = (title) => {
    const newTodo = {
        title     : title,
        completed : false
    };
    dbTodos.add(newTodo).then((doc) => {
        newTodo['id'] = doc.id;
        setTodos([ ...todos, newTodo ]);
    });
};

Upvotes: 0

Tonoslav
Tonoslav

Reputation: 519

You should move your setTodos in UseEffect

useEffect(() => {
    dbTodos.get().then((snapshot) => {
        const initialState = [];    // Also this you can move here

        snapshot.forEach((doc) => {
            const currentTodo = doc.data();
            currentTodo['id'] = doc.id;
            initialState.push(currentTodo);
        });

        setTodos(initialState);   /// here
    });
}, []);

Upvotes: 1

Related Questions