Issue
So I'm fairly new to React and I'm now learning about routing. I created a simple app where I have two pages (Home and Todos) and I route between them through a Tab Bar. On one page I have a list where I can add data to it through events. Code is:
App.tsx
const router = useNavigate();
return (
<div className='app-main-cont'>
<div className='app-main-content'>
<Routes>
<Route path='/' element={<Navigate replace to='/home'/>}></Route>
<Route path='/home' element={<Home/>}></Route>
<Route path='/todos' element={<Todos/>}></Route>
</Routes>
</div>
<nav className='tab-bar'>
<div className='tab' onClick={()=>{router('/home')}}>HOME</div>
<div className='tab' onClick={()=>{router('/todos')}}>TODOS</div>
</nav>
</div>
)
Todos.tsx
function Todos() {
const [todosList, addNewTodo] = useState<TodoClass[]>([]);
const [inputValue, changeInputValue] = useState('');
const handleChangeEvent = (event: ChangeEvent) => {
const val = (event.target) as HTMLInputElement;
changeInputValue(val.value);
}
const handleAddTaskButtonClick = (event: any) => {
let obj: TodoClass = {name: inputValue};
addNewTodo([...todosList, obj]);
}
return (
<div className='todos-cont'>
<div className='list'>
{todosList.map((item, index) => {
return (
<div className='list-item' key={index}>
{item.name}
</div>
)
})}
</div>
<div className='add-todo'>
<div className='input-todo'>
<input type="text" placeholder='Input a todo task' onChange={handleChangeEvent} />
</div>
<div className='add-btn' onClick={handleAddTaskButtonClick}>
ADD
</div>
</div>
</div>
)
}
Now the problem is that when I add data to the list, go to Home and then get back to the Todos page through the navbar, the data that I added before is lost. Why is that? Am I missing something? Thanks! (The same thing using Ionic-React is not happening that's why I suppose that I am missing something)
Solution
The todos
state is only local state in the Todos
component, and when you navigate from "/todos"
to "/home"
the todos route is no longer matched so the Todos
component unmounts. React state is not persisted.
You could lift the todos
state up to a common ancestor that remains mounted regardless of what route is matched and pass the state down as props.
Example:
const navigate = useNavigate();
const [todosList, addNewTodo] = useState<TodoClass[]>([]);
const addTodo = (todo: TodoClass) => {
addNewTodo(todos => [...todos, todo]);
};
return (
<div className='app-main-cont'>
<div className='app-main-content'>
<Routes>
<Route path="/" element={<Navigate replace to="/home" />} />
<Route path="/home" element={<Home />} />
<Route
path="/todos"
element={(
<Todos
todosList={todosList}
addTodo={addTodo}
/>
)}
/>
</Routes>
</div>
<nav className='tab-bar'>
<div className='tab' onClick={() => router("/home")}>HOME</div>
<div className='tab' onClick={() => router("/todos")}>TODOS</div>
</nav>
</div>
);
interface TodosProps {
todosList: TodoClass[];
addTodo: TodoClass => void;
}
function Todos({ addTodo, todoList }: TodosProps) {
const [inputValue, changeInputValue] = useState('');
const handleChangeEvent = (event: ChangeEvent) => {
const val = (event.target) as HTMLInputElement;
changeInputValue(val.value);
}
const handleAddTaskButtonClick = (event: any) => {
addTodo({ name: inputValue });
}
return (
<div className='todos-cont'>
<div className='list'>
{todosList.map((item, index) => (
<div className='list-item' key={index}>
{item.name}
</div>
))}
</div>
<div className='add-todo'>
<div className='input-todo'>
<input type="text" placeholder='Input a todo task' onChange={handleChangeEvent} />
</div>
<div className='add-btn' onClick={handleAddTaskButtonClick}>
ADD
</div>
</div>
</div>
);
};
An alternative might also be to persist the local todosList
state to localStorage, and initialize the state from localStorage. When the component is mounted the todosList
state is set to the persisted value or the initial state value []
. Use a useEffect
hook to persist state to localStorage when it updates.
Example:
function Todos() {
const [todosList, addNewTodo] = useState<TodoClass[]>(() => {
// Return persisted state value or default initial value
return JSON.parse(localStorage.getItem("todosList")) ?? [];
});
useEffect(() => {
if (todosList) {
// Persist todosList state to localStorage
localStorage.setItem("todosList", JSON.stringify(todosList));
}
}, [todosList]);
...
return (
...
);
}
Answered By - Drew Reese
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.