-
Notifications
You must be signed in to change notification settings - Fork 558
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
RFC: React Hooks #68
Conversation
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
Question about persisting values of 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? |
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? |
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 |
@pcmaffey Looks like this FAQ entry addresses your performance concern. |
Very exciting, this looks awesome! Questioning the tuple return type: const [count, setCount] = useState(0); Why an array tuple instead of an object containing const { value, set } = useState(0); Edit: yes, per @jaredLunde's comment, the properties would need constant names. |
@JoshuaKGoldberg How would you customize those object properties? Array is the cleanest way it works with custom naming. |
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. |
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 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 |
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 |
@eps1lon According to the FAQ,
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 Full disclosure: I'm planning to open an issue today to discuss a system for providing meaningful names for your |
Positional effects (using any of these Using 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. |
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 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 :). |
@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. |
@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. |
@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. |
I see what you mean. Hmm.. that's unfortunate 🤔 |
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:
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:
Other options for naming:
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. |
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:
Was it considered to pass the hooks into the functional component? Perhaps looking something like:
This means functional components which use hooks are Thoughts? |
for new react developers, useState hooks will be a magic. |
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? |
@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. |
@leebyron Alternatively, functional components could be invoked by the React internals with a meaningful |
What's the advantage of this approach over a HOC-based one such as the one used, for example, in recompose? |
@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. |
@benjamn The |
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. |
In your proposed design, It's also not entirely clear to me how you'd do something like this: function useCustomThing(value) {
useEffect(() => {
doSomethingWith(value)
})
}
function MyComponent({ prop }) {
let [state, setState] = useState('')
// ...
let derived = useMemo(() => expensiveStuff(prop, state), [prop, state])
useCustomThing(derived)
// ...
} In other words, it's not just "props and state" that need to be captured in effects and other closures, but anything that's calculated from them. Custom Hooks need access to the current values as their arguments — not just initial values. For example I don't have a clear idea of how you'd write this Additionally, Hooks should be able to chain. So you should be able to do const x = useStuff(props.foo)
const y = useStuff(x)
const z = useStuff(y) If Your proposal about accessing at "wrong time" also doesn't solve the core of the issue. It's not that there is a "right" and "wrong" time to access it. It's that we want closures to capture the values we rendered with, and to keep "seeing" those values forever. That's really important for concurrent mode where a notion of current value doesn't really exist. Hooks design models a component as being in many non-clashing states at the same time, instead of switching the "current" state (which is what classes model well). People don't really need to think about these details, but they're motivating the design a lot. One thing that slightly irks me is your assumption that we didn't want to really take this feedback into account, or that we just decided to ignore the discussion. Or that we didn't consider this design. I understand it might seem this way because my comment responding to your specific proposal wasn't very long. But as we mentioned earlier, realistically responding to every single proposal in detail would be a full-time job. I tried my best in this blog post but there's only so much you can document, and people will still come up with slightly different combinations. I'm sorry if our responses haven't been sufficient to satisfy your curiosity. Up until the release day, we read all the proposals, even if we didn't respond to all of them. I hope my questions above shine some light on why this particular variation didn't make it through. I will try my best to respond to follow-ups on this thread but I hope you can also understand that five months later after publishing the proposal, and given we also thought about it for several months before implementing (including this variation), responding to new slight variations that suffer from the same issues as older variations is not a top priority. (Thanks a lot for the bug report though!) |
Thanks @gaearon – you're totally right, I was relying all the Just as a side note, on the capturing side, I think perhaps you're misunderstanding my motivation for preventing access at the "wrong time" – maybe I'm way off here, but in my mind preventing accessing variables at the "wrong time" effectively forces the user to immediately capture any variables they plan on using within the hook body, by only allowing them to access the values synchronously ('right at the start of the function'). This means they're forced to "see" those values forever, as you suggest (seeing as any later attempts to read the values would fail with an error). The error throwing is therefore a mechanism that forces users to perform this capturing of immutable values. It could well be me that's misunderstanding your comment though. Your response has made me think about the tone I've been taking throughout this thread: I owe you and the rest of the team a big apology for throwing round accusations about not listening to users. It must be very frustrating to see people keep coming up with suggestions you've already considered and discarded. I suspect this is likely happening because (perhaps unusually for this project?) the design process happened behind closed doors, for reasons you discussed in your Reddit comment. This runs counter to most RFC processes I've been involved with, where somebody proposes an idea (perhaps with an initial strawman implementation to guide discussion), then various people chip in with criticisms, suggestions and alternatives, the merits of which are discussed in the RFC thread as the proposed solution gradually mutates into its final state, all alternatives having been discussed at various points throughout the thread. With the hooks API, the first stages of this process seem to have happened internally within Facebook, so for those of us reading this RFC we don't have any knowledge of the rejected prior attempts, so we have no idea that you've already discussed these alternatives at length and already found the flaws in them. If you'd dumped a bunch of gists/sketches of rejected alternatives to accompany the PR (to 'bring the spectators up to speed') there might have been fewer people like me pestering you with the same flawed proposals that you've already examined – although I do realise this involves effort on your part to placate a small minority of people who have problems with the API. If this had just been a feature announcement I would have just moaned a bit to my coder friends and moved on – it was the "RFC" positioning of it that made me expect more of a two-way debate, but that might be my misinterpretation more than anything, and I get that you're too busy keeping the ship afloat to respond in depth to every suggestion. The reason I got so deeply involved in this thread is that I care intensely about the way React is headed – in large part because React is currently my entire career: I market myself specifically as a 'React Developer' nowadays because I love using it so much that I wouldn't consider a job using any other framework. I still want to be able to call myself a React developer in 5 years' time, and so I really hope that between now and then nothing happens to discourage new users. I've had conversations with various tech decision-makers (mainly from imperative backgrounds) who already struggle to understand the concepts behind React even without hooks, and I hate to think of somebody one day rejecting React for use in a new project on account of it being 'too weird'. Regardless, I'm sure the project is in excellent hands: this is the first API decision I've ever disagreed with in the five years I've been using React, so I'm definitely optimistic in the long run. In short: I'm sorry for letting my exasperation get the better of me; you're all doing an incredible job, despite people like me making that a very difficult job at times. Hooks are a real game-changer – let's hope that when I'm still a React Developer 5 years from now there's even more cool features that make me love coming into work in the morning 😉 |
@gaearon would you comment on this approach? It's not nearly as complete (or pretty) as what @timkendrick posted, but I think I've worked through the most essential parts: state, effects, layout-effects, context and refs, with basic examples of each. What is the problem or what limitations do you see with this approach? Maybe point to something tricky you can do with hooks, and I'll see if I can reproduce it with this? |
@mindplay-dk it's almost certainly a better approach than my suggestion, but the obvious drawback to me is that you can't depend on prop values in e.g. the For example, consider the archetypal effect example: const StatusFeed = component((instance) => {
instance.useEffect(() => {
api.subscribeToFriendStatus(props.friendID, ...);
return () => {
api.unsubscribeFromFriendStatus(props.friendID, ...);
};
});
return (props) => ...;
}); The subscription mechanism requires access to current My first thought to try to work around this would be to store the friendID in some intermediary hook in order to access it within the effect: const StatusFeed = component((instance) => {
const [friendState] = instance.useState(({ friendID }) => ({ friendID }));
instance.useEffect(() => {
api.subscribeToFriendStatus(friendState.friendID, ...);
return () => {
api.unsubscribeFromFriendStatus(friendState.friendID, ...);
};
});
return (props) => ...;
}); …this feels a little clumsy due to having to introduce a composite mutable Maybe we could introduce some kind of const StatusFeed = component((instance) => {
const friendState = instance.useProps(({ friendID }) => friendID);
instance.useEffect(
() => {
api.subscribeToFriendStatus(friendState.current, ...);
return () => {
api.unsubscribeFromFriendStatus(friendState.current, ...);
};
},
[friendState.current] // This won't work
);
}); …as well as the 'encapsulation-breaking' properties of any potential I think this exposes a more general problem with effects/callbacks whose implementations depend on the current values of other hooks: how do you write the dependencies array for a const StatusFeed = component((instance) => {
const friendState = instance.useProps(({ friendID }) => friendID);
instance.useEffect(
() => {
api.subscribeToFriendStatus(friendState.current);
return () => {
api.unsubscribeFromFriendStatus(friendState.current);
};
},
() => [friendState.current]
);
return (props) => ...;
}); While this might get round the dependencies array issue, it still relies on the encapsulation-breaking As for whether it's a good idea to capture the relevant prop state as a separate hook value in the first place, I actually quite like this, seeing as to my mind it fairly accurately models the fact that the current state is derived from the props (and in retrospect I probably should have used a Anyway, this is just some stuff to think about – and there's also a good chance I'm misunderstanding your API, so take it with a pinch of salt 😃 |
@mindplay-dk You can perhaps go through |
@timkendrick this is great feedback, thanks :-) Did you understand that what gets passed to your const StatusFeed = component((instance) => {
instance.useEffect(() => {
api.subscribeToFriendStatus(instance.props.friendID, ...);
return () => {
api.unsubscribeFromFriendStatus(instance.props.friendID, ...);
};
});
return (props) => ...;
}); I guess I could pass them to the const StatusFeed = component((instance) => {
instance.useEffect(({ friendID }) => {
api.subscribeToFriendStatus(friendID, ...);
return ({ friendID }) => {
api.unsubscribeFromFriendStatus(friendID, ...);
};
});
return (props) => ...;
}); I'm sort of leaning towards the philosophy of keeping it "just a component" though, and it's perhaps a bit confusing with the |
Oh sorry, I didn't realise it was an actual instance – I assumed it was some kind of context object which you could use almost like a 'placeholder' reference to the component, gotcha. Is there even such a thing as a component instance with non-class-based-components? I thought that in the world of modern function components, you just have the combination of render function plus a collection of 'memory cells' within the current fiber that store hook state etc, and no actual instance? As far as I was aware the current thinking with the fiber internals is to move away from long-lived component instances towards a more stateless model that presumably is a better fit for async rendering / increased parallelization etc… I could be way off on this though. Either way, I think it's safe to say I misunderstood your API so my comments don't really apply to it 😃 |
@timkendrick I'm less interested in the micro performance details. What I like about hooks is how they enable you to compose behavior - I don't have any problem with class-based components as such (performance or otherwise) I'm just looking for a practical way to compose components and reuse behaviors and states. My main motivation is there are clearly initialization and rendering phases - I'd like to explore approaches that don't try to force them into a single phase. I don't think being "stateless" is even a goal (since there is clearly a component state, whether it's stored in a component instance or somewhere else) and I'd like to explore approaches that model that fact, rather than trying to make the API look functional by relying on global state, side-effects, counting calls, etc. I like simple things, that's all :-) |
I completely agree with the proposal here: facebook/react#14007 the text of which is below. I didn't see this pushed here as suggested, but the idea of useHooks passing an Do you want to request a feature or report a bug? Feature request? This is more to kickstart a discussion. What is the current behavior? With the current proposal, with any promise-based function call, useEffect must be used something like so function MyComponent(props) {
const [state, setState] = useState(0);
let unmounted = false;
useEffect(() => {
myXhrRequest()
.then(data => {
if (!unmounted) {
setState(data);
}
});
return () => {
unmounted = true;
};
});
...
} This is a fairly awkward pattern for promises, as one needs to add an extra check for mounted state potentially quite divorced from the unmount logic. It should be noted that for subscription-based patterns like with rxjs this is fine, but for promises this stinks and is a bit confusing - this would also get repetitive if there are multiple requests involved. Ideally the useEffect should make it easier to clean up asynchronous code without imposing on the pattern a user would to carry out async behavior. One manner that could remove the need for the confusing return function is to provide a isMounted helper function as an argument for the function passed into useEffect that returns whether the current component is mounted. It does not solve the verbosity completely, which might be outside the scope of React, but it removes the need for extra boilerplate that arguably hurts the readability. With this proposal, the useEffect part could be rewritten like this useEffect(({ isMounted }) => {
myXhrRequest()
.then(data => {
if (isMounted()) {
setState(data);
}
});
}); Just some thoughts anyhow, I would understand if this proposal is rejected. |
I really like the change. It makes for more clear and concise code. I have been trying to see an example of how to replace redux using context and hooks. Can you point me in the right direction? I have read through many articles and looks like everyone is doing it a little different and it is difficult to know, which one is better. |
@zeg-io Couldn't you just make that simple hook yourself and reuse it? const useMountedEffect = (callback) => {
let mounted = true;
useEffect(() => {
callback({ mounted });
return () => mounted = false;
});
};
// Use:
useMountedEffect(({ mounted }) => {
myXhrRequest()
.then(data => {
if (mounted) {
setState(data);
}
});
}); You could even adjust it so that the callback is only ever called if mounted, like: useEffect(() => {
if (mounted) {
callback();
}
return () => mounted = false;
}); |
@zeg-io I think we can cancel the request in useEffect(() => {
const myRequest = myXhrRequest()
.then(data => {
setState(data);
});
return () => {
myRequest.cancel();
}
}); If this a reuse state. cache it, or something like global store. useEffect(() => {
const myRequest = myXhrRequest()
.then(data => {
dispatch('updateSomeData', data);
// or globalStore.yourData = data;
});
return () => {
// no need to cancel
}
}); |
|
what about this? @pkf1994 |
One issue is your This is an issue I've noticed with the "bailout" pattern: I may have a I've done something similar but with a custom ref-based hook:
What's cool about this is:
When using this, you use |
I have a feeling that function MyComponent(props) {
const { paddingTop, content } = props;
const ref = React.useRef();
React.useEffect(() => {
// scroll to paddingTop when content changes?
ref.current.scrollTo(0, paddingTop);
}, [paddingTop, content]);
return <div ref={ref}>...</div>
} There is an undesired behavior: the hook is executed on paddingTop changes. Moreover, "content" is not, semantically, a dependency of the callback, but rather a dependency of the "when this side effect should happen". So I could use a ref, store the previous value of paddingTop, and compare the two. But that is cumbersome. What I would like to do, is express the "when this side-effect should happen" dependencies declaratively: function MyComponent(props) {
const { paddingTop, content } = props;
const ref = React.useRef();
React.useEffect(() => {
// scroll to paddingTop when content changes.
ref.current.scrollTo(0, paddingTop);
}, [paddingTop], [content]);
return <div ref={ref}>...</div>
} So a new signature for /**
* @param what - what this side effect does?
* @param whatDeps - which variables modify what the side effect does?
* @param whenDeps - which variables modify when the side effect occurs?
*/
type useEffect = (
what: (...args: any[]) => any,
whatDeps: any[],
whenDeps?: any[]
) => void; The community seems to be in need of a solution, see https://stackoverflow.com/q/55724642/2779871 What are your thoughts? EDIT-1: added signature. |
If you have ideas, concerns or suggestions, it would be best to file a new issue in the React repo or a new pull request in this repo. We're not tracking old discussions, and this one in particular will email 300 people on every comment. |
hook函数 与 hoc 使用的时机是什么时候?有什么区别和意义? |
For React it's easy to get the state and set function from an array, in the case of an object you have to extract keys using Object.keys() then spread the state and set function. In the case of an array index, 0 is the state and 1 is the function that handles the change in state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahmed
For the better or worst, this is a dead horse. Would one of the maintainers please lock this issue or at least restricted to avoid all the spam. |
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.)
Nov 16 Update by @sophiebits:
See @sebmarkbage’s comment #68 (comment) for a detailed response to the preceding comments that covers various tradeoffs and articulates why we’ve landed on the current design. If you have something to say, please check that it is not answered there already.