Code Monkey home page Code Monkey logo

Comments (8)

salvoravida avatar salvoravida commented on September 28, 2024

Named Hooks & Named Stack Hooks is my idea to solve Hooks inside statement.
https://github.com/salvoravida/react-class-hooks

S.

from rfcs.

strayiker avatar strayiker commented on September 28, 2024

Didn't you think this is a reinvention of HOC pattern that Hooks are dismiss?

from rfcs.

PascalSenn avatar PascalSenn commented on September 28, 2024

Well, really the point of it is that it is a HOC. But as we drilled props down in the past, we now can lift state up to the HOC. This would enable library developers, especially UI libraries (Fabric UI, Material UI), to give the developer the control over a component.

from rfcs.

opatut avatar opatut commented on September 28, 2024

Meta: Am I understanding correctly that now you're talking about react-class-hooks specifics (HOCs and whatnot), not "Hooks inside if statement" anymore?

Motivation

Back on topic: I think there is enough motivation to remove the restriction of hooks not being able to live inside loops/conditions, we don't need super specific use cases for that. There will be enough use cases, and simply removing the restriction would probably be worth it anyway: having fewer rules means there are fewer rules beginners can break.

Reason for the current rule

If I understood it correctly, the rule "don't nest hooks inside branched logic" stems from the fact that the engine needs to keep internal state for each hook, and does so by tracking the call order. If you now mix up that order by introducing more or less hook calls in the middle, the engine can't connect the hook calls across multiple renders.

Proposed solution

I have an observation: We have a similar pattern in react already, with an appropriate solution -- mapping over arrays when rendering.

Because JSX (or the equivalent createElement calls) has to desugar to/is an expression, not an instruction list, we don't have a problem there with conditionals. They keep their position, the falsy case just turns into a "noop" element (false or null), but keeps its place, and therefore also its auto-generated key. Loops however, which are solved in the expression form via Array.prototype.map, are truly dynamic and require, as we all know, manually adding the key prop for re-render re-identification.

Maybe we can find a similar pattern for hooks, where the consumer hook or function component can "name" or "identify" hook calls with manual keys somehow. Ideas/suggestions for the API follow below.

There is one major difference to rendering components with keys: If you somehow screw up the key prop in rendering your list, in many cases it won't be too much of a problem -- AFAIK we only get a warning for missing the key prop when rendering a list, not an error. That's because, unless you're relying on browser internal state, the result of wrong key props will be a performance hit when rendering, not a functional change. That's different with hooks, where you pretty much rely on the cross-render-call hook identification to work 100%, and any "misaligned" hooks will break functionality.

API Ideas

I can think of multiple patterns for identification of hooks. These work in both if statements, as well as loops or .maps or whatever branching patterns you use. I'm displaying the if case here, but by replacing the identification key with some item-dependent value (just like in .map inside JSX) it would also work in loops etc.

API Idea 1: Name hook before calling it

When using hooks, react does interal state-keeping anyway, so this extension to that doesn't feel too dirty anymore ๐Ÿ˜‚

import {namedHook, useState} from 'react';

let state;
if (condition) {
  const key = 'conditionalState'; // in a loop, this would depend on the item
  namedHook(key); // this is new
  state = useState(item.initialValue);
}

API Idea 2: Always call hooks via helper

To prevent the need to detect hook calls, we could make it explicit, by always calling hooks via a helper, which gets an (optional?) argument for identification of the hook call.

import {use, stateHook /* replaces useState */} from 'react'

let state;
if (condition) {
  const key = 'conditionalState';
  state = use(stateHook(item.initialValue), key);
}

API Idea 3: Same as above, but different call syntax

Idea 2 effectively needs state to be a higher order function. An alternative would be to enforce the key parameter, and append the arguments to the actual hook function after that:

import {use, stateHook /* replaces useState */} from 'react'

let state;
if (condition) {
  const key = 'conditionalState';
  // arguments[2] and later are passed on to the `stateHook` call
  state = use(stateHook, key, item.initialValue);
}

API Idea 4: Add key as parameter to state-tracking hooks only

The important hooks are those that keep internal state. Basically, those are most (or all?) of the built-in ones (search for createWorkInProgressHook in ReactFiberHooks.js).

We could add another parameter key to all of these for identification. The createWorkInProgressHook function would not look up in a chain, but in a dictionary or tree, every time you use a built-in hook. You'd need your custom hooks to accept keys as well to append more sub-hook-IDs to and pass them down the chain.

import {useState} from 'react'

let state;
if (condition) {
  const key = 'conditionalState';
  state = useState(item.initialValue, key);
}

API Idea 5: Manually group hooks into "named hook groups"

These would allow grouping of automatically indexed hook calls into a named group, by pushing the "current group" onto a stack, and tracking hook calls inside the current "hook group". Stack control could happen manually via push/pop or in callback form. Maybe an even more advanced syntax could use generators, but I'm not too much into them ;)

Lame "push/pop" or "start/end" variant:

import {startHookGroup, endHookGroup, useState} from 'react'

let state;
if (condition) {
  startHookGroup("myConditional");
  state = useState(item.initialValue);
  endHookGroup();
}

Much better "callback" variant:

import {hookStack, useState} from 'react'

let state;
if (condition) {
  state = hookStack(key, () => {
    return useState(item.initialValue);
  });  
}

Optional or mandatory?

Most of these patterns could maybe also fall back to an increment counter / single-linked-list (as implemented right now) by default, so you could skip the manual identification of your hook calls if you're not using loops or conditionals. I'm not sure how good that idea is, or whether that should always warn the developer, because it's probably hard to impossible to detect whether branch logic is being used unless the number of hooks changes (at least in custom hooks, where you can't inspect the function's String representation for for/if/else keywords).


With these ideas in mind, it seems removing the rule of no branched-logic hooks comes at the cost of a bigger API surface. That's IMO the primary discussion we need to have, and only if it's deemed worth it decide on the specific API. However, I outlined my ideas for the API here so we can get a feel for what this "bigger API surface" might look like.

Disclaimer: I have not taken into account performance or compatibility considerations. If those ideas screw up either of those big time, apologies, I'm not knowledgable about that stuff ;)

from rfcs.

j-f1 avatar j-f1 commented on September 28, 2024

I have a question about all these APIs: What happens if the conditional is set to false or an item is removed from the loop?

Would the hookโ€™s state be cleared if the component re-rendered once without the hook being called? If not, when would that happen? If the state isnโ€™t removed, it would cause memory leaks.

from rfcs.

salvoravida avatar salvoravida commented on September 28, 2024

@opatut : named hooks and named stack hooks?

something already proposed and implemented here:
https://github.com/salvoravida/react-class-hooks

React Hooks implementation for Class Components. Support React >= 15.3.2

Have a look!
Salvo

from rfcs.

opatut avatar opatut commented on September 28, 2024

@j-f1: Good point, haven't thought about that yet. But I guess it would be cleared / cleaned up (e.g. cleanup of useEffect with the returned callback needs to happen too).

Why not clean it up? If you want always-persistent state, you should always request it to exist in your hook/component, so keep it around top level. If you have conditionally persistent state, you can wrap it in a condition, and it will only exist as long as the condition holds. Seems logical to me.

@salvoravida: As far as I can tell react-class-hooks primarily solves a different problem: hooks in classes. I think the syntax form with useX.create("name") doesn't really work in the function(al) world we're in right now. But it looks to me like one could create another API suggestion from it:

API Idea 6: Higher order function to wrap hook in stack activator

import {nameHook, useState} from 'react'

// wrap the hook with a stack switching mechanism
const namedUseState = nameHook(useState, key);

let state;
if (condition) {
  state = namedUseState(initialValue);
}

This doesn't work as nicely with dynamic names (think list iteration/mapping) though, cause you have to closure the key first, then apply the resulting wrapped hook function:

const states = items.map((item) => nameHook(useState, item.key)(item.initialValue));

from rfcs.

salvoravida avatar salvoravida commented on September 28, 2024

@opatut warning: you cannot use keyString because you could have collision with other hooks used in others ccustom hooks!
you need .create("name") (if you see my lib) because it will create a symbol and bind it.
also for array namedHook create a namedStackHooks

useXXX.createStack("name")

ok, i can create an extra api like your
nameHook(useState, "name") === useState.create("name")

from rfcs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.