0

I have an component that renders different types of fields called Item. Item may render a select box with a list of Users or a list of Inventory. I have two containers: one for Users and another for Inventory. I originally thought to nest my containers but that appears to freeze my react app. Inventories and Users containers are identical except that one container holds inventory items and the other holds users.

Here is the Users container:

import React, { Component } from 'react';


 class UsersContainer extends Component{

   constructor(props){
     super(props);
     this.state = {
       users: []
     }

   }

   componentDidMount(){
     //put api call here
     this.setState({users: [{id: 1, name: "Test Name", email: "test@yahoo.com"}, {id: 2, name: "John Doe", email: "johndoe@gmail.com"}, {id: 3, name: "Jane Doe", email: "janedoe@yahoo.com"}]})
   }


   render(){
     return(
       <div className="users-container">

          {React.Children.map(this.props.children, child => (
               React.cloneElement(child, {...this.props, users: this.state.users })
           ))}

       </div>
     )
   }

 }
export default UsersContainer;

I originally tried to nest the containers but this causes React to freeze:

<UsersContainer>
    <InventoriesContainer>
        {this.props.items.map(i => (
            <Item name={i.name} />
        ))}
    </InventoriesContainer>
</UsersContainer>

Item looks something like this:

function elementUsesInvetory(inventories){
    //returns selectbox with list of inventory
}

function elementUsesUsers(users){
    //returns selectbox with list of users
}


function Item(props){
    render(){

        return(
            <>
               {elementUsesUsers(props.inventories)}
               {elementUsesInventory(props.users)}
            </>
        );
    }
}

How can I provide the data from UsersContainer and InventoriesContainer to the Item component?

Cannon Moyer
  • 3,014
  • 3
  • 31
  • 75
  • Can't you use a single function for creating a select box? Do they have to be separate? – mplusr Nov 22 '20 at 06:59
  • In my implementation I only use one function but I created two in the question just to try and convey my goal easily. @mplusr – Cannon Moyer Nov 22 '20 at 07:01
  • Why not to merge Users and Inventory containers to one component? Why do they need to be nested? – Ken Bekov Nov 22 '20 at 07:13
  • @KenBekov I could, I was just trying to reuse the code I already had since all the functionality I needed was already built in both of the containers. – Cannon Moyer Nov 22 '20 at 07:15
  • If _Users_ and _Inventories_ are identical, then the _Users_ component will pass **users** prop to _Inventory_ and not to the _Items_ component. You will have to change the way you access it inside _Inventories_ and then pass it to _Items_ accordingly or don't nest them to avoid all this unnecessary complications @CannonMoyer – mplusr Nov 22 '20 at 08:48
  • Why are you using .cloneElement? – timotgl Nov 22 '20 at 09:44

2 Answers2

0

Merging them into one component would avoid a lot of confusion. If you still want to nest them, you might want to pass the props by prop-drilling or by using the context API. React.cloneElement isn't preferred for nested child components. More on that here

You can pass down the data with the help of React's context API. The UsersContainer component holds the Provider and passes users down to Inventories

The Inventories will then pass on the users and inventories as props to the Items component. I'm not sure if you need separate functions for the select boxes but I've added them in the demo anyway.

const MyContext = React.createContext();
class UsersContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      users: []
    };
  }

  componentDidMount() {
    //put api call here
    this.setState({
      users: [
        { id: 1, name: "Test Name", email: "test@yahoo.com" },
        { id: 2, name: "John Doe", email: "johndoe@gmail.com" },
        { id: 3, name: "Jane Doe", email: "janedoe@yahoo.com" }
      ]
    });
  }

  render() {
    return (
      <div className="users-container">
        <MyContext.Provider value={this.state.users}>
          {this.props.children}
        </MyContext.Provider>
      </div>
    );
  }
}

class Inventories extends React.Component {
  static contextType = MyContext;
  constructor(props) {
    super(props);
    this.state = {
      inventories: []
    };
  }

  componentDidMount() {
    //put api call here
    this.setState({
      inventories: [
        { id: 1, name: "Test Name", email: "test@yahoo.com" },
        { id: 2, name: "John Doe", email: "johndoe@gmail.com" },
        { id: 3, name: "Jane Doe", email: "janedoe@yahoo.com" }
      ]
    });
  }

  render() {
    return (
      <div className="inventory-container">
        {React.Children.map(this.props.children, (child) => {
          return React.cloneElement(child, {
            ...this.props,
            users: this.context,
            inventories: this.state.inventories
          });
        })}
      </div>
    );
  }
}

function Items(props) {
  function usersSelect(items) {
    return (
      <select>
        {items.map((item) => (
          <option key={"user"+item.id} value="{item.id}">
            {item.name}
          </option>
        ))}
      </select>
    );
  }

  function inventoriesSelect(items) {
    return (
      <select>
        {items.map((item) => (
          <option key={item.id} value="{item.id}">
            {item.name}
          </option>
        ))}
      </select>
    );
  }

  return (
    <div>
      <h2>users</h2>
      {usersSelect(props.users)}
      <h2>inventories</h2>
      {inventoriesSelect(props.inventories)}
    </div>
  );
}


function App() {
  return (
    <div>
      <UsersContainer>
        <Inventories>
          <Items />
        </Inventories>
      </UsersContainer>
    </div>
  );
}

ReactDOM.render(<App/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Dharman
  • 30,962
  • 25
  • 85
  • 135
mplusr
  • 151
  • 15
0

I good approach would be to put the state in common between those components in a level up in the tree component.

So what are you trying to do:

<UsersContainer>
   <InventoriesContainer>
       {this.props.items.map(i => (
            <Item name={i.name} />
        ))}
    </InventoriesContainer>
</UsersContainer>

Would be:

RealFatherComponent extends Component {

// state that Item will need will be set here
  render() {
    return (
      < UsersContainer **propsShared**  >
        <Item **propsShared** /> 
      </UsersContainer>
      < InventoriesContainer **propsShared** >
        <Item **propsShared** /> );
      </InventoriesContainer>
  }
}
iwaduarte
  • 1,600
  • 17
  • 23