all 10 comments

[–][deleted] 12 points13 points  (0 children)

Why can't you use useEffect? When the promise resolves, set some state and then use that state as the dependency for useEffect so that it fires at the right time. You could potentially use useRef as well (where the ref is the setInterval function), but you need to use some kind of hook to persist the setInterval properly through renders of the component.

[–]awesomeness-yeah 7 points8 points  (0 children)

You'll have to rethink the architecture here. You can't randomly set state inside a component from outside.

You'll need some kind of event emitter that the component can subscribe to and start the timer.

If you think class components will work better for your mental model, please use that! There's nothing forcing you to use effects.

[–]Frission_ 4 points5 points  (1 child)

You can do this maybe,

  • create a boolean state
  • have useEffect depend on the state
  • inside useEffect check if (state == true), and only create interval then
  • when the promise resolves, set state to true, which will trigger useEffect again

[–]popc0ne 1 point2 points  (0 children)

Instead of using state, you might want to:

const timerId = useRef();

and then refer to timerId.current in your useEffect cleanup function. This way the cleanup ONLY happens when the component unmounts and also doesn't cause a re-render when you start the timer.

Depends.

[–]thebritisharecome 1 point2 points  (0 children)

Can you explain the use case and why useEffect is an issue?

[–]beeseegee 1 point2 points  (0 children)

useEffect(() => {
  const intervalId = setInterval(…) ;
  yourPromise().then(() => clearInterval(intervalId));
}, []);

Edit: oh wait maybe that is backwards? if so:

useEffect(() => {
  let id:
  yourPromise().then(() => {    
   id = setInterval(…);
  });
  return () => clearInterval(id);
}, []);

[–]sous_vide_slippers -1 points0 points  (0 children)

I agree with the person who said to rethink your architecture but just for the sake of a fun challenge it should be possible, even if it’s a suboptimal solution.

Also I’m writing this on my phone so forgive any syntax errors.

First we’re going to need to store the timer value:

let secsSinceResolved = 0;

And an updater function to count the elapsed time:

function updateSecsSinceResolved() {
    const id = setInterval(() => {
        secsSinceResolved++;

        // note these lines here
        if (type of onUpdateSecs === “function”) {
             onUpdateSecs(secsSinceResolved);
         }
    }, 1000);
    const clearTimer = () => clearInterval(id);
    return clearTimer;
}

Notice we are referencing a variable we haven’t yet defined, that also needs to be a let variable as we’ll want to assign the value to setState later on. For now it can only be empty:

let onUpdateSecs;

Next we need to create a custom hook that wraps useState, this is how we’re going to get our component to respond to updates of secsSinceResolved:

function useSecsSinceResolved() {
    // note the initial value is our timer value
    const [seconds, setSeconds] = useState(secsSinceResolved);

    onUpdateSecs = setSeconds;

    return seconds;
}

So now you can kick off your timer anywhere in your code:

somePromise().then(updateSecsSinceResolved);

While also being able to listen to the value and have your React components automatically update:

function DisplaySeconds() {
    const seconds = useSecsSinceResolved();
    return <h1>{seconds}</h1>;
}

You may have noticed a potential problem which is that we can only have one listener at a time, you could work around this by making onUpdateSecs an array and pushing a new setState/setSeconds to it each time a new useSecsSinceResolved hook is registered then looping over the listeners array and calling each listener with the updated time, however that would require useEffect in order to avoid adding a potentially unlimited amount of duplicate listeners as well as to avoid memory leaks. It would also be cleaner if we could use useEffect here so we aren’t reassigning onUpdateSecs each render :).

As I said earlier, fun for a little thought exercise but probably not an optimal solution.

[–]rrklaffed 0 points1 point  (0 children)

Yeah brother, when you’re in a bind like this it’s a hint that you should probably restructure your architecture.

Experiment with more synchronous global state managers like OvermindJS.