The Plan for React 18
reactjs.org
reactjs.org
In all seriousness, I'm sorry you had such a bad experience. That's not what we want the React community to be. Next time, feel free to tag me or someone else on the core team and we'll tell them to cut it out.
One thing I need to wrap my head around is avoiding unnecessary work with useDeferredValue, for example. I've got something set up which will defer multiple values, but I know the user will only want the most recent value by the time something can render. As a result, once values arrive it'll kind of cycle through each rendered value rapidly before stopping at the last one. I guess I need to implement some sort of throttling and/or debouncing under the hood better - this probably isn't up to concurrent mode to solve for me.
I'm glad to see batching being focused on. I have code which implements batching to some degree, and it's always struck me as so awkward that I can't rely on React to make good decisions for me in this area. The abstraction required in a context provider or component to accomplish this is fairly ugly and really gets in the way of focusing on the component's core responsibility. I think this will be a great improvement.
Tiny nitpick--we'd like to stop referring to it as a "mode" since the new gradual strategy is opt-in per feature. :-)
>As a result, once values arrive it'll kind of cycle through each rendered value rapidly before stopping at the last one.
Have you tried the latest alpha? The behavior you're describing sounds like a bug we fixed a few months ago. See https://github.com/facebook/react/issues/17185#issuecomment-....
> Tiny nitpick
Haha, 10-4 - I'll drop the habit
I use it 2014-style. Class-based components, componentWillMount, render, callbacks, and that's about it.
I love this workflow, and generally structure all my UIs this way regardless of platform - have a state struct and then fully render the UI as a function of the state. To me, that's the big hurrah for using React.
But I have no clue why React is at version 18 and why it keeps getting new features. I'm perfectly happy making quite complicated UIs with the pattern I know well. Should I be following along? Does it matter?
If this isn’t you I’m sure at least the general performance improvements will be nice otherwise I don’t see why you can’t just keep doing what you’re doing
You basically stick to writing pure functional components focused only on how to render, and try to abstract reusable logic into hooks, and expose only what you need
For example, you could make a useApi() hook and inside your component just do const { data, loading, error } = useApi('/endpoint'), so you can hide everything you don't need away from the rendering logic
Just FYI, once you add hooks, it is neither pure nor functional.
https://mckoder.medium.com/why-react-is-not-functional-b1ed1...
[1] Mostly useless aside: using a .then()able based plumbing would have opened up the possibility of using the async/await combinator language. The names async/await would give the wrong impression especially prior to the actual concurrency changes to eventually ship in React 18, but would have potentially been a more monadic fit.
And monads are impure [1]. If all your functions are impure then you're not doing functional programming.
[1] https://alvinalexander.com/scala/io-monad-doesnt-make-functi...
Disagree. It is in fact the defining characteristic of functional programming.
Ed: and I suppose if a function call allocates a stack frame it's no longer a pure function? Or are all functions of type crash-or-value?
Not necessarily. The IO Monad (Haskell et al.) is impure, a monad is just a functional abstraction that doesn't have anything to do with purity at all.
That's to say, at a certain point, all this is arbitrary.
Pure code is generally easier, but a program that's only pure code is pretty useless. You always need an escape hatch which is what some monads like IO or Futures provide. Monads and other functional paradigms allow us to write and interact with impure code in a pure way and mindset. That's exactly what hooks do as does the react mindset of, your view is a function of state.
Hooks easily let us separate that statefulness into their own abstraction - just as futures do for async calls, and IOs do for the file system. This lets us model our view as a function of state (and props which are just arguments to a function)
No. You can't interact with impure code "in a pure way". That makes no sense whatsoever.
I don't think you read the article I linked to earlier. Here it is again: https://mckoder.medium.com/why-react-is-not-functional-b1ed1...
You need state to do anything useful. A lot of functional programming revolves around the idea of isolating impure aspects of a language from the pure parts.
That's why we have Futures, IOs, Options, Eithers etc.
No one is saying they are pure, the types are an indicator that something impure is happening. They are abstractions that allow us to pretend they're pure and work with them regardless of the context of the type.
I can map over a Future, IO, or Option regardless of whether the Future resolved, the IO was successful, or the Option contains a value.
That's a functional mindset where the impurity is isolated to the data type and methods are exposed on the type that lets us ignore certain possibilities.
I don't care about the context when I map over one of these types, whether its a Future, Option, Array, IO etc. I can write my pure function and pass it off to map. Who cares if the Future really contains an error, the Option is None, or the Array is empty. Map handles all those cases for me and lets me interact with these impure aspects in a pure way.
The function I define and pass to map is pure and lets me interact with impure parts of the code in a pure way.
useState and useEffect should be thought of in the same way. Clearly it denotes something impure is happening, but the stateful value can be treated like a state machine that can be declaratively tested against a finite list of possible states.
In the case of a Promise, it's successful, pending, or error. For an Option its Some<T> or None. Etc etc.
If my hook returns 3 values, result, isPending, error (and if you really want, each value can be an option), I can easily handle each scenario deterministically.
That allows for pattern match like solutions (in JS it can be through cases clauses since there's no FP pattern matching).
This allows for writing components in a pure like approach even when its obviously impure.
There is really no need shoehorn something that's not a good fit for FP into the FP paradigm. It is not like customers are going to pay more money if it is FP. So don't bother.
If you’re using hooks like useState or useEffect I wouldn’t consider those functional components pure.
Hooks provide some nice syntactic sugar, which is not nothing, and make it easier to share logic between components. In my experience hooks have made my code easier to read, but they haven’t simplified it. The side effects and state might not be inlined but they’re still there. If anything they’ve made it less explicit because some of the details are hidden behind the magic that the React runtime uses to add state to functions.
But the beauty of hooks is as you say reusable statefull behavior. It also lets you treat your functional components more like a pure function in my opinion.
If the hooks you use are poorly designed, then yeah, it may be more difficult. But well designed and implemented hooks make your component appear more like a pure function.
The state is (more) decoupled from the UI code than before and lets me easily reuse behaviors across components.
And if you really wanted to make it 'pure', you could easily write a hook with an explicit return type that wraps the type of the initial value. If you're using typescript, you can then limit the possible types of the generic to only monads.
type State<T> = val as T;
const useStatex = <T>(initialState: T) => {
const [state, setState] = useState(initialState);
return [state, updateState] as [State<T>, (val: T) => void];
}
This new hook, useStatex, has an explicit type definition that indicates something stateful is taking place. Same as Futures. You can take this even further in the typing limiting the generic to only allow certain types for the underlying values to be monads, or even more simply type State<T> = Promise<T> | Array<T> | Option<T>
So you avoid an extra layer of nesting like State<Future<Array<T>>>
Point is, there are lots of ways to easily resolve any its not pure! arguments same as any other functional programming concepteta
The simplest way to limit the return type might even be to just do this
const useStatex = <T extends Promise | Array | Option>(initialState: T) => {The communication about the "why" of new features has been extremely clear. E.g. for hooks: https://reactjs.org/docs/hooks-intro.html
Thanks for the link, I remember when Hooks came out but I didn't feel the need to adopt them, I'll take a look again.
The useState hooks are a tiny bit clearer than the setState semantics. The effect hooks are less clear, but all told, the new mechanisms for handle global state and side effects better without Redux. (Redux is neat, but after working with it for years, I just have to conclude that in most cases it's just too much mental overhead compared to a more idiomatically Javascript solution.)
There's nothing wrong with continuing to use class-based components, but I think you'll find that everybody else is going to gradually deprecate it. The key facts about the work flow -- state to render function to surprisingly efficient DOM reconciliation -- remains the same.
https://www.reactiflux.com/transcripts/jordan-walke
> So I continued to explore framework-izing these ideas, and I had implemented several iterations of what eventually became React, in a few languages - one of the first explorations began as a rough implementation of the reconciler and component model in Standard ML (CreateClass was a "module function"). This was really great because SML embraces immutability by default which is natural when building React style components. We naturally wanted to deploy UI to web browsers, and at the time, the compile-to-JS landscape was not as mature as it is now - I don't even think source maps existed yet. So it made sense to port that exploration to JavaScript (...)
> One thing I noticed that when people were creating point to point bindings in their more traditional "MVC" app structure, it would almost always end up requiring "computable" bindings which invoke a function anytime a mutable cell had received a "change event". All these "computable bindings" ended up chaining together and small changes would end up causing large recomputation of the majority of the UI. I realized that functions already do transform inputs into outputs, and if we could just find a way to reinvoke those functions repeatedly, and quickly enough, that we could be much more expressive and concise, at not much more performance cost that the chain reaction that "computed bindings" would result in anyways.
I am asking because I do not remember them being used back then, so wondering if you had upgraded there.
Also what do you use for 'whole app' state handling (rather than component level) ?
Asking because React Context feature seems to be the right fit for the 'whole app' state handling, but it was not there back in 2014.
I also like you picked up preact for smaller project.. just because the size is so small. But for lager things (eg over 20K lines of JS) I seem to be staying with React.
Also, kudos the the React / facebook team for building such an exceptional framework. React has really enabled small teams like ours to build large applications and even a framework! It takes a lot of dedication and careful planning to create such a versatile lib for a broad audience. 8 years down the road and the team is still keeping it together while innovating web-dev to new frontiers.
Thanks again! And keep up the good work.
> I had also put the foundation in place, to ensure that React components could be server rendered - and that feature was more so inspired by demand and constraints at the time than any specific technology.
"Jordan Walke Transcript from Thursday January 26th, 2017 Facebook Engineer | Creator of React.js & Reason"
However, note that tree-shaking probably won't help much in the case of React. React isn't a library of independently useful features; it's a cohesive framework where features work together. We also have a really small API surface area. So there's no much to "shake" apart.
Arguably the main `react` package would benefit slightly from tree shaking, but those exports are already really small. The `react` package doesn't contain that much implementation; it mostly just calls into internal React DOM to schedule updates.
anyone who cares enough will opt in to use es modules, of this I'm certain.
Is there a reason that is not being considered?
https://github.com/facebook/react/issues/11503
However, there really isn't anything to tree-shake in React. The renderer implementation is effectively a single consolidated set of logic that handles all work for all types of components. So, there's no unused functions to get shaken out.
Here's the scenario: User is holding down the down arrow key. On each key event you have to update the screen so that appears to scroll up. But React gets in the way by batching setStates, and this causes the screen updates to be jumpy. Now you have to find a workaround. This is an example of React doing too much.
This does not sound correct. Quoting directly from the page on automated batching (https://github.com/reactwg/react-18/discussions/21):
>Note: React only batches updates when it’s generally safe to do. For example, React ensures that for each user-initiated event like a click or a keypress, the DOM is fully updated before the next event. This ensures, for example, that a form that disables on submit can’t be submitted twice.
If you see a problem like the one you're describing, please file an issue in the main React repo, and we'll have a look. I'm not sure how you could have experienced this behavior since we released the alpha an hour ago.
We call non-discrete events (like mousemove) "continuous", and for those we will batch updates across multiple events.
We've been testing this at Facebook for a while and we think our model is really solid, handling all the common cases and a bunch of edge cases, too. If you try it out and find something unexpected, please let us know and we'll fix it!
Like, yes, implementing batching in React was complicated, but in exchange the developer doesn't have to think about it anymore. It just works!
Edit: seems so! Post links to a release announcement on GitHub which says it’s “fully” supported.
However, React 18 does include a ton of work around Suspense, including:
- Fixes to quirks in its existing behavior (https://github.com/reactwg/react-18/discussions/7)
- startTransition API which lets you implement the "show old content while new data is loading" UX (https://github.com/reactwg/react-18/discussions/41)
- A whole new server renderer that uses <Suspense> for streaming HTML and hydrating the page in independent parts (https://github.com/reactwg/react-18/discussions/37)
So it's ongoing but maybe the "whole story" including recommended patterns will not quite be in the initial scope in 18.0 release.
I edited the announcement to clarify this.
- https://github.com/reactwg/react-18/discussions/41
- https://github.com/reactwg/react-18/discussions/37
Maybe somebody else has the energy to summarize them. :-)
The concern was because the old approach was to change the behavior globally. The new approach is to make it opt-in when you use specific new features that rely on it.
The new version apparently doesn't do that, unless you specifically use one of the new features, which means that you have accepted the other caveats for that scenario.
1. An entire generation of front end developers have staked their careers on a framework developed by Facebook. Facebook!
2. The hooks API. I honestly thought this was a joke when I first saw it. Developers will talk about the elegance of functional components then go and use the hooks API.
Personally, I prefer to use things that make more sense, even if I read dozens of articles and blogs explaining them.
Not to mention the hideous use APIs. Thanks you, React, for this array which apparently contains a state and a setter?
It's almost like taking the verbosity of class lifecycle methods, and 'pre compiling' it into a form which is terser, yes, but that now only a sufficiently informed reader can grok - the lifecycle is still there, it just has to be modelled by the reader instead of by the code.
Sometimes this is a good tradeoff, but not always.
As someone who started off thinking hooks were too "magic" to be taken seriously, such concerns are a thing of the past.
If you just think of calls to useState as variable declarations, it makes zero sense that you'd ever want to call useState in a different order.
this -
let userName = 'bob';
let favouriteColour = 'purple';
is this let [ userName, setUserName ] = useState('bob)';
let [ favouriteColour , setFavouriteColour ] = useState('purple)';
Hooks enable you to reason about the state your component is in in a way that class components couldn't.Hooks felt extremely awkward to me at first, because it was forcing me to write code in ways that felt unnatural. Once I stopped fighting it, the benefits became apparent, and now the old way feels awkward.
Hooks are to React what React was to jQuery.
Example: you have a {data, error} = fetchData() and then a useEffect(..., [data]) or 3. It would be very handy if you could just throw the error before the useEffect because you already know it's going to be irrelevant.
So then you put everything after in a new component (because that can be rendered conditionally) or keep checking for errors throughout the effects code. Both are kind of meh.
I'm not sure if this could maybe even work, actually, because there are no real out of order calls, only incomplete calls? But rules of hooks say no, and I have gotten the "hooks called out of order" error for it.
let [ data, setData ] = useState();
useEffect(() => {
let result = fetchData();
if (result.error) {
//do something, or abort?
return;
}
setData(result.data);
}, []);
useEffect(() => {
if (!data) return;
//perform secondary chained action using the fetched data
let result = fetchMoreData(data.guid);
}, [ data ]);https://en.wikipedia.org/wiki/Genetic_fallacy :P
IMO there are far greater concerns with centralized API services, which are nearly always opaque, closed-source, and incentivized towards vendor lock-in (AWS being the elephant in the room).
"Staking careers" is also a little much; React isn't that hard to learn (it's near-trivial compared to .NET or iOS development), and any developer worth their salt will have to frequently retrain into new patterns and frameworks during their career.
There can be legitimate concerns with power concentrations of open-source software based on the incentives of its stewards (Chrome comes to mind); but ultimately React carries a relatively small footprint, and can always be forked [0] if it goes in a direction that developers don't like.