Niklas Moss
Niklas Moss

Reputation: 71

Change state conditioned on in useEffect without triggering the hook and without breaking the exhaustive dependencies rule

I am trying to register a mutation as being inFlight in a useEffect hook. The registration should only occur if the mutation has not already been registered. Thus, I have a boolean variable in state to keep track of whether the mutation has already been registered and I register conditionally based on it. Right after I register the mutation in the useEffect, I set this variable to true. Consequently, I set it to false in my deregister mutation function right after I conduct the deregistration.

Here comes the issue: In order to adhere to the exhaustive dependency rule of useEffect, I have to include the boolean state variable in the dependency array. However, that means that as soon as I deregister, the useEffect will run and re-register the mutation. Instead, I want the deregister to simply "prepare" for registration and then only have registration occur once a specific value changes. Is that even possible while complying with the exhaustive dependency rule?

I've tried to change the boolean variable to an ENUM containing three states (AwaitingValueChange, ShouldDispatch, HasDispatched), only dispatch if its value is ShouldDispatch, and set the value to AwaitingValueChange in the deregister. Then I've created a new hook to be run every time the value that should trigger registration changes and set the state variable to ShouldDispatch if its current value is AwaitingValueChange. However, the new hook is conditioned on the state variable too, so I run into the exact same problem, just this time around the call chain is: Deregister -> new hook -> old hook -> register instead of deregister -> old hook -> register.

The register code:

useEffect(() => {
  if (hasRegisteredMutationInFlight && inFlightDispatch) {
        inFlightDispatch({ type: 'registerMutation', mutationName });
        setHasRegisteredMutationInFlight(true);
      }
  }, [hasRegisteredMutationInFlight, ...]);

The deregister code:

const deregisterMutation = () => {
  inFlightDispatch({ type: 'deregisterMutation', mutationName });
  setHasRegisteredMutationInFlight(false);
};

I would like some way to set a state, not have it trigger my useEffect but use that state to carry out an action conditionally in that same useEffect. I also would like to not break the exhaustive dependency rule. To me, it sounds impossible with the current implementation of useEffect, but I hope someone in here is a bit smarter than me and can see some solution I am overlooking

Upvotes: 0

Views: 2801

Answers (1)

Aritra Ghosh
Aritra Ghosh

Reputation: 666

You cannot use the same flag to register and deregister mutation. It doesn't make sense at all. Why do you need to setHasRegisteredMutationInFlight (false) in deregisterMutation? You can just deregister and while you register back on some value you set the flag to false such that it doesn't depend on the same value. Just a working example of what I mean

You can also let me know what specific use case you are catering for such that I can help you more

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

const mutationName = "MUTATION_NAME";

function App() {
  const [type, setType] = useState("");
  const [isMutationSet, setMutationToRegister] = useState(true);
  const inFlightDispatch = useCallback(
    stuff => {
      setType(stuff.type);
    },
    [setType]
  );

  useEffect(() => {
    if (isMutationSet && inFlightDispatch) {
      inFlightDispatch({ type: "registerMutation", mutationName });
      setMutationToRegister(false);
    }
  }, [isMutationSet, inFlightDispatch]);

  const deregisterMutation = () => {
    inFlightDispatch({ type: "deregisterMutation", mutationName });
  };

  const registerMutation = () => {
    setMutationToRegister(true);
  };

  return (
    <div className="App">
      {type}
      <button onClick={deregisterMutation}>Degister</button>
      <button onClick={registerMutation}>Register</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Upvotes: 1

Related Questions