Search code examples
javascriptreactjssetstate

How to setState() dynamically in between Parent and Child Component [Logic being run on child & state on the Root App level]


I am fooling around while learning React and I would like to know what would be an elegant solution for the scenario presented below.

Follow this Link to see the complete code set.

I have a child component, that has a <h1> tag. This tag returns a message that I am displaying dynamically as will be shown below.

App root container

I have my state on this container, and I want to keep it just there.

State:

The object of discussion here is the cockpitAlert object.

 state = {
        state = {
    persons: [
      { id: "a", name: "foo1" },
      { id: "b", name: "foo2" },
      { id: "c", name: "foo3" }
    ],
    CockpitAlert: {
      regular: "Some message",
      alert1: "The array is ending",
      alert2: "There is nothing to show"
    }
  };

Below is how I delete the rendered items - still on App.js

personDeleteHandler = index => {
    const person = [...this.state.persons];
    person.splice(index, 1);
    this.setState({ persons: person });
  };

Below is where I render my JSX

render() {
    return (
      <div className="App">
        <Cockpit
          regular={this.state.CockpitAlert.regular}
          alert1={this.state.CockpitAlert.alert1}
          alert2={this.state.CockpitAlert.alert2}
          personsLength={this.state.persons.length}
        />
        <Person click={this.personDeleteHandler} persons={this.state.persons} />
      </div>
    );
  }

Cockpit child

Below is the logic. let message dynamically changes the props according to the length of the persons array, that gets shortened upon clicks.

import React from "react";

function Cockpit(props) {
  let message = props.regular;
  if (props.personsLength <= 2) {
    message = props.alert1;
  }
  if (props.personsLength === 0) {
    message = props.alert2;
  }

  return (
    <div>
      <h1>{message}</h1>
    </div>
  );
}

export default Cockpit;

As you can see this does not look very nice at all.

I don't want to have all of these different props being pointed on my logic:

regular={this.state.CockpitAlert.regular}
alert1={this.state.CockpitAlert.alert1}
alert2={this.state.CockpitAlert.alert2}
personsLength={this.state.persons.length}

I wonder if there is a way that I could pass just one props, e.g props.message and change it dynamically, probably through setState() on the root level, where the state is set without having to move my logic from Cockpit to App.js


Solution

  • Well I have discovered how to solve this issue using React Context API

    here is the updated CodeSandBox

    I have first created the file auth-context.js in order to pass my state from the Parent component to the child component.

    As the logic is already based on the Cockpit.js I will only pass my state through React Context ApI in this case.

    auth-context.js file

    import React from 'react'
    
    const authContext = React.createContext({
      persons: [
        { id: "a", name: "foo1" },
        { id: "b", name: "foo2" },
        { id: "c", name: "foo3" }
      ],
      message: "Some message",
    
    });
    
    export default authContext
    

    On my App.js container

    Now I have set the Provider in the App.js root container:

    //first of all, import the file into the root element which will Provide the //initial state on this API
    
    import AuthContext from "./Context/auth-context";
    
    ...
    
    class App extends Component {
      state = {
        persons: [
          { id: "a", name: "foo1" },
          { id: "b", name: "foo2" },
          { id: "c", name: "foo3" }
        ],
        CockpitAlert: {
          message: "Some message"
        }
      };
    
      personDeleteHandler = index => {
        const person = [...this.state.persons];
        person.splice(index, 1);
        this.setState({ persons: person });
      };
    
      render() {
        return (
          <div className="App">
            //I am wrapping the whole deal here and set my state so I can manage it at
            // the Cockpit.js file further on
            <AuthContext.Provider
              value={{
                persons: this.state.persons,
                message: this.state.CockpitAlert.message
              }}
            >
             //no more props being passed at Cockpit.js!!!
              <Cockpit />
              <Person
                click={this.personDeleteHandler}
                persons={this.state.persons}
              />
            </AuthContext.Provider>
          </div>
        );
      }
    }
    
    export default App;
    

    Now I will pass my state into the child component

    Cockpit.js file

    import React, { useContext } from "react";
    //must import AuthContent here as well.
    import AuthContext from "../../Context/auth-context";
    
    function Cockpit(props) {
      //this is the way of setting it up on a functional component
      // now I have access to the state through authContext
      const authContext = useContext(AuthContext);
    
      //here I will point my state into different messages
      if (authContext.persons.length <= 2) {
        authContext.message = "running out";
      }
      if (authContext.persons.length === 0) {
        authContext.message = "nothing else to render";
      }
      //as you can see there are no props created
      return <div>{<h1>{authContext.message}</h1>}</div>;
    }
    
    export default Cockpit;
    

    Now my reflection is:

    This is not mutating the original state, at App.js. Would this be a good practice in this case?

    What do you guys think?