1

The components are rendered once the app has mounted (the reasoning is because these components need to be inserted within plain HTML that is fetched)

I see that the state is changed, but the elements are just not reacting to that

Is there any solution?

codesandbox

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

export default function App() {
  const [elements, setElements] = useState("none");

  useEffect(() => {
    console.log("global useEffect: " + elements);
  }, [elements]);

  const Input = ({ setElements }) => (
    <>
      <input
        type="text"
        onChange={e => {
          setElements(e.target.value);
        }}
      />
      <div>{elements}</div>
    </>
  );
  const Elements = ({ elements }) => {
    useEffect(() => {
      console.log("Elements useEffect: " + elements);
    }, [elements]);
    return <span>{elements}</span>;
  };
  const Elements2 = ({ elements }) => <span>{elements}</span>;

  useEffect(() => {
    console.log("useEffect first time");
    ReactDOM.render(
      <Elements elements={elements} />,
      document.getElementById("elements")
    );
    ReactDOM.render(
      <Elements2 elements={elements} />,
      document.getElementById("elements2")
    );
    ReactDOM.render(
      <Input setElements={setElements} />,
      document.getElementById("inputId")
    );
  }, []);

  // need to split plainhtml, otherwise codesandbox
  // ...gives an error that is IMO a bug
  const plainhtml1 = `
  <div>
  <div>This plain html has been fetched</div>
  <div id="inputId" />
  </div>
  `;
  const plainhtml2 = `
  <div>
  <div>number of elements:</div>
  <div id="elements" />
  </div>
  `;
  const plainhtml3 = `
  <div>
  <div>number of other elements:</div>
  <div id="elements2" />
  </div>
  `;

  return (
    <div>
      <h1>Hello CodeSandbox</h1>
      <div dangerouslySetInnerHTML={{ __html: plainhtml1 }} />
      <div dangerouslySetInnerHTML={{ __html: plainhtml2 }} />
      <div dangerouslySetInnerHTML={{ __html: plainhtml3 }} />
    </div>
  );
}
GWorking
  • 4,011
  • 10
  • 49
  • 90
  • 1
    It's highly unusual that your components are defined inside of another component; if you did that so these can share state with the parent, that's not going to work very well; you'll want to pass any shared state in as props instead or use a mechanism like context. – Jacob Apr 24 '20 at 16:20
  • Just tested with the components outside but the problem remains (if that's what you meant) – GWorking Apr 24 '20 at 16:22
  • I think it's because your useEffect that calls `ReactDOM.render` a bunch of times doesn't have `elements` in its dependency array – azium Apr 24 '20 at 16:23
  • @azium absolutely right, thanks a lot! I can see that Finesse has already answered this – GWorking Apr 24 '20 at 16:26

1 Answers1

3

The elements don't know that the App state changes when it does change. Tell them about the changes by using an effect like this:

useEffect(() => {
   ReactDOM.render(
    <Elements elements={elements} />,
    document.getElementById("elements")
  );
  ReactDOM.render(
    <Elements2 elements={elements} />,
    document.getElementById("elements2")
  );
  ReactDOM.render(
    <Input setElements={setElements} />,
    document.getElementById("inputId")
  );
}, [elements, setElement]);

React will call the effect (and thus rerender the elements) every time the elements and setElements values change. Actually, as you have a very similar effect, you just need to add elements to its dependencies list (the array in the second argument).

Finesse
  • 9,793
  • 7
  • 62
  • 92
  • Thanks a lot, does it mean that the render has to occur every time that elements change? Would have said that this couldn't be the case – GWorking Apr 24 '20 at 16:27
  • Yes, this is how React works. It would work the same if the elements were plain `App` children. But React will compare the virtual DOMs first and apply only the required changes to the real DOM. – Finesse Apr 24 '20 at 16:29