Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I think this post has actually pushed me back towards Symbol keys perhaps being a good idea. The useFormInput() example under Flaw 3 seems rather contrived – wouldn’t you just pass a Symbol key to useFormInput and it would then pass the key to useState, solving the supposed flaw? If you had to use useState several times in useFormInput, just use a WeakMap (they’re not that scary) with the keys being the Symbols passed to useFormInput. Or am I missing something in the explanation?


It's not really well explained in the article, but the argument against Symbols is that you (client code) have to store them somewhere for reuse in different calls.

That is that, you could do...

    useState("someID")
...and somewhere else (* or in the same place but on a different call) again...

    useState("someID")
...and this indeed refers to the same item. But using a Symbol, you need to first create it, store it somewhere and then use it. That is, you can't do this...

    useState(Symbol("someID"))
...because this will fail through different repeated calls. Instead you'd need to first...

    let someSymbol = Symbol("someID");
...and then...

    useState(someSymbol)
Or, alternatively, use Symbol.for("someID"), which then has both problems: creating the symbol first and clashing of identifiers.

While the article does not explain this clearly, the example used alludes to this in an indirect way.

Personally I do think that this would be a more desirable sacrifice to make than restricting call order, but the React team thinks otherwise, it seems.


That’s not really all that different to everyone moving their CSS-in-JS and GraphQL queries out of a functional component body though, is it? This is kind of exactly the scenario Symbols seem to have been intended for.

Also... a nitpick, but `new Symbol` always throws a TypeError. And Symbol.for() is a useful escape hatch.


>That’s not really all that different to everyone moving their CSS-in-JS and GraphQL queries out of a functional component body though, is it?

I suggest you to take the `useSubscription` example from the "diamond problem" section and try to convert it to your proposed API. I think you'll see why it falls apart.

(Don't forget effects would also need keys.)


> Also... a nitpick, but `new Symbol` always throws a TypeError. And Symbol.for() is a useful escape hatch.

Yes, you're right. I was distracted with other stuff and meant just Symbol, without new. I'll fix it. Thanks.

As for the rest... Well, I don't really care much for React and many of the decisions they make. I don't like CSS-in-JS at all, and GraphQL... well, that one's nothing new.


>wouldn’t you just pass a Symbol key to useFormInput and it would then pass the key to useState, solving the supposed flaw?

Then `useFormInput()` wants to add another state (e.g. `isHovered`). How are you going to compose Symbols? We get to the next flaw (manual composition is annoying and error-prone).


> How are you going to compose Symbols?

I would’ve used a WeakMap with Symbol keys that mapped to a list or object containing Symbols.

Sorry if I’m just overlooking something obvious, but most of the examples that I’ve seen to explain why Symbol keys aren’t better than the current proposal seem to be just be examples of badly implemented custom hooks, not necessarily flaws with Symbol keys per se.


Can you convert the `useSubscription` example from the "diamond problem" section so we can compare "before" and "after"?

A key design goal is that creating a custom Hook is easy. You should be able to literally copy paste part of your component (e.g. a bunch of useState calls and some event handlers) and call it a day.

I'm struggling to see how what you're suggesting could be easy for the end user but maybe I'm missing something.


Here’s what I mean: https://gist.github.com/thomasfoster96/c4a20053c747196f027fc...

> A key design goal is that creating a custom Hook is easy. You should be able to literally copy paste part of your component (e.g. a bunch of useState calls and some event handlers) and call it a day.

I'd totally understand that reasoning, because the keyed Hooks are more verbose and would generally require two or three parts of a component to be copy-pasted – but the examples under Flaws #3 and #5 didn't make this clear (to me at least), and I hadn’t seen ‘ease of custom Hook implementation’ cited as an argument against keyed hooks before.

I’m really just playing devil’s advocate here, because in my playing around with Hooks I haven’t yet found a case where keyed Hooks are necessary, but I have accidentally put calls to useState() inside a conditional a heap of times.

Edit: Flaw #5, not #7


It is definitely possible but there’s so many more places you could make a mistake if each custom Hook has to do bookkeeping like this. I don't know if I could write or extend the code you wrote without making quite a few mistakes in the process. By comparison, I find following a rule like "calls should be static" much simpler.

My post does mention that we care about copy paste experience:

>Code passing non-unique or badly composed keys would accidentally work until a Hook is called multiple times or clashes with another Hook. Worse, if it’s meant to be conditional (we’re trying to “fix” the unconditional call requirement, right?), we might not even encounter the clashes until later.

>Remembering to pass keys through all layers of custom Hooks seems fragile enough that we’d want to lint for that. They would add extra work at runtime (don’t forget they’d need to serve as keys), and each of them is a paper cut for bundle size. But if we have to lint anyway, what problem did we solve?

I later go into why allowing conditional declarations of state or effects isn’t even particularly useful or desirable because the semantics are too confusing. So I do think I kind of addressed that.


> It is definitely possible but there’s so many more places you could make a mistake if each custom Hook has to do bookkeeping like this. I don't know if I could write or extend the code you wrote without making quite a few mistakes in the process. By comparison, I find following a rule like "calls should be static" much simpler.

The book keeping could be moved into a couple of utility functions - it’d be largely the same for most custom hooks.

I’m also not sure relying on a linter is necessarily going to make static call Hooks simpler. Poorly written hooks are going to be buggy whether they’re Symbol keyed or not. I think that one of the big disadvantages of static call Hooks would seem to be that incorrect conditional usage could still accidentally work.

> My post does mention that we care about copy paste experience

I think I misunderstood that section when I first read it - it makes sense now. I’m not convinced it’s a huge win though.

> I later go into why allowing conditional declarations of state or effects isn’t even particularly useful or desirable because the semantics are too confusing.

We’ll have to agree to disagree - while I’m not eagerly wanting to use Hooks in conditionals, I dont think the semantics are that confusing.


Small, late technical digression that's been bugging me, but Symbols won't provide the behavior you'd expect from a WeakMap.

A) Symbols often get interned as a part of the modules that own them and may never be garbage collected in the lifetime of an app. Thus the items in the WeakMap may never expire (because modules themselves are currently rarely unloaded/garbage-collected).

B) Primitive data types are actually expressly prohibited from being WeakMap keys in the spec (to avoid issues like [A]), and proper implementations are expected to throw errors if you try. MDN expressly makes this clear that this means that Symbols are not allowed to be WeakMap keys:

> Primitive data types as keys are not allowed (e.g. a Symbol can't be a WeakMap key).

Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


i think the problem with the naive symbol approach is you need to know all the keys the user hook wants to use and pass them all in which is not really scalable. however, instead of using a single symbol you could use an array of symbols which would solve that problem. a user hook would take in an array of symbols from its caller which would define its namespace then it would add a symbol to this namespace to create a new array for each nested call it wants to make.

the example would become something like:

    const nameNamespace = [Symbol()];
    const surnameNamespace = [Symbol()];

    const name = useFormInput(nameNamespace);
    const surname = useFormInput(surnameNamespace);


    const valueKey = Symbol();
 
    function useFormInput(namespace) {
      const [value, setValue] = useState(extendNamespace(namespace, valueKey));
      return {
        value,
        onChange(e) {
          setValue(e.target.value);
        },
      };
    }
there are a lot of drawbacks with doing it like this tho. like it is much less performant because you are creating all these arrays and having to do these comparisons across possibly big chains of symbols. also, there doesn't seem to be any way to easily store these chains in a map like structure for fast lookup except for using nested maps which is a bit weird.


This is how I would have hypothetically done it with WeakMaps: https://gist.github.com/thomasfoster96/c4a20053c747196f027fc...




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: