I need to add some event handlers that interact with an object outside of React (think Google Maps as an example). Inside this handler function, I want to access some state that I can send through to this external object.
If I pass the state as a dependency to the effect, it works (I can correctly access the state) but the add/remove handler is added every time the state changes.
If I don't pass the state as the dependency, the add/remove handler is added the appropriate amount of times (essentially once), but the state is never updated (or more accurately, the handler can't pull the latest state).
Codepen example:
Perhaps best explained with a Codepen: https://codepen.io/cjke/pen/dyMbMYr?editors=0010
const App = () => {
const [n, setN] = React.useState(0);
React.useEffect(() => {
const os = document.getElementById('outside-react')
const handleMouseOver = () => {
// I know innerHTML isn't "react" - this is an example of interacting with an element outside of React
os.innerHTML = `N=${n}`
}
console.log('Add handler')
os.addEventListener('mouseover', handleMouseOver)
return () => {
console.log('Remove handler')
os.removeEventListener('mouseover', handleMouseOver)
}
}, []) // <-- I can change this to [n] and `n` can be accessed, but add/remove keeps getting invoked
return (
<div>
<button onClick={() => setN(n + 1)}>+</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
Summary
If the dep list for the effect is [n]
the state is updated, but add/remove handler is added/removed for every state change. If the dep list for the effect is []
the add/remove handler works perfectly but the state is always 0 (the initial state).
I want a mixture of both. Access the state, but only the useEffect once (as if the dependency was []
).
Edit: Further clarification
I know how I can solve it with lifecycle methods, but not sure how it can work with Hooks.
If the above were a class component, it would look like:
class App extends React.Component {
constructor(props) {
super(props)
this.state = { n: 0 };
}
handleMouseOver = () => {
const os = document.getElementById("outside-react");
os.innerHTML = `N=${this.state.n}`;
};
componentDidMount() {
console.log("Add handler");
const os = document.getElementById("outside-react");
os.addEventListener("mouseover", this.handleMouseOver);
}
componentWillUnmount() {
console.log("Remove handler");
const os = document.getElementById("outside-react");
os.removeEventListener("mouseover", handleMouseOver);
}
render() {
const { n } = this.state;
return (
<div>
<strong>Info:</strong> Click button to update N in state, then hover the
orange box. Open the console to see how frequently the handler is
added/removed
<br />
<button onClick={() => this.setState({ n: n + 1 })}>+</button>
<br />
state inside react: {n}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
Noting how the add/remove handler is only added once (obviously ignoring the fact that the App component isn't unmounted), despite the state change.
I'm looking for a way to replicate that with hooks