-
Notifications
You must be signed in to change notification settings - Fork 27
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
[email protected]: hooks, forwardRef support, and better types #83
Conversation
I'm probably not the best to review the TypeScript changes but everything else looks great. 👍 |
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.
Couple thoughts inline.
One thought is also if the equals
func should care / have a different branch for arrays over regular objects. Since the current for in
will just give us the indexes. Or is it only used on objects?
Also the Typescript typing changes are a bit over my head but I saw more types and that is always good right? :badpokerface:
|
||
const newValue = calculate(dependency, props); | ||
if (!shallowEqual(newValue, dependencyValue)) { | ||
setDependencyValue(newValue); | ||
if (!deepEqual(newValue, dependencyValue.dependency)) { |
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.
Curious why the change here from shallow to deep?
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.
whoops, copypasta from the new version of connect
. connect
requires the deepEqual
because the state in that component is a DependencyMap, so we need to check that the entire map is the same and trigger the setState if any of the sub-objects is meaningfully different, but in useStoreDependency
it's a single result so it can be a shallowEqual. I'll update
return false; | ||
} | ||
} else { | ||
if (!_equal(obj1[property], obj2[property], false)) { |
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.
Nit: could just return this.
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.
:nod:
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.
quick update, ya can't actually! since this is iterating over every property, returning the value here will short circuit the logic and could return a false-positive if this logic isn't the last property checked
|
||
describe('deepEqual', () => { | ||
it('deeply compares values', () => { | ||
expect(deepEqual(1, 1)).toBe(true); |
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.
nit: Array test?
I don't know if they're perfect, but they definitely do work, which is all I was really hoping for. ¯\_(ツ)_/¯ I'm excited to see how many CRM things explode when we introduce the TS types to those components. Excited for lots of weird stuff like |
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.
Oh man. I've been having deep nostalgia for GeneralStore the last few weeks. I'm absolutely going to build something with it once you release this 🤩
Passing extra arguments shouldn't cause a problem for existing dependencies, and the fact that there were actually different code paths based on the number of arguments your deref took always felt weird to me.
I think that had to do with the StoreDependencyMixin
and like [email protected] when dependencies could read from the state of the component they were bound to. We used the deref arg length, to decide if the dependency needed to re-evaluate when the component state changed.
general-store/src/dependencies/DependencyMap.js
Lines 147 to 157 in 69902c9
export function calculateForStateChange( | |
dependencies: DependencyMap, | |
props: Object, | |
state: ?Object | |
): Object { | |
return oFilterMap( | |
dependencies, | |
(dep) => dep.deref && dep.deref.length > 1, | |
(dep) => calculate(dep, props, state) | |
); | |
} |
At the time it saved a ton of cycles because mostly deps didn't use state, but I don't think that's relevant to the more recent connect strategies! It's probably safe to remove that behavior, but y'all would probably know better than me 😉
Wow, TIL. That makes perfect sense, and definitely explains why the |
Yeah, it was kind of a mess. Good riddance! 😛 |
Gonna look at integrating https://github.com/joarwilk/flowgen into the build process. If we're doubling down on shipping types we should probably output Flow types as well for completeness |
So good news, I finally got flow typegen working. Bad news, I'm not sure the types will be super useful 😞 since the TS typings require conditional types to work well and Flow doesn't support conditional types I'm not sure how useful they'll be. At least there's something though? |
Also, I think I'm going to just submit this as one huge PR. I tried to split things out atomically, but the types for |
@colbyr @Friss @henryqdineen going to merge and publish under |
4.0.0 published under the |
4.0 seems to be working well. I did have to change from We should also update the docs to reflect that you would need to do a |
good call, I'll update the docs before publishing latest. probably not worth building automation for this internally since it's an easy find+replace? |
Might take a bit of coordination to migrate libraries and UIs with this change but the underlying change is pretty simple. |
|
This is a proposal for a v4 of
general-store
. There's not actually any new functionality, but I figure that correct TS typings + the ESM main module is enough to warrant a new major version given that those are both technically breaking changes. Realistically, internally at HubSpot I don't think our repackage will have to bump a major version, since we shim CJS modules anyway I believe the default import of general store will still work, and none of the other code changes are breaking - connect + useStoreDependency still work fine.TypeScript
In v3, I did the TypeScript conversion, but given the complexity of the types (and the fact that I hadn't tried to use General Store in an actual TS project), the types were compiling successfully but didn't actually type anything in a meaningful way. The old types wouldn't properly validate the props on a connected component to verify they were a superset of the general store dependencies, and the output of a
useStoreDependency
call was always typed asany
.This PR fully reworks the TS types using generics and inferred conditional types to ensure that all General Store calls are typed properly. The TS compiler will fail the
connect
call if the wrapped component doesn't include all of the dependencies in the DependencyMap in its props type, anduseStoreDependency
correctly infers its return type assuming the dependency has an inferrable type (e.g. the TS compiler doesn't have to resort toany
).Type checks in action
Everything type checks just fine!
But if the store is of an unexpected type,
connect
anduseStoreDependency
both throw.Connect also throws if the dependencies don't sufficiently overlap with the component props.
Lastly, we now publish the project's TS types and reference them in the
package.json
, allowing external projects to pull in the type definitions properly.Connect
The old legacy version of
connect
was a bit bloated, and relied on a lot of extra mapping + reducing of objects, creating some internal performance problems. The new version just uses the same internals asuseStoreDependency
, allowing us to vastly simplify the internal code of General Store. By my estimate (some rough back of the napkin math) this reduces the bundle footprint of General Store by more than 30%.One small change that was made - we no longer check the
length
onderef
functions to determine whether or not to pass the state/dependency map. Passing extra arguments shouldn't cause a problem for existing dependencies, and the fact that there were actually different code paths based on the number of arguments yourderef
took always felt weird to me.Misc
GeneralStore.ts
entry point now uses ES2015 exports rather than CJS. The rest of the module uses ES2015 modules, so by converting the entry point to do the same, the library is now fully ESM compliant and will be treeshake-able.connect
works - the secondstate
argument was never documented up until now, you'd only find out about it from reading prior art or the library's actual source@colbyr