Search code examples
javascriptreactjsredux-form

How can I accessing nested component in react from parent component?


I want to access a nested component from parent component.

This is Bill Form.jsx

      import BillDetailForm from './BillDetailForm';


         render(){

          return (
            <form onSubmit={handleSubmit}>

             <FieldArray

               name= 'detail'
               component={BillDetailForm}
               placeholder= '...detail'
               label='Detail'

                 />
               </form>
                  );
                   }
               }

BillForm is the parent component.

This is a nested component or child component of BillForm: BillDetailForm.jsx

     render(){


    return(
         <form onSubmit={ handleSubmit }>


       <div>Detail:</div>
      <FieldArray
        name= 'detail'
        component={RenderDetail}
        label='Detail'
      />

      </form>

    )

} 

Inside BillDetailForm is RenderDetail:

        const RenderDetail = ({fields, meta: { error,submitFailed}},props) => (
          <dl>
         <dt>
            <button type="button" className= 'btn btn-primary' onClick={() => fields.push()}>Add 
        Detail</button>
             {submitFailed && error && <span>{error}</span>}
           </dt>
               { fields.map((registerDetail, index) =>
               //In the following line renderDetail is accesing Detail component.
               <Detail detailItem={registerDetail} fields={fields} index={index} key={index}/>

               )
             }

            {error && <dt className="error">{error}</dt>}
            </dl> 
            );

This is Detail Class Component:

          class Detail extends Component{

                   render(){

                   const{detailItem,index,fields,isSubtotal} = this.props;

                        return(


                    <dd key={index}>
                    <br></br>
                    <button className= 'btn btn-light mr-2'
                     type="button"
                     title="Remove detail"
                     onClick={() => { fields.remove(index)
                      if(fields.length == 0 || fields.length === undefined){

                        }
                      try{
                     for(let x in fields){
                     fields.remove(index) 
                     let d = fields.selectedIndex;
                    if(fields.remove(index) && d >= 1 && d< fields.length ){
                    fields.removeAll(index);
                     }
                     }
                  }catch{console.info("deletes non numerical index")}

                      }}> Delete </button>

              <h4>DetailRegister #{index + 1}</h4>

               <Field 
                 id={`${detailItem}._id`}
                 name={`${detailItem}.quantity`}
                 component= {NumberPickerInteger}
                 placeholder= '...quantity'
                 label = "Quantity" 
                  />
          <br></br>
          <h3><b>Product</b></h3>
               <Field 
                id={`${detailItem}._id`}
                name={`${detailItem}.product.code`}
                type="number"
                component= {RenderFieldNumeric}
                placeholder='...Product's code'
               label = "Product's code" 
             />
          <Field 
           id={`${detailItem}._id`}
           name={`${detailItem}.product.name`}
           type="text"
           component= {RenderField}
           placeholder='...Product's name'
           label = "Product's name" 
         />
            <Field 
              id={`${detailItem}._id`}
              name={`${detailItem}.product.price`}
              component= {NumberPickerr}
              placeholder= '...Price'
              label = "Product's price" 
             />
         <br></br>
      <h3><b>Subtotal</b></h3>
       <Field 
          id={`${detailItem}._id`}
          name={`${detailItem}.subtotal`}
          component= {SubtotalWidget}
          placeholder= '...subtotal'
          label = "Subtotal" 
           >
            {isSubtotal}
             </Field>



            </dd>

            );

          }
       }

I want to access e.g ${props.detailItem}.subtotal that is in Detail from BillForm. BillForm accesses to BillDetailForm, BillDetailForm accesses to renderDetail, and last renderDetail acceses to Detail.

The question is: How can I access and use props like quantity and subtotal with dynamic index (props.index) from BillForm? I want to access Detail component from BillForm, respecting the following secuence in order access: BillForm -> BillDetailForm -> RenderDetail -> Detail


Solution

  • If I understand correctly what you are saying, it seems you are going against the ethos of React. If your parent component wants access to a piece of data, then that data should start in the parent and be passed down. This way, if the data changes it will call a re-render of components and update all necessary components.

    Some other advice. Try not o have so much logic inside your component handlers, it looks messy and will run every render cycle. Abstract this into a method on the class and call it when required.

    My example will hopefully help you with your issue, but I recommend having a read of the React documentation as it is very good with simple examples. The use of class will be deprecated eventually in favour of function components and the Hooks API.

    class ParentComponent {
      state = {
        value: 0,
      }
    
      methodToDoSomething = (passedVal) => {
        this.setState({
          value: passVal,
        });
      }
    
      render() {
        const myState = this.state;
        return (
         <Component {...myState} />
        )
      }
    }
    
    class Component {
      state = {}
    
      render() {
        const { value , methodToDoSomething } = this.props;
        return (
         <div onClick={methodToDoSomething}>
          {value}
         </div>
        )
      }
    }
    
    // Hooks API
    
    const ParentComponent = () => {
      
      const [stateVal, updateState] = React.useState('myString');
    
      return (
        <div>
          {stateVal}
          <Component passedVal={stateVal} passedHandler={updateState} />
        </div>
      )
    }
    
    const Component = ({ stateVal, passedHandler }) => {
      
      function updateMyValue() {
        passedHandler('menewvalue');
      }
    
      return (
        <div onClick={updateMyValue}>
         {stateValue}
        <div/>
      )
    }
    

    To avoid passing lots down all the children components, I would recommend reading up on the Context Hook.

    *** UPDATE *** The above example is rudimentary and tries to answer the question presented, there are always many ways to solve a problem.

    Passing props can be messy and a maintenance overhead. Most larger applications will benefit from using a state library to manage their global state. The Context API is a good tool to use to wrap a cohesive set of components so they can share data/props without prop-drilling (passing props down many child components).

    Custom hooks are another good way to share data. Create a hook containing your data and any other methods for the task and use this hook inside parent and child components to share said data.