0

I have this useEffect and it complains about add not in dependencies.

useEffect(()=>{
    add();
}, [count]);

The app is a counter I but I can't get it to work. I want it everytime when counter is changed, to add to a history of changes which I implemented like this:

const [hist, setHist] = useState({
        past: [],
        present: 0,
        future: []
    }
);

and in add, this supposed to happen

const add = () => {
    const newPresent = count;

    setHist({
        past: [...hist.past, hist.present],
        present: newPresent,
        future: []
    });

    console.log("add count:", count);
    console.log("add hist:", hist);
}

But when adding or removing from the counter, despite "add count" logs on console the current count state (let's say -1), present: in history is always one step back. Also it adds zero to the past: array twice.

And when I add "add" to the dependencies array, I get a compile error

ReferenceError: can't access lexical declaration `add' before initialization

https://codesandbox.io/s/festive-dew-3ix3d

EDIT: Here is now a working example of undo/redo with a counter utilizing hints taken from SILENT's answer:

https://codesandbox.io/s/angry-blackwell-bsdsx

16
  • Can you show a minimal reproducible example of your component? It seems like it'd be easier to keep state if you just have a single history array and a pointer currentIndex that points to wherever the current historical value is (for normal operation, this would be the last element in the array). I'm not clear what good useEffect does here; seems like everything is synchronous. Commented Feb 4, 2020 at 0:31
  • How do I do that? I can't add all the code here Commented Feb 4, 2020 at 0:32
  • The link explains how. Commented Feb 4, 2020 at 0:33
  • I tried to use the code snipped but it gives me a syntax error "message": "SyntaxError: expected expression, got '<'", "filename": "stacksnippets.net/js", "lineno": 77, "colno": 8 Commented Feb 4, 2020 at 0:40
  • Where's your createContext? Commented Feb 4, 2020 at 0:44

1 Answer 1

2

So I had to change a lot. Usually I don't do this but it seemed quick to do. Here's a react counter with history;

import React, { useState, useCallback } from "react";
import Context from "./Context";

const ContextProvider = props => {
  const [hist, setHist] = useState({
    past: [],
    present: 0,
    future: []
  });

  const incr = useCallback(() => {
    setHist(({ past, present }) => {
      past = [...past, present];
      return {
        past,
        present: present + 1
      };
    });
  }, []);

  const decr = useCallback(() => {
    setHist(({ past, present }) => {
      past = [...past, present];
      return {
        past,
        present: present - 1
      };
    });
  }, []);

  const undo = useCallback(() => {
    setHist(hist => {
      let { past, present, future = [] } = hist;
      past = [...past];
      future = [...future, present];
      present = past.pop();
      console.log("undo:", past, present, future);
      return {
        past,
        present,
        future
      };
    });
  }, []);

  const redo = useCallback(() => {
    setHist(hist => {
      let { past, present, future } = hist;
      if (future && future.length > 0) {
        future = [...future];
        past = [...past, present];
        present = future.pop();
        return {
          past,
          present,
          future
        };
      }
      return hist;
    });
  }, []);

  return (
    <Context.Provider
      value={{
        count: hist.present,
        incr,
        decr,
        undo,
        redo
      }}
    >
      {props.children}
    </Context.Provider>
  );
};

export default ContextProvider;

With duplicate counter state

import React, { useState, useCallback } from "react";
import Context from "./Context";

const ContextProvider = props => {
  const [count, setCount] = useState(0);
  const [, setHist] = useState({
    past: [],
    present: 0,
    future: []
  });

  const incr = useCallback(() => {
    setHist(({ past, present }) => {
      past = [...past, present];
      let nC = count + 1;
      setCount(nC);
      return {
        past,
        present: nC
      };
    });
  }, [count]);

  const decr = useCallback(() => {
    setHist(({ past, present }) => {
      past = [...past, present];
      let nC = count - 1;
      setCount(nC);
      return {
        past,
        present: nC
      };
    });
  }, [count]);

  const undo = useCallback(() => {
    setHist(hist => {
      let { past, present, future = [] } = hist;
      past = [...past];
      future = [...future, present];
      present = past.pop();
      setCount(present);
      console.log("undo:", past, present, future);
      return {
        past,
        present,
        future
      };
    });
  }, []);

  const redo = useCallback(() => {
    setHist(hist => {
      let { past, present, future } = hist;
      if (future && future.length > 0) {
        future = [...future];
        past = [...past, present];
        present = future.pop();
        setCount(present);
        return {
          past,
          present,
          future
        };
      }
      return hist;
    });
  }, []);

  return (
    <Context.Provider
      value={{
        count,
        incr,
        decr,
        undo,
        redo
      }}
    >
      {props.children}
    </Context.Provider>
  );
};

export default ContextProvider;

https://codesandbox.io/s/heuristic-cherry-oqted

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks. Although I gonna need some time to understand why this works.
I removed correct answer because it's not answering my question. It provides a working example but it's avoiding the problem in the question by simply removing the function that caused the issue.
About useEffect? That's cause you aren't suppose to useEffect in that situation.
No, don't get me wrong, your answer is a good hint, but in my question I had a count variable and an add() function, that supposed to mean "add to history (normal operation on count)". The counter just worked entirely on the counter and undo redo just supposed to handle the movement through the history. You simply removed the count state variable and the add() function and set counter = hist.present for the output. That works and is syntactically correct for a display but is in function different to my original where I wanted to undo redo the changes to the count state.
@Supertyp You wanted duplicate info (counter and hist.present)? Thats simple. Either way, history is recorded (past, present, and future).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.