Redux: TypeScript: Excessive stack depth comparing generic preloadedState with DeepPartial

Created on 20 Jun 2019  路  9Comments  路  Source: reduxjs/redux

Do you want to request a _feature_ or report a _bug_?
Bug

What is the current behavior?
Passing a generically typed preloadedState parameter into createStore creates an "Excessive stack depth" error in TypeScript due to the comparison between S and DeepPartial<S>

Sandbox: https://codesandbox.io/s/busy-leavitt-wrpnx?fontsize=14

More information at https://github.com/Microsoft/TypeScript/issues/21592#issuecomment-503837592 - this is caused by a TypeScript bug, but the TypeScript devs have suggested that Redux may not actually need to be using the DeepPartial type here.

What is the expected behavior?
No TypeScript errors

Which versions of Redux, and which browser and OS are affected by this issue? Did this work in previous versions of Redux?
In theory this affects any version of Redux using the DeepPartial type. The symptoms and workaround have changed in recent versions of TypeScript; I've reproduced the issue with [email protected] and [email protected]

Most helpful comment

I'm currently at the beach, so I can look at it next week.

All 9 comments

Do you have self-referential data/types in your state? You shouldn't, as your preloadedState needs to be serializable (by simple means, e.g. JSON.serialize, not via a special serializer) so it can be provided to the store. A circular reference would create a problem with this process, so would likely be another issue you run into even if the typings are fixed. As such, I'd almost say this is TS doing it's job and preventing a runtime error.

The type S is unconstrained, so this function is making no assertions that the state doesn't have self-referential data/types, but neither does the signature of Redux.createStore itself (I don't know of any way to model this constraint using TypeScript). In practice I don't pass any self-referential objects into the function.

The issue as I understand it is in the fact that, because S is a generic type, TypeScript goes into a stack overflow trying to compare it with the recursively defined DeepPartial<S>.

It doesn't recurse infinitely, though. It will bail once it hits a non-object: https://github.com/reduxjs/redux/blob/beb1fc29ca6ebe45226caa3a064476072cd9ed26/index.d.ts#L252

Unless you have circular references in your types, or have a very deep state tree, this isn't something you should trip in your types.

Unfortunately, we have to keep the preloadedState as partial. It may not be a complete representation of the store state once the INIT action is fired. This is particularly true for those with dynamically-added reducers. That's a common enough use case that we need to support it.

If anything, we should probably extend it to handle Arrays:

export type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends Array<infer U>
    ? Array<DeepPartial<U>>
    : T[P] extends ReadonlyArray<infer U>
    ? ReadonlyArray<DeepPartial<U>>
    : DeepPartial<T[P]>
}

That won't fix the issue you're running into, but I don't think there's something we can do without breaking a greater set of users. Sorry :(

Sorry, I hadn't looked at the latest master! This bail condition actually does fix the issue I'm having.

The latest release v4.0.1 has DeepPartial defined with no bail condition:

https://github.com/reduxjs/redux/blob/c5d87d95f3b9b0ebdb57791f69b53d8507cebbed/index.d.ts#L213

Is there a timeline on a new release which will include this improved type definition?

I'm currently at the beach, so I can look at it next week.

@timdorr Hope you had a fun time at the beach. Any chance we could get a patch with this fix?

Yep, sorry. I can look into a release soon.

Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

benoneal picture benoneal  路  3Comments

elado picture elado  路  3Comments

dmitry-zaets picture dmitry-zaets  路  3Comments

ramakay picture ramakay  路  3Comments

ms88privat picture ms88privat  路  3Comments