Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to recursively inject props to each React component in a tree?

So, I've been using emotion-js for a while, and I love it! But I really hate e2e tests for that. All selectors look like this: div > div > div > p, because emotion injects classnames which are random-generated strings. So I thought of injecting data- attributes with DisplayName to each element to be able to test them in more clean way: [data-test-id*="DateButton"] or something like that. So I wrote this simple wrapper: https://gist.github.com/sergeyvlakh/3072a4e2e27456bda853a1e1a213a568

And I use it like this:

import Injector from './injector';

export default props => 
    (
        <Injector>
            <MyReactTree />
        </Injector>
    )

The problem here is what I'm not "dynamically" inject data attribute. I want to use it like any HoC from recompose: export default Injector(App)

OR even with dynamic prop name like: export default Injector('data-test-id')(App). But...children for Injector will be undefined in that case, so I don't know how to proceed :)

UPDATE: Thanks to remix23 I did this way: https://gist.github.com/sergeyvlakh/f99310cdef018c06451333a36e764372

However I strongly recommend to use his code.

like image 809
Disciples Avatar asked Mar 19 '19 09:03

Disciples


People also ask

Can React components be recursive?

A Practical Use for Recursion in React Follow along to understand the ins and outs of a working React Tree component powered by recursion + React rendering. Giving our component nested data in the following shape, we'll now have a functioning, modular, and recursive component ready for use!

How do I pass a prop to a component?

To pass props, add them to the JSX, just like you would with HTML attributes. To read props, use the function Avatar({ person, size }) destructuring syntax. You can specify a default value like size = 100 , which is used for missing and undefined props.

How do you pass all props to the child functional component?

Instead, to pass all React props from parent to child at once, you need to take advantage of the spread operator ( ... ). The spread operator lets you pass the entire props object to a child component as that child's props object.


1 Answers

You can inject props to any React element by using React.cloneElement(element, newPropsToMerge, newChildren)

You can do it recursively to follow chilren in depth by accessing each element children and so forth.

And maybe more costly but more straightforward this should work also:


    function Injector(Target) {
         class InjectedTarget extends Component {
              render() {
                  const { chilren, ...rest } = this.props;
                  return (
                      <Target
                          data-test-id={Target.DisplayName}
                          {...rest}
                      >
                          {React.Children.map(children, (child) => {
                              const isComponent = 
                                  child &&
                                  child.type &&
                                  typeof child.type !== 'string' && 
                                  Object.prototype.isPrototypeOf.apply(
                                      React.Component.prototype,
                                      [child.type.prototype]
                                  );
                              if (!isComponent) {
                                  return child;
                              }
                              return React.createElement(Injector(child.type), { ...child.props, 'data-test-id': Target.DisplayName });
                          })}
                      </Target>
                  );
              }
         }
         return InjectedTarget;
    }

I think that will be awfull performance wise, but this is for testing right?

like image 68
remix23 Avatar answered Sep 29 '22 20:09

remix23