New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: React Hooks #68

Open
wants to merge 2 commits into
base: master
from

Conversation

Projects
None yet
@sebmarkbage
Collaborator

sebmarkbage commented Oct 25, 2018

In this RFC, we propose introducing Hooks to React. See the RFC and the documentation for more details.

https://reactjs.org/docs/hooks-overview.html

View formatted RFC

(The talk video has now been published too.)

RFC: React Hooks
In this RFC, we propose introducing *Hooks* to React. See the RFC and the documentation for more details.

https://reactjs.org/docs/hooks-overview.html
@grabbou

This comment has been minimized.

grabbou commented Oct 25, 2018

Question about persisting values of useState - is the preferred way to create a custom usePersistedState hook that calls into storage mechanism?

I was wondering if there was a way to batch the write operations in environments such as React Native and how did you approach that problem at Facebook during your initial adoption?

@pcmaffey

This comment has been minimized.

pcmaffey commented Oct 25, 2018

Is this the right place for feedback? First of all - amazing work. I love where this is going with composability. My big question is about performance of Hooks.

Are all these function hooks redefined and recalled on every render?

@pheuter

This comment has been minimized.

pheuter commented Oct 25, 2018

Just watched the React Conf keynote, awesome presentation, super excited to give hooks a shot!

Not sure if this is the right place to ask but was wondering how function components that make use of hooks affect server-side rendering. In useEffects() for example, will errors be thrown when accessing browser apis like window during ssr?

@pheuter

This comment has been minimized.

pheuter commented Oct 25, 2018

@pcmaffey Looks like this FAQ entry addresses your performance concern.

@JoshuaKGoldberg

This comment has been minimized.

JoshuaKGoldberg commented Oct 25, 2018

Very exciting, this looks awesome!

Questioning the tuple return type:

const [count, setCount] = useState(0);

Why an array tuple instead of an object containing count and setCount members? Is this for performance (and if so, do we have numbers to show browsers or Node behaving strongly differently)? It feels like leaving the returned object open to adding more fields might make it easier to remain backwards compatible in the future, similar to how APIs often end up taking in single objects as a parameter rather than long lists of parameters.

const { value, set } = useState(0);

Edit: yes, per @jaredLunde's comment, the properties would need constant names.

@jaredLunde

This comment has been minimized.

jaredLunde commented Oct 25, 2018

@JoshuaKGoldberg How would you customize those object properties? Array is the cleanest way it works with custom naming.

@alexeyraspopov

This comment has been minimized.

alexeyraspopov commented Oct 25, 2018

Why an array tuple instead of an object containing count and setCount members?

You can assign names you need. Object destructuring requires you to put specific keys. You may end up having something like

let { state: name, setState: setName } = useState('Name');
let { state: surname, setState: setSurname } = useState('Surname');

Which is not the case with tuples.

@jamesplease

This comment has been minimized.

jamesplease commented Oct 25, 2018

I'm still familiarizing myself with this API, but I'm optimistic about these changes. Great work, React team!

My main concern with hooks is related to the learning curve. There are some APIs introduced that new (and seasoned) developers may not immediately pick up on.

One in particular that stands out the most to me is useEffect(myEffect, []). It's not very expressive. For those who haven't read the docs, what do you think this does? I'd be surprised if someone could guess.

For those who are still familiarizing themselves with the API, it calls the effect only on mounting and unmounting, and not on updates (docs here).

I know that this proposal introduces a lot of new functions, but it might be worthwhile adding another one that functions the same as useEffect(myEffect, []). As for the name? I'm not sure. Perhaps something like useMountEffect()?

@eps1lon

This comment has been minimized.

eps1lon commented Oct 25, 2018

It is a very early time for Hooks, so some integrations like DevTools support or Flow/TypeScript typings may not be ready yet.

Will I be able to inspect what hooks a component is using?

Having an object with named properties as state made it easy to see what's what. I guess this will be hard with the new useState?

@benjamn

This comment has been minimized.

benjamn commented Oct 25, 2018

@eps1lon According to the FAQ, useState is backed by a list of anonymous memory cells:

There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.

So if you ever find yourself trying to debug the underlying state, you're probably going to be working with a linked list of unnamed values. To figure out which memory cell is which, you'll have to refer back to the order of your useState calls.

Full disclosure: I'm planning to open an issue today to discuss a system for providing meaningful names for your useState calls, which should significantly relax the current usage restrictions, and eliminate the need for a linter to enforce those rules.

@spicydonuts

This comment has been minimized.

spicydonuts commented Oct 25, 2018

Positional effects (using any of these use* functions is an effect, even just to read state) worry me. It seems easy to get wrong, and doing so might not present until production. For the same reason it also seems like it'll be extremely difficult to introduce to beginners -- "the magic function works here but not there".

Using Symbols instead of positional keys would make it easier to understand and harder to get wrong:

const $count = Symbol("count");

function MyComponent() {
  const [count, setCount] = useState($count, 0);
  ...
}

Edit for clarification: I don't mean to say the Symbol would be a global key -- it would still use the current context (the Fiber, I assume) as the primary key and the Symbol as the secondary key (where the effectful call order is currently being used).

Another edit for followup: After playing with hooks a bit more I don't feel as strongly about this as I thought I would. It's not perfect, but you could also pass the wrong Symbol in this alternative as well. Also seeing the eslint plugin catching on relieves the worry I had a bit too.

@adamhaile

This comment has been minimized.

adamhaile commented Oct 25, 2018

Just curious, are React hooks at all inspired by S and Surplus? There are similarities down to even the names and terminology used. S has S.effect() and S.cleanup(), and useState() is analogous to S's S.data().

For instance, here's the hooks example in Surplus:

function Example() {
  const count = S.data(0); // S.data instead of React.useState

  S.effect(() => { // S.effect instead of React.useEffect
    document.title = `You clicked ${count()} times`;
  });

  return (
    <div>
      <p>You clicked {count()} times</p>
      <button onClick={() => count(count() + 1)}>
        Click me
      </button>
    </div>
  );
}

In fact, I used almost this exact same example in the Surplus FAQ to explain how functional components could have state -- the very same issue hooks are solving for React.

I'm the author of S and Surplus, and reading the hook docs gave me a strong case of déjà vu! The similarities may be coincidental -- we're probably reading the same upstream sources. But if there was any influence, I thought that would be cool :).

@alexeyraspopov

This comment has been minimized.

alexeyraspopov commented Oct 25, 2018

@alqamabinsadiq, sorry if my comment was somehow confusing. This is not how the hook intended to be used, I was making an example of additional effort required for using object destructuring. This is how the correct code looks like:

let [name, setName] = useState('Name');
let [surname, setSurname] = useState('Surname');

Less effort to write, less effort to read.

@sophiebits

This comment has been minimized.

Member

sophiebits commented Oct 25, 2018

@adamhaile To my knowledge we weren’t influenced at all by Surplus (this is my first time seeing it). It’s true the “effect” name is similar, but both names are descriptive, so I guess it shouldn’t be too surprising that the names overlap.

@sebmarkbage

This comment has been minimized.

Collaborator

sebmarkbage commented Oct 25, 2018

@benjamn @spicydonuts If you decide to publish a follow up RFC with named hooks which doesn’t have the conditionality constraints, then be sure to cover other pitfalls that you may also get. In the current proposal there is clearly one thing that will unexpectedly break when you don’t have names. However there are other things that might become confusing too. Such as that an effect doesn’t cleanup and refire when it’s in a conditional. So even with names you might find it best practice to avoid conditionals.

@spicydonuts

This comment has been minimized.

spicydonuts commented Oct 25, 2018

I see what you mean. Hmm.. that's unfortunate 🤔

@rudolfolah

This comment has been minimized.

rudolfolah commented Oct 25, 2018

I like the idea, but I think the naming doesn't really reflect what's going on here. The term hook in other contexts, such as web hooks or Emacs, implies that something happens at a particular point in time and calls out to some other functions/methods to make it happen.

In the words of the Github docs for Web hooks:

Webhooks allow you to build or set up GitHub Apps which subscribe to certain events on GitHub.com. When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL.

For example web hooks are called whenever Github detects a branch has been created. They're called in Emacs whenever a file is loaded or a particular mode (for syntax highlighting + shortcuts) is activated.

In React, the only hooks I can think of at the moment are the componentDidMount and componentWillUnmount functions, since they're part of a lifecycle and call out to some other function (as part of being overridden in a Component class)

From the React Hooks documentation it looks like these are managers:

  • useState manages and coordinates state
  • useEffect manages and supervises side-effects
  • useContext manages context

Other options for naming:

  • manager
  • supervisor
  • coordinator
  • handler

I'm sure there are other, better names other than "hook" which can be used.

Also, the naming of the methods don't need to change since none of them seem to have the word "hook" in them, the naming change would just affect the rest of the documentation/examples.

@leebyron

This comment has been minimized.

leebyron commented Oct 25, 2018

Loving this new API, but I have one primary concern - where the hooks come from.

I'm concerned that these hook functions are provided by the top level react module and imply (or require) global state to work. Ideally, hooks and effects functions could be provided to the functional component when it's executed.

So while this example as illustrated by Dan requires from the module:

const { useContext } = require('react')
const SomeContext = require('./SomeContext)

function Example({ someProp }) {
  const contextValue = useContext(SomeContext)
  return <div>{someProp}{contextValue}</div>
}

Was it considered to pass the hooks into the functional component?

Perhaps looking something like:

const SomeContext = require('./SomeContext)

function Example({ someProp }, hooks) {
  const contextValue = hooks.useContext(SomeContext)
  return <div>{someProp}{contextValue}</div>
}

This means functional components which use hooks are (Props, Hooks) => ReactNode. While this is still not "pure" because hooks have effects and are not pure, at least the renderer could provide the appropriate hooks per call-site instead of relying on global state. I could imagine that the Fiber renderer might have different implementation of hooks than some potential future renderer, a server-side renderer, a test renderer with mocked effects, etc.

Thoughts?

@KARTIK01

This comment has been minimized.

KARTIK01 commented Oct 25, 2018

for new react developers, useState hooks will be a magic.

@ntucker

This comment has been minimized.

ntucker commented Oct 25, 2018

How does this interact with React.memo()? Does it just shallow check props and ignore state and context. If not, how does it know what state and context to check?

@alexeyraspopov

This comment has been minimized.

alexeyraspopov commented Oct 25, 2018

I'm concerned that these hook functions are provided by the top level react module and imply (or require) global state to work. Ideally, hooks and effects functions could be provided to the functional component when it's executed.

@leebyron, I think, the way how it works, is by using an incapsulated access to the current rendering fiber that holds the state and effects. The API may look like something "global" happens under the hood, but it is still scoped to the specific fiber that reflects the component that is rendering at the moment.

@benjamn

This comment has been minimized.

benjamn commented Oct 25, 2018

@leebyron Alternatively, functional components could be invoked by the React internals with a meaningful this object, containing any number of useful methods.

@david

This comment has been minimized.

david commented Oct 25, 2018

What's the advantage of this approach over a HOC-based one such as the one used, for example, in recompose?

@sebmarkbage

This comment has been minimized.

Collaborator

sebmarkbage commented Oct 25, 2018

@leebyron Conceptually they’re algebraic effects. It just happens that the engine (a pointer to the current “handler”) lives in the react package. Note that the actual implementation doesn’t live in the react-package. So anyone can implement their own dispatcher. Even inside a react render. If this is a popular pattern you could just have a general purpose pattern for this like “dispatcher” package or something.

Just like context there doesn’t have to be one. In multi-threaded environments there can be several.

We considered passing it but then you would have to pass it through multiple layers of indirection. We already know this to be a pain (which is why we have context to begin with) and for something as light weight as custom hooks it is even more so.

@jaredLunde

This comment has been minimized.

jaredLunde commented Oct 25, 2018

@david acdlite/recompose@7867de6

@sebmarkbage

This comment has been minimized.

Collaborator

sebmarkbage commented Oct 25, 2018

@benjamn The this was actually in the first idea and later dropped because it added weirdness and got even weirder when composed with custom hooks. Since they would need to change to a different variable or use .call.

@probablyup

This comment has been minimized.

probablyup commented Oct 25, 2018

Are there any concerns about the interaction of transpilers with state hooks? The order of function calls could conceivably be rearranged by a transpiler or minifier in a way that differs between an SSR and client-side bundle.

@ChibiBlasphem

This comment has been minimized.

ChibiBlasphem commented Nov 13, 2018

@Kovensky @timkendrick in the proposal of Tim we can access hooks by their reference and getHookValue

@timkendrick

This comment has been minimized.

timkendrick commented Nov 13, 2018

@ChibiBlasphem: @Kovensky was talking about my most recent version "option 4" that I added in response to feedback from @FredyC. It was a valid concern, but can be worked around (see the second version of option 4)

@askbeka

This comment has been minimized.

askbeka commented Nov 13, 2018

@Kovensky that is the challenge, one might not choose react if application size is a big concern.
React could provide a way to extend it and define a other way to define Components and their behavior.
This opens up ways for community to decide what is the good component and allow library authors to find a better way to express their ideas without trying to hack around and fit into restrictions of React

@Retsam

This comment has been minimized.

Retsam commented Nov 13, 2018

One thing I'd add to the "Hooks vs. HoCs" argument, in favor of hooks: they're way friendlier for static typing. I don't know about the situation in Flowtype; but in Typescript at least, HoC patterns don't infer well and often require pretty complex types, and interfere with patterns like generics.

In theory, that probably says more about Typescript and its current limitations, but pragmatically (shoot, I just outed myself as a member of the "pragmatist camp" 😉) it's a huge reason I'm excited for hooks based APIs.

And I am a big believer that "if it's hard for the compiler, it's probably hard for the human". I get that there's a lot of functional purity goodness about the idea of HoCs, but I do think they bring with some of the "intuitive clarity of abstract mathematics", as well.

@satya164

This comment has been minimized.

Member

satya164 commented Nov 13, 2018

@Retsam even with Flow it's a nightmare to properly type HOCs.

@timkendrick

This comment has been minimized.

timkendrick commented Nov 13, 2018

@Retsam I agree – TypeScript isn't quite there yet when it comes to partial application etc. Seems like the mood of the meeting is against HoCs anyway, so I'll stop trying to defend them here 😃

@timkendrick

This comment has been minimized.

timkendrick commented Nov 13, 2018

Another alternative simplified option for people who don't like nested closures:

// Option 5

const SelectableUser = React.withHooks(
  {
    selected: (props, hooks) => useState(false),
    online: (props, hooks) => useFriendStatus(props.friend.id),
    otherStuff: (props, hooks) => useTotallyUnsafeSideEffects(),
  },
  function SelectableUser(props, { selected, online }) {
    const [isSelected, setSelected] = selected;
    const isOnline = online;
    return (
      <label className={isOnline ? 'online' : ''}>
        <input type="checkbox" checked={isSelected} onClick={() => setSelected(!isSelected)} />
        {props.friend.username}
      </label>
    );
  }
);

// Alternative array form

const SelectableUser = React.withHooks(
  [
    (props, hooks) => useState(false),
    (props, hooks) => useFriendStatus(props.friend.id),
    (props, hooks) => useTotallyUnsafeSideEffects(),
  ],
  function SelectableUser(props, [selected, online]) {
    const [isSelected, setSelected] = selected;
    const isOnline = online;
    return (
      <label className={isOnline ? 'online' : ''}>
        <input type="checkbox" checked={isSelected} onClick={() => setSelected(!isSelected)} />
        {props.friend.username}
      </label>
    );
  }
);

Any of these any better for you @FredyC?

@askbeka

This comment has been minimized.

askbeka commented Nov 13, 2018

@timkendrick I see that limitation arises from simplicity in usage, otherwise hooks had to provide ways to explicitly pass identifier for the hook or create unique instance of hook beforehand and used in the body of component.

With generators problem stays the same but there might be different options to solve this problem

@mcjazzyfunky

This comment has been minimized.

mcjazzyfunky commented Nov 13, 2018

@timkendrick First, thanks for your interesting and well-written comments. I especially like the remarkable friendly and polite language that you are using :)

Of course it's a matter of taste, but at least IMHO there's basically nothing wrong with using a factory pattern.
Is it more verbose? Yes, obviously ... but is it really awfully verbose? I really doubt that :)

May I ask you two questions regarding your "Option 2" example?

1.) Are there any known use cases where selected and online are used for something else than just being the first argument for getHookValue invocations?
Just asking because I am not really sure whether the obviously impure getHookValue function is really needed => isn't it possible just to use selected() instead of getHookValue(selected) as you have done it syntactically similar in "Option 4"?

2.) Let's say the custom hook useFriendStatus uses (like all custom hooks) some hooks internally.
Where do createHook and getHookValue come from inside of the custom hooks?

@KylePalko

This comment has been minimized.

KylePalko commented Nov 13, 2018

const hooks = {
  selected: (props, hooks) => useState(false),
  online: (props, hooks) => useFriendStatus(props.friend.id),
  otherStuff: (props, hooks) => useTotallyUnsafeSideEffects(),
}

export const Component = ({ selected, online }) => function SelectableUser(props) {
  const [isSelected, setSelected] = selected();
  const isOnline = online();

  return (
    <label className={isOnline ? 'online' : ''}>
      <input type="checkbox" checked={isSelected} onClick={() => setSelected(!isSelected)} />
      {props.friend.username}
    </label>
  );
}

export default React.withHooks(hooks, Component);

@timkendrick, I like what you've presented quite a bit. I like that the above is possible, reminds me of the Container Pattern. You'll notice I exported the Component variable, that is then easily testable somewhere else, which is my biggest problems with hooks—the oddity it will be to test.

EDIT: Hopefully I didn't break your design in abstracting this out, but take it as pseudo-code if I did 👍

@timkendrick

This comment has been minimized.

timkendrick commented Nov 13, 2018

@mcjazzyfunky Thanks! Excellent questions. Yeah TBH anything with a separate 'hook instantiation' phase outside the render function would be fine by me.

  1. Agreed – I originally separated the two because I wanted to separate the impure getHookValue stateful function from the (immutable) hook definition instance, but who are we kidding – might as well be honest with ourselves and just make the hook instances stateful functions

  • createHook: As far as I'm aware, createHook should not need to be be called within custom hooks. Other functions allowed to return hook definitions (a purely descriptive object that would be returned by useState() and friends), but passing one of these to the component's createHook function tells the component to take that definition and turn it into a stateful 'hook instance' that exists for the duration of the component instance's lifecycle. So within your custom functions you'd just need useEffect() or whatever.
  • getHookValue: Good point. (In my original version, getHookValue was a library-level export, but I decided against that). Another reason to make hook instances callable like you suggest.
@timkendrick

This comment has been minimized.

timkendrick commented Nov 13, 2018

@KylePalko Thanks – yep testability concerns were why I was plugging the HoC approach, which would allow you to test the hook behavior totally independently of UI. Understandably though, people find them fiddly to write so prefer to combine the two into a single component.

Interesting approach with extracting the second argument as a 'contained component' – should actually be pretty simple to feed mock arguments into the view for unit testing. Nice!

The more I think about it, the more I feel like those arguments in the 'simplified' option 4 should be the evaluated hook values, then the contained render function can be reinvoked whenever one of the values changes (like reselect does), rather than making them lazy/callable.

Think I might go back and edit that now…

@askbeka

This comment has been minimized.

askbeka commented Nov 13, 2018

@Kovensky Just curious. Can you elaborate why it is hard to type generators, I haven't used TypeScript that much, but knowing other typing systems in different languages, this should not be a problem. Is there any example I could look at

@Kovensky

This comment has been minimized.

Kovensky commented Nov 13, 2018

TypeScript has no knowledge that what you yielded has any correlation at all to what yield will return. A yield's return is always a non-signalling any.

@vadzim-revolist

This comment has been minimized.

vadzim-revolist commented Nov 13, 2018

@timkendrick while option 2 looks interesting, option 5 has one big disadvantage - there is no chance for flow or typescript compiler to understand that there is unused state variables and to inform the developer about an abandoned code.

In the following code

const [selected, setSelected] = useState()

if selected and setSelected is not used in the rest of the body existent compilers can warn the developer that there is unused variables.

@mcjazzyfunky

This comment has been minimized.

mcjazzyfunky commented Nov 13, 2018

@timkendrick
FYI: @PutziSan has opened a separate discussion thread regading that factory pattern thing two days ago - just in case you are interested
PutziSan/react-factory-hooks#1

To complete my comments about your proposals:

[Edit: The following examples are just for trying to increase conciseness a bit - really no need to discuss them - as the first pattern has already been declined by the community and the second pattern can be discussed in the obove mentioned seperate discussion thread if wanted]

I think what you've suggested in "Option 2" could be a bit rewritten (without changing the basic concept, I think) like follows:

/*
  Edit: Just because it has caused some confusion: That local "use" object has
  the following fixed methods:
  state, context, effect, layoutEffect and hook (no need for callback and ref, I think)
  use.hook(useSomeCustomHook(a, b)) is just syntactical sugar for:
  useSomeCustomHook(a,b)(use) because I personaly like the use.hook(...) syntax better
  ("use.hook" was called "useCustom" in my example some days ago).
   
  The main difference to most other approaches I have seen, is that there are no stateful
  and effectful global React.useXXX functions ... all the dirty stuff will be done by that local
  "use" object - and everybody is used to local objects that do dirty stuff,
  so I thought this may be at least an alternative to "dirty" global functions.
  Obviously, using this "use" object is more verbose :(
*/

const SelectableUser = React.withHooks(use => {
  const
    selected = use.state(() => false),
    online = use.hook(useFriendStatus(props => props.friend.id))
  ...
  return props => {
    ....
  }      
})

const useFriendStatus = idSelector => use => {
  // ...
}

But I have already suggested something like that a couple of days ago and it seems that nobody likes that pattern :)
... and I think someone of the React core team has somewhere pointed out that they've already evaluated the idea of passing around a "hooks" object (called "use" in my example) and found out that it may not be the best idea (but I do not know the details why)

To reduce the verbosity and to get rid of such local "hooks" or "use" objects you could use the following (this is basically the pattern that @PutziSan has already suggested - it's about the pattern not about implementation details):

const SelectableUser = React.withHooks(() => {
  const
    selected = useState(() => false),
    online = useFriendStatus(props => props.friend.id)
  ... 
  return props => {
    ....
  }      
})

function useFriendStatus(idSelector) {
  // use some React.useX functions and custom hooks here
}
@KylePalko

This comment has been minimized.

KylePalko commented Nov 13, 2018

@mcjazzyfunky the use object isn't too bad, but does require some extra knowledge around that object. Whereas @timkendrick's solution is a bit more lean. Not a huge advantage, but maybe I'm missing the advantage of a use object over Tim's (seemingly more direct) alternative.

@Kovensky

This comment has been minimized.

Kovensky commented Nov 13, 2018

One thing that kinda bugs me about almost all the proposals to make it "not magic" are basically just making HOCs that don't even need to be in react core. The only reason the dynamic scoping looks "magic" is because it's an inherently new pattern.

I've commented this before, but a function component is not really "just a function that returns an object", it's a fiber that is executed by the React runtime. The hooks just give you access to thread-local storage (well, fiber-local storage) through dynamic scoping.

@KylePalko

This comment has been minimized.

KylePalko commented Nov 13, 2018

@Kovensky Many of us aren't opposed because it's magic. We're opposed because it goes against a core tenet of the functional programming paradigm—the function is no longer pure, it's a product of its environment.

While we don't have to be dogmatic, we have to wonder, why are we throwing out a core principle of a paradigm for our own convenience?

I'm especially wondering why are we throwing it out when there are seemingly similar options that don't violate the paradigm.

@timkendrick

This comment has been minimized.

timkendrick commented Nov 13, 2018

@vadzim-revolist Maybe I'm misunderstanding you, but sometimes you want to have hooks that aren't used in the render function, but used solely for its side-effects. In my example the useTotallyUnsafeSideEffects() hook illustrates this, but a real-world example would be e.g. updating the document title whenever the props are updated. If you're talking about unused parameters in the render function itself, they should be picked up by IDEs/linters as you would expect.

@mcjazzyfunky Thanks for putting so much thought into this. My reactions:

  • I personally think use.xxx is a step in the wrong direction - relying on a predefined set of global hooks could be a bit of an API compatibility nightmare (e.g. for preact-style alternatives, or for testing frameworks which mock the library), also not great for tree-shaking etc (all the hooks will be bundled whether you like it or not). I think it would be better if use were a function that you could pass any inbuilt/custom hook definition (essentially my createHook suggestion)
  • I don't want to speak for the React core team, but I think they were objecting to having to pass the hooks object as a second constructor argument to every component throughout the app, not to the use of a 'bag-o-stuff' hooks object in itself

@Kovensky interesting comments. Some thoughts:

  • I'd suggest that the reason these things end up looking like HOCs is because HOCs emerged to fill exactly this need: until now, they were the officially-endorsed way to compose complex behavior in components (and they do a pretty solid job of it – not perfect, but definitely not bad). Personally I think it's a good thing that these suggestions don't need to be in the core (who ever asked for a framework with a larger core?)
  • If I understand correctly what you mean by 'dynamic scoping', the reason dynamic scoping looks "magic" is because JS is a lexically scoped language, not a dynamically scoped language, so this is very confusing to somebody who is expecting their code to work like normal JS. The second half of the magic comes in the fact that I can call some arbitrary function called "useSomeStuff()" within a render function, totally ignoring the return value, then some side-effect happens as a result. This seems to totally contradict the principle of a render function being a pure function of props and state, which the React team were plugging so hard a year or two ago (and which IMO is what I absolutely love about React).
  • I actually disagree with the idea that 'a function component is not really "just a function that returns an object"'. Until now, functional components (i.e. SFCs) have been exactly that: a function that takes props and turn it into a static description of a rendered element hierarchy. As mentioned, I think the simplicity and flexibility of this approach is brilliant (and so did the React team until not so long ago!)
  • I'm not an expert on this, but it seems to me that the "fiber" side of things is just how it happens to be implemented, and is never mentioned in any of the API docs as far as I know. React 17 could do away with fibers completely, while still keeping an identical API to the current one. The API docs do talk a lot about 'state' however – and without knowing the implementation details, it seems to me that fiber-local storage is just private state that only the framework is allowed to access.

I think that what we're really all dancing around here is the need for a first-class "stateful component" primitive, which isn't based on the existing 'component class' API but rather allows behavior to be built via composition of encapsulated modules. My concern is that if we're introducing a new type of stateful primitive then we ought to do it explicitly, rather than overloading our existing pure-function components (SFCs) by crowbarring state into them, thereby sacrificing their purity (and therefore their primary motivation in the first place).

@hamedmam

This comment has been minimized.

hamedmam commented Nov 13, 2018

how would you guys recommend getting state from props in hooks ?
a replacement for

state = {
    name: this.props.name
  };

in class components

should I use useEffect as "componentDidMount" and pass the props to state ?

@FredyC

This comment has been minimized.

FredyC commented Nov 13, 2018

@hamedmam const [name, setName] = useState(props.name) ? If you mean something else, please supply more complete example.

@hamedmam

This comment has been minimized.

hamedmam commented Nov 13, 2018

@FredyC oh awesome, I didnt think twice lol

@mcjazzyfunky

This comment has been minimized.

mcjazzyfunky commented Nov 13, 2018

@timkendrick @KylePalko

I do not want to pollute the discussion thread with too much not-so-useful information, so please do not answer to this comment. Just for the record: I think there might have been some misunderstanding, therefore I've added some explanation to the first example in #68 (comment)
Like said, really no need for further discussion about that mentioned idea of mine - that whole stuff is obsolete :)

@FredyC

This comment has been minimized.

FredyC commented Nov 13, 2018

@timkendrick

My concern is that if we're introducing a new type of stateful primitive then we ought to do it explicitly, rather than overloading our existing pure-function components (SFCs) by crowbarring state into them, thereby sacrificing their purity (and therefore their primary motivation in the first place).

I am seeing similar concerns all over the place, but I don't get it. How are hooks forcing you to have stateful component? You can continue writing pure components that just render stuff (especially with React.memo). Coincidentally, the very same function approach can be used to introduce a stateful component. Frankly, I find that simply brilliant. You don't need to learn two different API as you were with class components till now. 🎉

It is/was one of the main reasons why people opted to used class components for almost everything so they don't need to convert SFC when a state or lifecycle is suddenly needed. Now we have the ability to start with a functional component and if it happens we need a simple state to turn the button red when clicked, then why the hell I would need to convert that component to something completely different? 🙄 I absolutely adore, that I can add a single line of code and that's it.

So, in the end, it would be about good behavior and establishing patterns to avoid abusing hooks to mix it with your UI components too often. It's even super easy to make HOC built on top of "hooked" component (see the draft of such approach)

@timkendrick

This comment has been minimized.

timkendrick commented Nov 13, 2018

I am seeing similar concerns all over the place, but I don't get it

If a lot of people are raising a concern that doesn't seem to make sense to you, there are two possibilities: a) they're all deluded, or b) you don't fully understand their point of view. (If you fully understand the point of view but don't agree with it then that's a different matter.)

I totally get the ease of use / ergonomics argument – that's why I've been trying to find a happy compromise and come up with simpler formulations that are still pure-ish (see my options 4 and 5, which I really don't think are particularly verbose at all) – but you haven't seemed to want to engage with them or offer constructive feedback. I suspect neither of us is going to convince the other of the merits of their view so we should probably stop spamming everyone else on the list 😃

EDIT: sorry, I didn't mean this comment to sound so snarky – just wanted to say that we're going round in circles so I think we'll have to agree to disagree on this one

@FredyC

This comment has been minimized.

FredyC commented Nov 13, 2018

@timkendrick I've dismissed those variants as well for the reasons I've mentioned above. Apologizes, that I wasn't explicit enough :) We did have class components and SFC alongside and you can see how it ended up. Some people opted to use classes only to avoid a hassle of converting later. That means if you introduce an approach that requires any extra hassle then my humble prediction is that history is going to repeat itself and we won't get anywhere. In my opinion, the verbosity is not such a big concern in the end, but the need to actually think in different patterns if you need somewhat smarter component might be.

I am pretty much convinced that the React team has been alongside these thought trains and dismissed them for similar reasons and it led them to the current proposal. Perhaps I am mistaken, but by reading the motivation I have a feeling it was one of the goals to simplify the process of creating components be it simple UI component or complex one.

I don't want to convince you about anything. Your point of view is surely valid, but I just don't see the actual problems you are trying to solve. Sorry :)

@j-f1

This comment has been minimized.

j-f1 commented Nov 13, 2018

I created a re-implementation of class components that demonstrates how some common features of classes can be done with hooks instead.

@bpartridge

This comment has been minimized.

bpartridge commented Nov 13, 2018

So Hooks finally started to "click" for me when I realized they're very similar to Python decorators. In fact, they're essentially Python-like decorators, curried against the renderer, in an alternate syntax that reduces duplicate typing.

To be clear: I'm NOT advocating for Hooks moving towards decorator syntax, and I agree with the reasons why this suggestion isn't the right direction to move. But I think that the comparison with decorators is useful nonetheless, and perhaps could guide some of the ways in which we introduce Hooks to skeptical stakeholders.

Consider, for a moment, that Python almost adopted the following syntax: https://www.python.org/dev/peps/pep-0318/#community-consensus ...

using:
    classmethod
    synchronized(lock)
def func(cls):
    pass

That's incredibly similar to the use prefix that's becoming the convention for Hooks. In time, I think we'll come to see the presence of a "preamble" in a Javascript function - one that contains only function calls starting with the word use - as perfectly analogous to a decorator block, but one that can "inject" parameters without you needing to write the names twice. And that's a good thing; it means that Javascript as a language has become flexible enough that this type of emergent behavior can be implemented without changes to the language itself.

Additionally, I think that thinking in terms of Python can address some common criticisms of Hooks:

  • Hooks break the contract that anything in a Javascript function should support arbitrary control flow: Technically, even Python decorators are inside an execution context where you can do a lot of fancy control flow if you don't follow the @ shorthand syntax... but nobody would think about wanting to apply a decorator in a loop, or conditionally apply a decorator. The decorators themselves handle control flow, and that's exactly what the implementations of hooks do.

  • Hooks silently access global state: If you were a Python developer who always needed to write @synchronize(someReallyCommonLock) def func: ... everywhere, you might consider writing a curried decorator def _sync(f): return synchronize(someReallyCommonLock)(f) then just calling @_sync def func: ... everywhere instead. This is very reasonable. It's also exactly what React exposes to you. The fact that someReallyCommonLock, or in this case theInternalReactRendererState, is hidden from the user is actually a benefit.

  • Hooks carry a runtime cost: So does Example 4 here; it's an extra function call, on every call to the decorated function, that essentially just copies some references into new variables. Which is no more or less than what useState does every time it's called. We need to trust our interpreters and JITs that this type of jump is optimized.

So I, for one, welcome our new Hook overlords. It's taken me some time to come around to that opinion, but now I honestly can't imagine any changes to the core syntax that would improve this proposal.

@KylePalko

This comment has been minimized.

KylePalko commented Nov 14, 2018

In my opinion, the verbosity is not such a big concern in the end, but the need to actually think in different patterns if you need somewhat smarter component might be.

@FredyC I think this is what I'm having a hard time agreeing with and understanding. We're setting aside FP principles (pure functions and function composition) for the sake of having a simpler API. Why should we sacrifice those principles when the simplicity of the API is not even marginal?

I genuinely don't understand this line of thinking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment