1

Remark: this is not a duplicate of Change the state when clicking outside a component in React I do not want to handel mousedown/mouseup events.

I would like to do change the state of my component "from the outside". So the component will wait until some action id performed by the HTML code (button click). The click can change something in the DOM, but is there a way (another then watching for buttondown events) to detect the change?

The result will be like this:

<div id="root">
... the component will be rendered here, hidden at the beginning
</div>

<button onClick=....>Show the Component</button>
Pepe
  • 431
  • 5
  • 14
  • Is the button part of a React component (even though it's in a completely different part of the UI from where you want to do this? Could it be? – T.J. Crowder Nov 05 '20 at 12:36
  • what do you mean by ' from the outside ' ? Outside of what ? – Mihai T Nov 05 '20 at 12:37
  • No , the button is outside the React component. I am trying to add the component to an existing HTML/js page. – Pepe Nov 05 '20 at 12:39
  • I think you will have the best luck setting up an async task to poll for your state change. For example apps that keep you logged in usually have a "refresh" call on a timer to the authorization service to keep your logged in session alive. To the user it just appears that their session stays active as long as the browser is open. You will get better answers if you describe what you mean by "some action id performed by the HTML Code" if you don't really mean "button click." Otherwise see the duplicate link. – No Refunds No Returns Nov 05 '20 at 12:40
  • By 'outside' I mean 'outside of the React component. – Pepe Nov 05 '20 at 12:40
  • @NoRefundsNoReturns - There's no need for polling. There's an element we can hook to receive events. – T.J. Crowder Nov 05 '20 at 12:42
  • Does this answer your question? [How do I detect a click outside an element?](https://stackoverflow.com/questions/152975/how-do-i-detect-a-click-outside-an-element) – No Refunds No Returns Nov 05 '20 at 12:48

1 Answers1

1

You have at least a couple of options:

  1. Use a portal, either to move the button into the React tree even though it's elsewhere in the DOM, or to render your desired component elsewhere. But if you want to keep the button entirely outside of React, this option doesn't apply.

  2. If you want to leave the button entirely outside of React, use a parent component around the component you want rendered/not-rendered, and have that parent component listen for clicks on the button element. Hook up that click handler in componentDidMount or a useEffect hook with no dependencies, and remove it in componentWillUnmount or a cleanup callback you return from your useEffect.

  3. Or driving it from the other side, use non-React code to hook the click and then respond to the click by mounting the component directly on id="root" via ReactDOM.render.

Here's an example of #2:

const {useState, useEffect} = React;

const TheComponent = () => {
    return <div>This is the component</div>;
};

const Example = () => {
    const [showing, setShowing] = useState(false);
    useEffect(
        () => {
            const clickHandler = () => {
                setShowing(flag => !flag);
            };
            console.log("Hooking click on button");
            document.getElementById("the-button").addEventListener("click", clickHandler);
            // return cleanup handler:
            return () => {
                console.log("Unhooking click on button");
                document.getElementById("the-button").removeEventListener("click", clickHandler);
            };
        },
        [] // <== No deps = mount/unmount only
    );
    return (
        <div>
            {showing && <TheComponent />}
        </div>
    );
};

const App = () => {
    const [mounted, setMounted] = useState(true);
    return (
        <div>
            {mounted && <Example />}
            <div>
                <input type="button" value="Unmount the parent component entirely" onClick={() => setMounted(false)} />
            </div>
        </div>
    );
};

ReactDOM.render(<App />, document.getElementById("root"));
<div>React <code>id="root"</code>:</div>
<div id="root"></div>

<div style="margin-top: 10px">Outside of React:</div>
<input type="button" id="the-button" value="Toggle">

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>

Here's an example of #3:

const {useState, useEffect} = React;

const TheComponent = () => {
    return <div>This is the component</div>;
};

const root = document.getElementById("root");
let mounted = false;
document.getElementById("the-button").addEventListener("click", () => {
    if (mounted) {
        ReactDOM.unmountComponentAtNode(root);
    } else {
        ReactDOM.render(<TheComponent />, root);
    }
    mounted = !mounted;
});
<div>React <code>id="root"</code>:</div>
<div id="root"></div>

<div style="margin-top: 10px">Outside of React:</div>
<input type="button" id="the-button" value="Toggle">

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I like the idea of dynamically mounting the component! But this is not the applicable here. – Pepe Nov 05 '20 at 12:51
  • Could you elaborate a little bit on option 2? How can I listen for clicks on an element that is outside the 'parent component'? – Pepe Nov 05 '20 at 12:53
  • 1
    @Pepe - By finding the element in the DOM and adding a click handler to it. I've added examples of #2 and #3. The example of #2 could be simpler but I wanted to demonstrate unmounting the parent component so you could see the event handler cleanup happen, since that's important. – T.J. Crowder Nov 05 '20 at 12:57
  • 1
    Option 2 I exactly what I was looking for :) Now, when you had answered it, it seems obvious, but including 'plain js' into React wasn't something that came to my mind. Thanks! – Pepe Nov 05 '20 at 12:58
  • @Pepe - My pleasure. :-) It's something you want to avoid if it's reasonable to avoid it, but it's absolutely fine for those times when it isn't. :-) – T.J. Crowder Nov 05 '20 at 13:19