Share state between components in React

Viewed 93

I'm trying to make a dashboard using React. There are few components: App, Block and other child components, let's call them Content.

Block is a simple bootstrap card with title, classes and some css. In App I call Block and pass in the Content components. But in some Content components there are functions, which can change Block component (e.g. add or remove classes).

Now I use states in App and Content components to change Block, but I don't think this is the right approach.

Adding setState in Block component is impossible, as I know, because there is no way to change props.

How can I change Block states from within Content components?

Example:

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. Sorry for my English)

2 Answers

Problem is solved by moving Content components into Block body and using React.cloneElement. Now, it's look like this:

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>
)
}

Doing it that way, you won't be able to share state between those components, since they're siblings (although I might get corrected on that)

You could instead, make myTable to be a child of Block, and pass a useState function TO myTable as a prop, that way you can change the state of Block from myTable

This is a sample implementation

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>
 )
 }
Related