I work on a todo app with React and things become clearer, but I struggle to undersand the "lifecycle". In VueJS I know a ComponentDidMount() hook, which would help me to solve this issue if I guess, but in React I can´t find it out.
I have an array of todos like this: const todos = [{description: "walk dog", done: false}]
This is the initial state of my app:
const [alltodos, handleTodos] = useState([]);
On load I use this useEffect hook to get data from localStorage.
useEffect(() => {
const items = localStorage.getItem("todos");
const parsed = JSON.parse(items);
handleTodos(parsed);
}, []);
I count my todos with this function:
const countTodos = () => {
const donetodos = alltodos.filter((item) => {
return !item.done;
});
countOpen(donetodos.length);
};
I update the count if a dependency changes:
useEffect(() => {
countTodos();
localStorage.setItem("todos", JSON.stringify(alltodos));
}, [alltodos]);
So what happens is that the counter starts with 0 and than "flickers" for a milisecond before it shows the number of todos which I get from localstorage.
Is there a way to prevent that behaviour? As far as I know the component gets rendered FIRST and then the useEffect hook gets triggered. How I render my component AFTER the data is pulled from localstorage?
The best way to do this would be with a lazy initial state. Also, cleaning up the variables and using a standardized [variable, setVariable] will save you headache debugging in the future.
const [alltodos, setAlltodos] = useState(() => {
const items = localStorage.getItem("todos");
const parsed = JSON.parse(items);
return parsed || "";
});
Initialize the allTodos state with null. As long as this state is null, render a notification or just return null to render nothing.
You can calculate the open todos count directly from the current alltodos state, without the need of useEffect.
const [alltodos, handleTodos] = useState(null);
useEffect(() => {
const items = localStorage.getItem("todos");
const parsed = items ? JSON.parse(items) : [];
handleTodos(parsed);
}, []);
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(alltodos));
}, [alltodos]);
if(alltodos === null) return 'Loading todos list';
// this is derived from state, so you don't have to create a state for it
const openTodosCount = alltodos.reduce((acc, o) => acc + !o.done, 0);
The fastest way would be to add new state that would be responsible for loading. For example
const [isLoading, setIsLoading] = useState(true);
set it initially on true and then after you do all your calculations change it to false.
Then you can depend on that state and show div with a text "Loading" or anything else but when isLoading would go to false it will show component elements.