Skip to content
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

"Lazy initial state" is actually greedy #3338

Open
Aprillion opened this issue Oct 17, 2020 · 9 comments
Open

"Lazy initial state" is actually greedy #3338

Aprillion opened this issue Oct 17, 2020 · 9 comments

Comments

@Aprillion
Copy link

Is there any good reason to call the useState(() => ...) feature "Lazy initial state"? The actual implementation is synchronous ("greedy") as far as I can tell, so I would propose to rename it to "Functional initial state" (for consistency with "Functional updates").

Related "Lazy initialization" in useReducer section => I would propose "Initialization function"

I can make a PR unless someone points out some aspect of the naming that I missed.

@gaearon
Copy link
Member

gaearon commented Oct 17, 2020

Hmm good point. I think the intended part that was implied is that the computation is cached. I don't think "functional" describes that but you're right this is not exactly "lazy" either.

@gaearon
Copy link
Member

gaearon commented Oct 17, 2020

We're gonna rewrite those parts anyway (#3308) so we'll do that change there.

@Aprillion
Copy link
Author

Brainstorming time if you haven't decided on the final version yet:

  • Initial state function
  • Memoized initial state (...but all initial state is memoized)
  • How to optimize expensive initial state calculations

@eps1lon
Copy link
Collaborator

eps1lon commented Oct 17, 2020

I don't think "functional" describes that but you're right this is not exactly "lazy" either.

The "lazy ref init" pattern uses the same terminology so if we decide to use another term we should look at every usage of "lazy" in the docs.

I always understood lazy as in "called when actually needed". For useState that means it won't be called on updates because we definitely are going to throw away the result. As opposed to useState(() => new Connection()) where we'll only throw it away if the component doesn't get mounted.

So for me I do consider the state lazy. That lazy init just happens to be needed sync on mount. On updates React is actually lazy and just ignores it.

If you look at "lazy initialization" on google:

Lazy initialization of an object means that its creation is deferred until it is first used.

-- https://docs.microsoft.com/en-us/dotnet/framework/performance/lazy-initialization#:~:text=Lazy%20initialization%20of%20an%20object,and%20reduce%20program%20memory%20requirements.

In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed.

-- https://en.wikipedia.org/wiki/Lazy_initialization

As I interpret the React docs the term is fitting. Though we should always make an effort to explain terms.

@Aprillion
Copy link
Author

Aprillion commented Oct 17, 2020

The "lazy ref init" pattern uses the same terminology so if we decide to use another term we should look at every usage of "lazy" in the docs.

@eps1lon hm, good point, but I don't think this feature was intended for actual lazy data structures in the sense you quote:

const [value] = useState(function*() { let n=0; while(true) yield n++ })
console.log(value.next())

I would probably define the generator outside of React, initialize the iterator in useEffect(..., []) and store the iterator instance in a useRef()...

The documentation that you point to supports my point (if I may interpret it charitably):

useRef does not accept a special function overload like useState. Instead, you can write your own function that creates and sets it lazily:

So the useState function argument is NOT intended for lazy initialization in the quoted meaning of "lazy"!! And the docs might need more extensive review of the usage of "lazy"...

FTR: Following is already "lazy" in this sense because a Component is a function that is not executed immediately, but only when actually rendered (and we don't call them Lazy Initialized Components):

function Component() {
  const connection = getConnection()  // initialized on first render and cached later, nothing to do with useState
  const [value, setValue] = useState(connetion)
}

@Aprillion
Copy link
Author

to clarify: useState(getConnetion) is a better pattern than my last example, but that fact has nothing to do with "lazy" vs "greedy" evaluation and everything to do with optimization to prevent calculating expensive values that would be ignored anyway

@Aprillion
Copy link
Author

Aprillion commented Oct 17, 2020

-- https://docs.microsoft.com/en-us/dotnet/framework/performance/lazy-initialization#:~:text=Lazy%20initialization%20of%20an%20object,and%20reduce%20program%20memory%20requirements.

And to be crystal clear whether we are on the same page in understanding of "lazy" from the quoted C++ article:

// Initialize by using default Lazy<T> constructor. The
// Orders array itself is not created yet.
Lazy<Orders> _orders = new Lazy<Orders>();
// We need to create the array only if displayOrders is true

if (displayOrders == true)
{
    DisplayOrders(_orders.Value.OrderData);
}

That is not how useState function argument works:

const [value, setValue] = useState(() => ({orderData: 'data'))

// value.orderData already exists on the above line, React does NOT defer evaluation until the line below

if (displayOrders === true) {
  console.log(value.orderData)
}

@eps1lon
Copy link
Collaborator

eps1lon commented Oct 17, 2020

// value.orderData already exists on the above line, React does NOT defer evaluation until the line below

It couldn't know this without an actual compiler. As soon as the state is declared it is considered "used". But I understand how this is an overly technical explanation.

I would still caution against changing the term because it might make perfect sense right now to some people (though I am biased here because it makes sense to me). I'd rather explain the term in the context of React. The docs already use a bunch of terms that aren't technically correct in every instance (pure, side-effect free, memoization).

@smeijer
Copy link

smeijer commented Oct 17, 2020

I'm with @eps1lon. Specifically:

So for me I do consider the state lazy. That lazy init just happens to be needed sync on mount. On updates React is actually lazy and just ignores it.

But I also understand the problem that @Aprillion mentions. Maybe we can call it something like function argument or initializer, just to stop any confusion?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants