skip to Main Content

Like the title says, on mount I want to insert/append a Component, either as a child or sibling.

Things I’ve found online that haven’t worked:

  • ReactDOM.render()
    • This replaces the whole node and its children with the <Component />.
  • .insertAdjacentElement()
    • I get the type error Argument of type 'ReactNode' is not assignable to parameter of type 'Element'.
  • .appendChild()
    • I get the error Argument of type 'JSX.Element' is not assignable to parameter of type 'Node'.
  • .append()
    • I get error Argument of type 'Element' is not assignable to parameter of type 'string | Node'.

For example:

React.useEffect(() => {
  // Code that makes <Component /> appear in DOM
}, []);

<div>
  <div>
    <p>Something</p>
    {/* On mount <Component /> is inserted here */}
  </div>
</div>
[Edited for more detail]:

I’m looking for a solution where I don’t have to enter anything inside of the return ();.

All functionality to insert the <Component /> needs to be done above that.

3

Answers


  1. You can use the useState hook to store an array of components in your state, and then use the map method to render them as children of another component. Inside the useEffect callback, you can use the setState function to update the array of components with a new component.

    For example:

    const [components, setComponents] = useState([]);
    
    useEffect(() => {
      setComponents([...components, <Component key={components.length} />]);
    }, []);
    
    return (
      <div>
        <div>
          <p>Something</p>
          {components.map((component) => component)}
        </div>
      </div>
    );
    

    You can see the whole example here: codesandbox.io


    UPDATED ANSWER

    I’m not sure this is the best solution but I think it can help you.

    // App.jsx
    
    import { useRef } from "react";
    import usePortal from "./usePortal";
    import MyComponent from "./MyComponent";
    
    function App() {
      const containerRef = useRef(null);
      usePortal(MyComponent, containerRef);
    
      return (
        <div className="App">
          <h1>App</h1>
          <div ref={containerRef}></div>
        </div>
      );
    }
    
    export default App;
    
    
    // usePortal.jsx
    
    import { useRef, useEffect } from "react";
    import { createRoot } from "react-dom/client";
    
    function usePortal(Component, containerRef) {
      const rootRef = useRef(null);
    
      useEffect(() => {
        if (containerRef.current) {
          if (!rootRef.current) {
            rootRef.current = createRoot(containerRef.current);
          }
          rootRef.current.render(<Component />);
        }
      }, [Component, containerRef]);
    }
    
    export default usePortal;
    

    You can see the whole example here: codesandbox.io

    Login or Signup to reply.
  2. You could handle it like this:

    1. use a state boolean flag to handle the component’s visibility
    2. in the JSX part return the dynamic content(which is also a react component), with a condition
    3. use an useEffect hook to execute a scope just once after the first render, to change the visibility to true
    4. create a clickHandler that switches the visibility on/off, and link it to a button
    import { useEffect, useState } from 'react';
    
    const DynamicContent = () => <div>dynamic content</div>;
    
    const MyComponent = () => {
      const [isVisible, setVisibility] = useState(false);        //1
    
      useEffect(() => {
        setVisibility(true);                                     //3
      }, []);
    
      const handleClick = () => {
        setVisibility(!isVisible);                               //4
      };
    
      return (
        <div className="my-component">
          <h1 className="my-component__dynamic-content">This is dynamic content</h1>
          <button onClick={handleClick}>change visibility</button>
          {isVisible && <DynamicContent />}                      //2
        </div>
      );
    };
    
    export default MyComponent;

    note that I am using:

    • AtomicDesign folder structure
    • BEM className pattern
    Login or Signup to reply.
  3. Most of the methods listed are DOM api calls (insertAdjacentElement, appendChild() etc.) The reason you’re getting all these type errors is because you can’t mix and match React and DOM elements like that. React uses it’s own virtual dom and doing something outside it’s rendering mechanisms is a huge anti-pattern.

    <Component /> although this looks like a DOM element, JSX actually compiles to function calls behind the scenes. You could potentially do something like this to render HTML directly in your effect.

    const textnode = document.createTextNode("Water");
    node.appendChild(textnode);
    

    But this will not be treated as React and you’ll lose the state management functionality. The proper way to do this is to use a conditional in your render() method like the other answers suggested.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search