Как не передавать много функций через пропсы в React?
У меня вопрос в том, как избавиться от момента, когда у тебя в App.js куча функций и все их ты передаешь пропсами в разные элементы. Например вот так:
<TodoPanel
addFastTodo={addNewTodo}
addTodo={handleAddTodo}
completeAllTodos={completeAllTodos}
removeCompleteTodos={removeCompleteTodos}
/>
<TodoList
todos={sortedTodos}
onChangeStatus={toggleStatusTodo}
onRemove={removeTodo}
onSelect={selectTodo}
onPin={pinTodo}
/>
Вопрос, как лучше от этого подхода избавиться? Желательно не использовать сторонние библиотеки, чтобы лучше понять самому как это работать должно. Я попробовал реализовать свой хук, который большУю часть функций возвращает, но насколько я знаю, хуки не должны быть супер большими, да и + использование этого хука не удобно. Особенно деструктуризировать эту кучу функций.
Код useTodoState:
import { useState } from 'react';
const getSavedTodos = () => {
const savedTodos = localStorage.getItem("todos");
return savedTodos ? JSON.parse(savedTodos) : [];
};
const useTodoState = () => {
const [todos, setTodos] = useState(getSavedTodos());
return {
todos,
setTodos: setTodos,
addTodo: newTodo => setTodos(
[newTodo, ...todos]
),
removeTodo: id => setTodos(
prevTodos => prevTodos.filter(item => item.id !== id)
),
changeTodo: newTodo => setTodos(prevTodos =>
prevTodos.map(item => item.id === newTodo.id
? newTodo
: item)
),
toggleStatusTodo: id => setTodos(prevTodos =>
prevTodos.map(item => item.id === id
? { ...item, isComplete: !item.isComplete }
: item)
)
}
};
export default useTodoState;
В моей голове идея такая, создать какой-то глобальный объект в котором будут все эти функции и само состояние todos и импортировать этот объект всем элементам изменяющие этот todos но я не понимаю, как называется (наверняка есть название такому подходу в React) и как реализовать подобное по правилам React?
Полный код App.js:
import { useEffect, useMemo, useState } from "react";
import TodoList from "./components/TodoList";
import ModalWindow from './components/ModalWindow';
import TodoItemForm from "./components/TodoItemForm";
import useTodoState from "./hooks/useTodoState";
import { CSSTransition } from 'react-transition-group';
import TodoPanel from "./components/TodoPanel";
import "./App.css";
function App() {
const {
todos,
setTodos,
addTodo,
changeTodo,
removeTodo,
toggleStatusTodo
} = useTodoState();
const [openTodoInfo, setOpenTodoInfo] = useState(false);
const [selectedTodo, setSelectedTodo] = useState({});
const selectTodo = (item) => {
setSelectedTodo(item);
setOpenTodoInfo(true);
}
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);
const addNewTodo = (todoValue) => {
if (todoValue === "") return;
const todo = {
id: Date.now(),
isComplete: false,
isPin: false,
header: todoValue,
content: ""
};
addTodo(todo);
return todo;
}
const completeAllTodos = () => {
setTodos(todos.map(element => {return {...element, isComplete: true};}))
}
const removeCompleteTodos = () => {
setTodos(todos.filter(element => element.isComplete === false))
}
const handleAddTodo = (todoValue) => {
const todo = addNewTodo(todoValue);
if (todo) {
setSelectedTodo(todo);
setOpenTodoInfo(true);
}
}
const pinTodo = (id) => {
setTodos(prevTodos => prevTodos.map(item => {
return item.id === id ? { ...item, isPin: !item.isPin } : item;
}));
}
const _changeTodo = (newTodo) => {
changeTodo(newTodo);
setOpenTodoInfo(false);
}
const sortedTodos = useMemo(() => {
return [...todos].sort((left, right) => {
const comp = !left.isPin - !right.isPin;
return comp ? comp : right.id - left.id;
});
}, [todos]);
return (
<div className="container">
<CSSTransition
in={openTodoInfo}
classNames='modal'
timeout={150}
unmountOnExit
>
<ModalWindow
setVisible={setOpenTodoInfo}
className="modal_window"
>
<TodoItemForm
item={selectedTodo}
changeTodo={_changeTodo}
/>
</ModalWindow>
</CSSTransition>
<TodoPanel
addFastTodo={addNewTodo}
addTodo={handleAddTodo}
completeAllTodos={completeAllTodos}
removeCompleteTodos={removeCompleteTodos}
/>
<TodoList
todos={sortedTodos}
onChangeStatus={toggleStatusTodo}
onRemove={removeTodo}
onSelect={selectTodo}
onPin={pinTodo}
/>
</div>
);
}
export default App;
Ответы (2 шт):
Ребята из facebook не глупые и рекомендуют использовать Redux Toolkit. Так же возможно вам поможет построение компонента через классы а не через функции
В итоге мне понравилось решение с контекстом (понравилось в плане реализации и использования, насчет оптимизации не уверен, что хороший подход).
Так теперь выглядит мой TodoProvider.js:
import { createContext, useState, useContext, useEffect } from "react";
const TodoContext = createContext();
export const useTodos = () => useContext(TodoContext);
const defaultTodo = {
title: "",
detail: "",
done: false,
pin: false,
};
const getSavedTodos = () => {
const savedTodos = localStorage.getItem("todos");
return savedTodos ? JSON.parse(savedTodos) : [];
};
export default function TodoProvider({ children }) {
const [todos, setTodos] = useState(getSavedTodos());
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);
const addTodo = title => {
setTodos([
{
...defaultTodo,
id: Date.now(),
title: title,
},
...todos,
]);
};
const removeTodo = id => {
setTodos(todos.filter(todo => todo.id !== id));
};
const changeTodo = (id, changes) => {
setTodos(
todos.map(todo => {
return todo.id === id ? { ...todo, ...changes } : todo;
})
);
};
const providerValue = {
todos,
setTodos,
addTodo,
removeTodo,
changeTodo,
};
return (
<TodoContext.Provider value={providerValue}>
{children}
</TodoContext.Provider>
);
}
Оборачиваю компонент App в него так (index.js):
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import TaskProvider from "./providers/TodoProvider";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<TaskProvider>
<App />
</TaskProvider>
);
Пример использования например в TodoItem:
const { removeTodo, changeTodo } = useTodos();
const toggleDoneTodo = () => {
changeTodo(item.id, { done: !item.done });
};
const togglePinTodo = event => {
event.stopPropagation();
changeTodo(item.id, { pin: !item.pin });
};
const removeTodoClick = event => {
event.stopPropagation();
removeTodo(item.id);
};