Adding a prop to a specific React element type in any level of the DOM tree

756 Views Asked by At

I'm trying to add a "position" prop to the 'Child' components and use it inside the 'Parent' component, So the only purpose of the 'PositionWrapper' is to add one prop and return the new children. The problem is that when I call props.children inside 'Parent' I get a 'PositionWrapper' component instead of 'Child' components as I want. I know that I can call props.children.props.children and I will get the 'Child' components but this solution doesn't look like a dynamic one (What if I remove the 'PositionWrraper'? Or what if add more wrappers?)

Does anyone know an optimal/ a better solution? (or Am I implementing the 'PositionWraaper' correctly? )

Thanks!

The code:

Child.js:

const Child = (props) => {
    return (
        <>
        <p>my id is :{ props.id}</p>
        <p>my position is : {props.position} </p>
        </>
    )
}

export default Child;

PositionWrapper.js :

import React from "react"

const PositionWrapper = (props) => {
    return (
        React.Children.toArray(props.children).map((child)=> React.cloneElement(child, { position: [0,0,0]}))        
    )
}

export default PositionWrapper;

Parent.js:

import React from "react";

const Parent = ( props) => {

    // here I want to do things with children components of type 'Child' but props.children consists 'Wrapper' Component.

    return (
        props.children
    )
}

export default Parent;

App.js :

import './App.css';
import PositionWrapper from './Wrapper'
import Child from './Child';
import Parent from './Parent'
function App() {
  return (

    <Parent>
      <PositionWrapper>
      <Child id ={1} />
      <Child id ={2} />
    </PositionWrapper>
    </Parent>
    
  );
}

export default App;
2

There are 2 best solutions below

0
On

You can do a complete virtual DOM tree search to pass the props only to a specific type like below. This is a simple recursive function doing depth-first traversal.

We process the children of the child and pass it as the third argument of React.cloneElement.

React.cloneElement( element, [config], [...children] )

const Child = (props) => {
  return (
    <React.Fragment>
      <p>my id is :{props.id}</p>
      <p>my position is : {props.position} </p>
    </React.Fragment>
  );
};

const PositionWrapper = (props) => {
  return React.Children.toArray(props.children).map((child) =>
    React.cloneElement(child)
  );
};

const Parent = (props) => {
  // here I want to do things with children components of type 'Child' but props.children consists 'Wrapper' Component.

  return passPositionPropToChildren(props.children, [1, 2, 3]);
};

const passPositionPropToChildren = (children, position) => {
  return React.Children.toArray(children).map((child) => {
    // Process the childrens first - depth first traversal
    const childrenOfChildren =
      child.props && child.props.children
        ? passPositionPropToChildren(child.props.children, position)
        : null;
    return React.isValidElement(child)
      ? React.cloneElement(
          child,
          child.type.name === "Child"
            ? {
                ...child.props,
                position: position
              }
            : child.props,
          childrenOfChildren
        )
      : child;
  });
};

function App() {
  return (
    <Parent>
      abc
      <h2>hey</h2>
      <PositionWrapper>
        <Child id={1} />
        <Child id={2} />
        <div>
          <Child id={3} />
        </div>
      </PositionWrapper>
      <Child id={4} />
    </Parent>
  );
}

ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

2
On

Why dont you pass position prop to Parent - since you want to use it in Parent ?

Anyway, uou should use High-Order-Component for this scenario.

Check: https://reactjs.org/docs/higher-order-components.html