OiO.lk Community platform!

Oio.lk is an excellent forum for developers, providing a wide range of resources, discussions, and support for those in the developer community. Join oio.lk today to connect with like-minded professionals, share insights, and stay updated on the latest trends and technologies in the development field.
  You need to log in or register to access the solved answers to this problem.
  • You have reached the maximum number of guest views allowed
  • Please register below to remove this limitation

Component unmounting causing property to reset

  • Thread starter Thread starter Sizmon
  • Start date Start date
S

Sizmon

Guest
I am making a Task application for myself to improve my workflow. I had gotten around to introducing a timer to the tasks which upon reaching 0 sets the task as overdue. Everything looked fine until I realised the timer disappears when the component unmounts and remounts (due to a filtering process I use). I am self teaching myself react and am quite new to it. Everything has been going pretty smooth but I have been stuck on this roadbump for a while now because of how much state is actually handling the Timer.

I will provide you with a brief overview of my code in the simplest way I possibly can as it is quite a lot.

THE INITIAL STRUCTURE OF A TASK.

Code:
function createTodo(todo) {
         if (!todo) return alert('Please enter a task')
         else {
             setTodos([...todos, 
                 {id: uuidv4(),
                 task: todo, // This will be used to display the users input task
                 description: '', // This will be used to describe the task
                 catagories: [], // This will be used to filter todos by catagory
                 completed: false, // This will be used to mark a task as completed or in progress
                 isEditing: false, // This will be state to determine if a task is being edited
                 priority: false, // This will be state to determine if a task is being edited
                 timer: false, // This will be used to set a timer for a task
                 overdue: false, // This will check whether a timer has run out and a task is overdue
                 }])
             }
     }

THE LIST WHICH DISPLAYS THE TASKS This is where the Tasks are mapped and where the issue is occuring due to the filtering unmounting the components

Code:
const filteredTodos = todos.filter(todo => {
        // Check if the todo starts with the search term
        let matchesSearchTerm = searchTerm === "" || todo.task.toLowerCase().startsWith(searchTerm.toLowerCase());

        // Check if the todo matches the checkbox conditions
        let matchesPriority = !priorityChecked || todo.priority;
        let matchesOverdue = !overdueChecked || todo.overdue;
        let matchesCompleted = !completedChecked || todo.completed;

        // Return true if all conditions are met
        return matchesSearchTerm && matchesPriority && matchesCompleted && matchesOverdue;
    });

{filteredTodos.length > 0 ? (
                        filteredTodos.map((todo, index) => (
                            <TodoItem
                                key={index}
                                todo={todo}
                                editTodo={editTodo}
                                editingItemId={editingItemId}
                                editDescription={editDescription}
                                editTitle={editTitle}
                                deleteTodo={deleteTodo}
                                changePriority={changePriority}
                                markComplete={markComplete}
                                createTimer={createTimer}
                                changeOverdue={changeOverdue}
                                updateTimer={updateTimer}
                            />
                        ))

THE TODO ITEM COMPONENT IS IRRELEVANT LETS JUST ASSUME ITS THIS

Code:
<div>
 <Timer 
   todo={todo}
                            timerActive={timerActive}
                            setTimerActive={setTimerActive}
                            createTimer={createTimer}
                            changeOverdue={changeOverdue}
                            updateTimer={updateTimer}
 />
</div>

THIS IS THE TIMER COMPONENT ITSELF

Code:
import CountdownTimer from './CountdownTimer';

export default function Timer({ todo, createTimer, timerActive, setTimerActive, changeOverdue, updateTimer}) {

    // Timer Parameters
    const timerMinute = 60;
    const timerHour = 3600;
    const timerDay = 86400;
    const timerWeek = 604800;

    // Timer 
    const [timerInput, setTimerInput] = useState(0)
    const [timeLeft, setTimeLeft] = useState(0);
    const [timerType, setTimerType] = useState(timerMinute);
    const [timerMenu, setTimerMenu] = useState(false);
    const [userActivatedTimer, setUserActivatedTimer] = useState(false);

    const selectedTimeType = timerType === timerMinute ? "Minutes" : timerType === timerHour ? "Hours" : timerType === timerDay ? "Days" : "Weeks";

    useEffect(() => {
        let timer = null;

        if (timerActive && timeLeft > 0) {
            timer = setInterval(() => {
                setTimeLeft(timeLeft => timeLeft - 1);
                updateTimer(todo.id, timeLeft - 1);
            }, 1000);
        } else {
            clearInterval(timer);
        }

        return () => clearInterval(timer);
    }, [timerActive, timeLeft]);

return (
        <div className="flex flex-col relative justify-center items-center bg-zinc-800 rounded-lg p-2">
            {timerActive ? (
                <>
                    <p className='text-white p-1'>TASK TIMER</p>
                    <CountdownTimer 
                    todo={todo}
                    timeLeft={timeLeft} 
                    userActivatedTimer={userActivatedTimer} 
                    changeOverdue={changeOverdue} />
                </>
            ) : ( THIS IS AN INPUT FOR THE USER TO START THE TIMER IF NOT STARTED ))

FINAL COMPONENT WHICH IS THE COUNTDOWN TIMER THAT IS USED TO DISPLAY THE TIMER CONVERTED INTO ACTUAL TIME FOR THE USER

Code:
import React, { useEffect, useState } from 'react';

const CountdownTimer = ({todo, timeLeft, userActivatedTimer, changeOverdue }) => {
    const [time, setTime] = useState({
        days: 0,
        hours: 0,
        minutes: 0,
        seconds: 0,
    });

    useEffect(() => {
        const intervalId = setInterval(() => {
            if (timeLeft <= 0) {
                if (userActivatedTimer) {
                    changeOverdue(todo.id);
                }
                clearInterval(intervalId);
            } else {
            timeLeft--;
            
            const seconds = (timeLeft % 60).toString().padStart(2, '0');
            const minutes = Math.floor((timeLeft / 60) % 60).toString().padStart(2, '0');
            const hours = Math.floor((timeLeft / 3600) % 24).toString().padStart(2, '0');
            const days = Math.floor((timeLeft / 86400)).toString().padStart(2, '0');

            setTime({
                days,
                hours,
                minutes,
                seconds,
            });
            }
        }, 1000);

        return () => clearInterval(intervalId);
    }, [timeLeft]);

    return (
        <div className='bg-amber-500 p-2 rounded-lg'>
            <p className='text-white'>{time.days}:{time.hours}:{time.minutes}:{time.seconds}</p>
        </div>
    );
};

export default CountdownTimer;

Essentially everything works as intended until I unmount the todoItem, as this resets the timer in the TodoItem. So I would like to make it so that the TodoItem retains its timer property even on unmounting (For some reason all the other data from the todoItem remains, its just the timer which is reset.) I have read into this quite a lot and there have been a lot of suggestions to use Context or Redux, instead of just prop drilling by raising the timer states, I dont know much at the moment about Context or Redux as I haven't needed to use it yet. So I am essentially looking for an expert opinion on how to handle a situation like this. I don't expect or need a full solution as it is too much code to evaluate just a nudge in the right direction of how to handle this issue. (Also bare in mind that I have multiple tasks with their own individual timers)

<p>I am making a Task application for myself to improve my workflow. I had gotten around to introducing a timer to the tasks which upon reaching 0 sets the task as overdue. Everything looked fine until I realised the timer disappears when the component unmounts and remounts (due to a filtering process I use). I am self teaching myself react and am quite new to it. Everything has been going pretty smooth but I have been stuck on this roadbump for a while now because of how much state is actually handling the Timer.</p>
<p>I will provide you with a brief overview of my code in the simplest way I possibly can as it is quite a lot.</p>
<p><strong>THE INITIAL STRUCTURE OF A TASK.</strong></p>
<pre><code>function createTodo(todo) {
if (!todo) return alert('Please enter a task')
else {
setTodos([...todos,
{id: uuidv4(),
task: todo, // This will be used to display the users input task
description: '', // This will be used to describe the task
catagories: [], // This will be used to filter todos by catagory
completed: false, // This will be used to mark a task as completed or in progress
isEditing: false, // This will be state to determine if a task is being edited
priority: false, // This will be state to determine if a task is being edited
timer: false, // This will be used to set a timer for a task
overdue: false, // This will check whether a timer has run out and a task is overdue
}])
}
}
</code></pre>
<p><strong>THE LIST WHICH DISPLAYS THE TASKS</strong>
<em>This is where the Tasks are mapped and where the issue is occuring due to the filtering unmounting the components</em></p>
<pre><code>const filteredTodos = todos.filter(todo => {
// Check if the todo starts with the search term
let matchesSearchTerm = searchTerm === "" || todo.task.toLowerCase().startsWith(searchTerm.toLowerCase());

// Check if the todo matches the checkbox conditions
let matchesPriority = !priorityChecked || todo.priority;
let matchesOverdue = !overdueChecked || todo.overdue;
let matchesCompleted = !completedChecked || todo.completed;

// Return true if all conditions are met
return matchesSearchTerm && matchesPriority && matchesCompleted && matchesOverdue;
});

{filteredTodos.length > 0 ? (
filteredTodos.map((todo, index) => (
<TodoItem
key={index}
todo={todo}
editTodo={editTodo}
editingItemId={editingItemId}
editDescription={editDescription}
editTitle={editTitle}
deleteTodo={deleteTodo}
changePriority={changePriority}
markComplete={markComplete}
createTimer={createTimer}
changeOverdue={changeOverdue}
updateTimer={updateTimer}
/>
))
</code></pre>
<p><strong>THE TODO ITEM COMPONENT IS IRRELEVANT LETS JUST ASSUME ITS THIS</strong></p>
<pre><code><div>
<Timer
todo={todo}
timerActive={timerActive}
setTimerActive={setTimerActive}
createTimer={createTimer}
changeOverdue={changeOverdue}
updateTimer={updateTimer}
/>
</div>
</code></pre>
<p><strong>THIS IS THE TIMER COMPONENT ITSELF</strong></p>
<pre><code>import CountdownTimer from './CountdownTimer';

export default function Timer({ todo, createTimer, timerActive, setTimerActive, changeOverdue, updateTimer}) {

// Timer Parameters
const timerMinute = 60;
const timerHour = 3600;
const timerDay = 86400;
const timerWeek = 604800;

// Timer
const [timerInput, setTimerInput] = useState(0)
const [timeLeft, setTimeLeft] = useState(0);
const [timerType, setTimerType] = useState(timerMinute);
const [timerMenu, setTimerMenu] = useState(false);
const [userActivatedTimer, setUserActivatedTimer] = useState(false);

const selectedTimeType = timerType === timerMinute ? "Minutes" : timerType === timerHour ? "Hours" : timerType === timerDay ? "Days" : "Weeks";

useEffect(() => {
let timer = null;

if (timerActive && timeLeft > 0) {
timer = setInterval(() => {
setTimeLeft(timeLeft => timeLeft - 1);
updateTimer(todo.id, timeLeft - 1);
}, 1000);
} else {
clearInterval(timer);
}

return () => clearInterval(timer);
}, [timerActive, timeLeft]);

return (
<div className="flex flex-col relative justify-center items-center bg-zinc-800 rounded-lg p-2">
{timerActive ? (
<>
<p className='text-white p-1'>TASK TIMER</p>
<CountdownTimer
todo={todo}
timeLeft={timeLeft}
userActivatedTimer={userActivatedTimer}
changeOverdue={changeOverdue} />
</>
) : ( THIS IS AN INPUT FOR THE USER TO START THE TIMER IF NOT STARTED ))
</code></pre>
<p><strong>FINAL COMPONENT WHICH IS THE COUNTDOWN TIMER THAT IS USED TO DISPLAY THE TIMER CONVERTED INTO ACTUAL TIME FOR THE USER</strong></p>
<pre><code>import React, { useEffect, useState } from 'react';

const CountdownTimer = ({todo, timeLeft, userActivatedTimer, changeOverdue }) => {
const [time, setTime] = useState({
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
});

useEffect(() => {
const intervalId = setInterval(() => {
if (timeLeft <= 0) {
if (userActivatedTimer) {
changeOverdue(todo.id);
}
clearInterval(intervalId);
} else {
timeLeft--;

const seconds = (timeLeft % 60).toString().padStart(2, '0');
const minutes = Math.floor((timeLeft / 60) % 60).toString().padStart(2, '0');
const hours = Math.floor((timeLeft / 3600) % 24).toString().padStart(2, '0');
const days = Math.floor((timeLeft / 86400)).toString().padStart(2, '0');

setTime({
days,
hours,
minutes,
seconds,
});
}
}, 1000);

return () => clearInterval(intervalId);
}, [timeLeft]);

return (
<div className='bg-amber-500 p-2 rounded-lg'>
<p className='text-white'>{time.days}:{time.hours}:{time.minutes}:{time.seconds}</p>
</div>
);
};

export default CountdownTimer;
</code></pre>
<p>Essentially everything works as intended until I unmount the todoItem, as this resets the timer in the TodoItem. So I would like to make it so that the TodoItem retains its timer property even on unmounting (For some reason all the other data from the todoItem remains, its just the timer which is reset.) I have read into this quite a lot and there have been a lot of suggestions to use Context or Redux, instead of just prop drilling by raising the timer states, I dont know much at the moment about Context or Redux as I haven't needed to use it yet. So I am essentially looking for an expert opinion on how to handle a situation like this. I don't expect or need a full solution as it is too much code to evaluate just a nudge in the right direction of how to handle this issue. (Also bare in mind that I have multiple tasks with their own individual timers)</p>
 

Latest posts

Top