Как не передавать много функций через пропсы в 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 шт):

Автор решения: John Belousov

Ребята из facebook не глупые и рекомендуют использовать Redux Toolkit. Так же возможно вам поможет построение компонента через классы а не через функции

→ Ссылка
Автор решения: Bonifacy

В итоге мне понравилось решение с контекстом (понравилось в плане реализации и использования, насчет оптимизации не уверен, что хороший подход).

Так теперь выглядит мой 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);
};
→ Ссылка