-1

I am trying to wrap my head around functional programming and one of the main concepts is closures - but I think there is something more that I am lacking and I cannot put things together. This is a state closure that I wrote similar to the one in React, I know the right answer to make the code snippet work, I just don't understand because of what theoretical facets this does not work.

const useState = function () {
    let state: maze = {
        size: 0,
    };

    const setState = (newState) => {
        state = { ...newState };
    };

    const myStateObj = {
        state,
        setState,
    };

    return myStateObj;
};

const handleMazeSize = (e: InputEvent) => {
    const newMaze: maze = {
        size: Number((e.target as HTMLInputElement).value),
    };

    console.log(useState().state);
    console.log(useState().setState(newMaze));
    console.log(useState().state); // still size 0, expected the inputted size
};

Why does it not get modified?

  • Your `useState()` returns a completely fresh object. – tkausl May 19 '22 at 07:25
  • _"I know the right answer to make the code snippet work"_ What is the right answer to make this code snippet work? – jabaa May 19 '22 at 07:32
  • @jabaa another implementation of the useState() that I found online – enchantingbyholma May 20 '22 at 07:17
  • Can you show it? I think it's expected to return a new object in each call. – jabaa May 20 '22 at 07:36
  • @jabaa it is here - https://codesandbox.io/s/ff4rx?file=/src/index.js:673-756 but a bit different than my thinking and I wanted to do it my way – enchantingbyholma May 20 '22 at 09:44
  • You have a working code and an explanation why the code in the question doesn't work. Do you still have a question? – jabaa May 20 '22 at 10:56
  • If you're trying to learn functional programming, please notice that [React's `useState` is not functional](https://stackoverflow.com/a/71931727/1048572) – Bergi May 23 '22 at 11:06

1 Answers1

0

The two main issues in your snippet are:

  • Every time you handle the change event, you create a new state with value 0. You need to persist your state outside of your event handler.
  • The way you expose the state property only returns a reference to the initial internal state object.

There are many ways to solve these issues. In the snippet below I:

  • Replaced the size property of your useState return object with a getter
  • Call useState once before attaching the event listener

const useState = function() {
  let state = {
    size: 0,
  };

  const setState = (newState) => {
    state = { ...newState };
  };

  const myStateObj = {
    get state() { return state; },
    setState,
  };

  return myStateObj;
};

const mazeState = useState();

const handleMazeSize = (e) => {
  const newMaze = {
    size: Number(e.target.value)
  };

  console.log("from:", mazeState.state);
  mazeState.setState(newMaze);
  console.log("to:", mazeState.state);
};

document.querySelector("input").addEventListener("change", handleMazeSize);
<input type="number" value="0">

Here's another way of solving the second issue. Instead of using a get that always returns the current internal value of the let state variable, we expose that state object once and mutate it.

The downside is that you could (accidentally) edit the internal state from the outside. (e.g. const { state } = useState(); state.foo = "bar")

const useState = function() {
  const state = {
    size: 0,
  };

  const setState = (newState) => {
    // Mutate the exposed object
    Object.assign(state, newState);
  };

  const myStateObj = {
    state,
    setState,
  };

  return myStateObj;
};

const mazeState = useState();

const handleMazeSize = (e) => {
  const newMaze = {
    size: Number(e.target.value)
  };

  console.log("from:", mazeState.state);
  mazeState.setState(newMaze);
  console.log("to:", mazeState.state);
};

document.querySelector("input").addEventListener("change", handleMazeSize);
<input type="number" value="0">
user3297291
  • 22,592
  • 4
  • 29
  • 45
  • Firstly, thank you for such a neat solution! To get it a bit better, and correct me if I am wrong - you had to initialize useState() in order to create an object and that, opposed to my solution is how you avoid creating a new object on each call. I've been searching quite a bit as to why you are using this getter method instead of a generic method on the object - it's utiity can be exercised when the property is not publicly available or when you want to run some code every time a property is requested and in your situation it seems to me to be neither. Is it preference or is it better? – enchantingbyholma May 23 '22 at 09:18
  • A call to `useState` returns an object with a `state` property. The problem is that that property points to the initial state object. That means you'd have to mutate that object for state changes to be exposed to the outside world. The `get` code ensures that everytime you get that property, it actually returns the state object that is currently set to the `state` variable inside your closure. (Wow.. this is hard to explain... I'll add some code to my answer to illustrate what I mean) – user3297291 May 23 '22 at 09:23
  • Thanks for the answer - actually I got it why there's a need for get/a method when returning state. – enchantingbyholma May 25 '22 at 08:42