Search code examples
reactjsdynamicweb-component

How can I add React web components to a parent component dynamically?


Let's say I have a bunch of React web components

const ComponentA = () => {
  return (<div>A</div>)
}
const ComponentB = () => {
  return (<div>B</div>)
}
const ComponentC = () => {
  return (<div>C</div>)
}
const ComponentD = () => {
  return (<div>D</div>)
}
const ComponentE = () => {
  return (<div>E</div>)
}

and I have a parent component that renders some of them.

export default function () {
  const [state, setState] = useState({ count: 0, initialized: false, components: [ComponentA, ComponentB, ComponentC] });
  return (
    <FlexLayout>
      {state.components.map((comp, i) => React.createElement(comp, { key: i }))}
    </FlexLayout>
  );
}

This works as I can see my three components A, B, and C are rendered properly. Next, I add a button to add new items dynamically.

export default function () {
  const [state, setState] = useState({ components: [ComponentA, ComponentB, ComponentC] });
  const handler = () => 
  {
    const list = state.components.slice(0);
    list.push(ComponentD);
    setState ({ components : list })
  }

  return (
    <FlexLayout>
      <button onClick={() => setState(handler)}>Add Component</button>
      {state.components.map((comp, i) => React.createElement(comp, { key: i  }))}
    </FlexLayout>
  );
}

This also works when I click the button for the first time. I can see that I have added ComponentD . However, If I try to add one more instance of ComponentD the app crashes with the following error:

enter image description here

What is causing this loss of scope where my components are no longer available to the parent?


Solution

  • Some feedback:

    • Storing entire components in state is an anti pattern. Instead, store IDs in the state. Simpler state will reduce likelihood of bugs.
    • as @Mary said, your handler was incorrectly assigned.

    Here's a working example:

    const ComponentA = () => {
      return (<div>A</div>)
    }
    const ComponentB = () => {
      return (<div>B</div>)
    }
    const ComponentC = () => {
      return (<div>C</div>)
    }
    const ComponentD = () => {
      return (<div>D</div>)
    }
    
    const componentMap = {
      a: ComponentA,
      b: ComponentB,
      c: ComponentC,
      d: ComponentD,
    }
    
    export default function () {
      const [components, setComponents] = useState([
        'a','b','c',
      ]);
      const handler = () => {
        setComponents ( [...components, 'd'] );
      }
    
      return (
        <FlexLayout>
          <button onClick={handler}>Add Component</button>
          {components.map(ID => {
            const Component = componentMap[ID];
            return <Component key={ID}/>
          })}
        </FlexLayout>
      );
    }
    

    Live Demo