ManavM
ManavM

Reputation: 3108

Ensure component can only child of specific parent components

Basically I want to be able to define a list of components that can be immediately above Child Component in the component hierarchy. Is there a programmatic way to check for this?

The list would basically be an array of classes like

const allowed_parents  = [Parent1, Parent2, Parent3];

and then

<UnListedParent>
  .
  .
  .
  <Child />
</UnListedParent>

should throw an error

Upvotes: 3

Views: 380

Answers (1)

Karen Grigoryan
Karen Grigoryan

Reputation: 5442

You cannot access the parent directly from a child with any known public React API.

There are "hacky" ways of course, like, to createRef on the parent and pass it down to the child, using React.Children.map, and React.cloneElement programmatically, but this is such a bad design, that I am not going to even post it here, to not be associated with that code :D

I think a better approach, though, that aligns better with the React philosophy and unidirectional top-down flow is to use a combination of HigherOrderComponent-wrapped "allowed parents" that pass a specific flag onto children they "allow", and subsequently check in the child if flag exists or error out otherwise.

That could go approximately something like this

import React, { useState } from "react";
import ReactDOM from "react-dom";

const Child = ({ isAllowed }) => {
  if (!isAllowed) {
    throw new Error("We are not allowed!");
  }

  return <div>An allowed child.</div>;
};

const allowParentHOC = Wrapper => {
  return ({ children, ...props }) => {
    return (
      <Wrapper {...props}>
        {React.Children.map(children, child =>
          React.cloneElement(child, {
            isAllowed: true
          })
        )}
      </Wrapper>
    );
  };
};

const Parent1 = allowParentHOC(props => <div {...props} />);
const Parent2 = allowParentHOC(props => <div {...props} />);

const UnListedParent = ({ children }) => children;

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  componentDidCatch(error, info) {
    this.setState({ hasError: true, info });
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return (
        <>
          <h1>This Child was not well put :(</h1>
          <pre>{JSON.stringify(this.state.info, null, 2)}</pre>
        </>
      );
    }
    return this.props.children;
  }
}

class App extends React.Component {
  state = {
    isUnAllowedParentShown: false
  };

  handleToggle = () =>
    this.setState(({ isUnAllowedParentShown }) => ({
      isUnAllowedParentShown: !isUnAllowedParentShown
    }));

  render() {
    return (
      <>
        <button onClick={this.handleToggle}>Toggle Versions</button>
        {this.state.isUnAllowedParentShown ? (
          <UnListedParent>
            <Child />
          </UnListedParent>
        ) : (
          <>
            <Parent1>
              <Child />
            </Parent1>
            <Parent2>
              <Child />
            </Parent2>
          </>
        )}
      </>
    );
  }
}

export default App;

const rootElement = document.getElementById("root");
ReactDOM.render(
  <ErrorBoundary>
    <App />
  </ErrorBoundary>,
  rootElement
);

Upvotes: 3

Related Questions