Совместное использование состояния между компонентами в React

#reactjs

Вопрос:

Я пытаюсь создать панель мониторинга с помощью React. Существует несколько компонентов: приложение, блок и другие дочерние компоненты, назовем их Контентом.

Блок-это простая загрузочная карточка с названием, классами и некоторыми css. В приложении я вызываю блок и передаю компоненты содержимого. Но в некоторых компонентах контента есть функции, которые могут изменять компонент блока (например, добавлять или удалять классы).

Теперь я использую состояния в компонентах приложений и контента для изменения блока, но я не думаю, что это правильный подход.

Добавление setState компонента в блок невозможно, как я знаю, потому что нет способа изменить реквизит.

Как я могу изменить состояния блоков внутри компонентов содержимого?

Пример:

 function App() {
 return (
  <div>
   <Block
    id="users-component"
    title="Users Table"
    classes=[]
    content={
     <MyTable class="users" />
    }
   />

   <Block
    id="status-component"
    title="Status Component"
    classes=[]
    content={
     <Status class="status" />
    }
   />

   <Block
    id="bdays-component"
    title="Bdays Component"
    classes=[]
    content={
     <Bdays class="bdays" />
    }
   />
  </div>
  
 )
}

function Block(props) {
 return (
  <div id={props.id} className={props.classes.join(" ")}>
   <h2>{props.title}</h2>
   {props.content}
  </div>
 )
}

function MyTable(props) {
 return (
  <table className={props.class}></table>
)
}

function Status(props) {
 const handleClick = (title) => {
  changeTitle(title)     // changes title in Block
 }
 return (
  <div className={props.class}>
   <button onClick={() => handleClick("newTitle")}>New Title</button>
  </div>
)
}

function Bdays(props) {
 const handleClick = (class) => {
  addNewClass(class)     // add new class to array "classes" in it's block
 }
 return (
  <div className={props.class}>
   <button onClick={() => handleClick("newClass")}>New Class</button>
  </div>
)
}
 

P.S. Извините за мой английский)

Ответ №1:

Проблема решается путем перемещения компонентов содержимого в тело блока и использования React.cloneElement. Теперь это выглядит так:

 function App() {
 return (
  <div>
   <Block
    id="users-component"
    title="Users Table"
   >
    <MyTable class="users" />
   </Block>

   <Block
    id="status-component"
    title="Status Component"
   >
    <Status class="status" />
   </Block>

   <Block
    id="bdays-component"
    title="Bdays Component"
   >
    <Bdays class="bdays" />
   </Block>
  </div>
  
 )
}

function Block(props) {
 [classes, addClasses] = useState([""])
 [title, renewTitle] = useState(props.title)
 return (
  <div id={props.id} className={classes.join(" ")}>
   <h2>{props.title}</h2>
   {React.cloneElement(children, {addClasses, renewTitle})}
  </div>
 )
}

function MyTable(props) {
 return (
  <table> </table>
)
}

function Status(props) {
 const handleClick = (title) => {
  props.renewTitle(title)     // changes title in Block
 }
 return (
  <div>
   <button onClick={() => handleClick("newTitle")}>New Title</button>
  </div>
)
}

function Bdays(props) {
 const handleClick = (class) => {
  props.addClasses([class])     // add new class to array "classes" in it's block
 }
 return (
  <div className={props.class}>
   <button onClick={() => handleClick("newClass")}>New Class</button>
  </div>
)
}
 

Ответ №2:

Делая это таким образом, вы не сможете делиться состоянием между этими компонентами, так как они являются братьями и сестрами (хотя я мог бы исправить это).

Вместо этого вы могли бы сделать так, myTable чтобы быть дочерним элементом Block , и передать функцию useState в myTable качестве опоры, таким образом, вы можете изменить состояние Block с myTable

Это пример реализации

 import { useState } from "react";

export default function App() {
  return (
    <Block
    id="users-component"
    content={
     <MyTable class="users" />
    }
    />
  );
}

function Block(props) {
  const [headerText, setHeaderText] = useState("Marco!")
  return (
  <div id={props.id}>
    <h1> {headerText} </h1>
    <MyTable changeHeaderText = {setHeaderText} />
  </div>
  )
 }
 
 function MyTable(props) {
  return (
   <table className={props.class}>
     <tbody> 
     <tr>
       <td> 
         I'm a table and I can change my parent's header text!
       </td>
     </tr>
     <tr>
       <td>
        <button
         onClick = {() => props.changeHeaderText("polo!")} >Click me!</button>
       </td>
     </tr>
     </tbody>
   </table>
 )
 }
 

Комментарии:

1. Спасибо. Но у меня есть несколько различных дочерних компонентов, таких как MyTable, и я использую многократную копию блока в приложении с ними в качестве реквизита. Таким образом, невозможно перевести все из них в функцию «Блок». И я сейчас не понимаю, как люди используют компоненты в качестве шаблона, чтобы вставлять в них другие компоненты. Они создают это в каждом дочернем компоненте?

2. Не могли бы вы подробнее пояснить примерами, как вы планируете реализовать то, что говорите?

3. Я обновил пример и добавил комментарии. Я хочу использовать «changeTitle» и «addNewClass» в <Блоке/>, без состояний в <Блоке/><Приложении/>

4. Я не думаю, что есть способ сделать это, не помещая состояние в основной компонент приложения. Лучше всего использовать Redux или крючок useReducer для создания глобального состояния, которое может передаваться между всеми компонентами

5. Спасибо за ответы. Я думаю, я изменю структуру проекта, чтобы избежать этой проблемы.