Skip to content
This repository was archived by the owner on Dec 31, 2020. It is now read-only.

Preserve generics when using observer#244

Merged
danielkcz merged 5 commits intomobxjs:nextfrom
Kukkimonsuta:preserver-generic-through-observer
Feb 16, 2020
Merged

Preserve generics when using observer#244
danielkcz merged 5 commits intomobxjs:nextfrom
Kukkimonsuta:preserver-generic-through-observer

Conversation

@Kukkimonsuta
Copy link
Copy Markdown
Contributor

Related issue #243

This change allows wrapping generic components while preserving generic parameters.

There is also unintended change that preserves all static types. This has mostly positive effect, though $$typeof, type, compare, and render (which are not copied over) remain with their original type if defined on base component. I didn't find a way to omit these properties as any kind of narrowing will cause generic parameters to be converted to unknown.

@danielkcz
Copy link
Copy Markdown
Collaborator

@xaviergonz @mweststrate Could really use your help with reviewing this, I am not that strong with TypeScript yet.

@danielkcz danielkcz requested a review from xaviergonz November 25, 2019 09:22
@mweststrate
Copy link
Copy Markdown
Member

mweststrate commented Nov 25, 2019 via email

@Kukkimonsuta
Copy link
Copy Markdown
Contributor Author

I have noticed an issue where empty options were not accepted, which while silly could be an issue in future if more options are added. Should be fixed in last commit.

@mweststrate
Copy link
Copy Markdown
Member

mweststrate commented Dec 18, 2019

The types are getting fairly complicated / overloaded now, I wonder if we can make it a bit simpler by avoiding overloads, and inferring return type roughly as follows, I think the type inference and error messages will be better if we do it this way, as it avoids 'backtracking' the generic arguments, and takes the input argument as starting point:

export function observer<
  T extends React.FunctionComponent<any> | React.RefForwardingComponent<any>, 
  Options extends IObserverOptions
>(
    baseComponent: C,
    options?: Options,
): Options extends { forwardRef: true } 
  ? C extends React.RefForwardingComponent<infer TRef, infer P> 
    ? React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>
    : never /* forwardRef set for a non forwarding component */
  : C

@danielkcz
Copy link
Copy Markdown
Collaborator

As Michel said...

I wonder if we can make it a bit simpler by avoiding overloads

@Kukkimonsuta Did you try to remove other overloads?

@Kukkimonsuta
Copy link
Copy Markdown
Contributor Author

I have merged the two new overloads using Michels sample with slight modifications, however I'm not certain whether we want to remove the original overloads too as it would be breaking change and following transformation in users code would have to be done by users during migration to new version:

interface IProps {
    value: string;
}
const TestComponent = observer<IProps>(({ value, children }) => {
    return null
})

to

interface IProps {
    value: string;
}
const TestComponent = observer(({ value, children }: React.PropsWithChildren<IProps>) => {
    return null
})

@mweststrate
Copy link
Copy Markdown
Member

mweststrate commented Dec 18, 2019 via email

@danielkcz
Copy link
Copy Markdown
Collaborator

Btw, this next branch, we can afford to have some breaking changes, there is very few of those so far. But I personally want to keep observer<Props> for sure.

@danielkcz
Copy link
Copy Markdown
Collaborator

I am usually doing this and we have various aliases for children (NoChildren, SingleChild, Required<SomeChildren>, etc...).

type TProps = SomeChildren & {
  extra: string
}

export const Comp = observer<TProps>(({ extra }) => { ... })

I suppose doing observer(({ extra }: TProps) => { ... }) is not too horrible, but it will be pain to refactor that. We should probably create a codemod or something to migrate this automatically.

The "problem" with official PropsWithChildren is it refers to ReactNode which includes undefined and that is wrong (an app will crash). It's some legacy crap going deep into how class components work. I have a custom ReactNode as using only FC and it works nicely.

@mweststrate
Copy link
Copy Markdown
Member

mweststrate commented Dec 18, 2019 via email

@danielkcz
Copy link
Copy Markdown
Collaborator

It actually is documented ... https://mobx-react.js.org/observer-hoc
No surprise there since I wrote that docs 😎

@mweststrate
Copy link
Copy Markdown
Member

mweststrate commented Dec 18, 2019 via email

@Kukkimonsuta
Copy link
Copy Markdown
Contributor Author

I've experimented a bit further with this, however at this point I think it's out of my ability to merge rest of the overloads into one declaration.

The main issue I see is that there is a conflict of typing authority between the two: observer<MyProps>((props) => null) needs observer to declare type of the function component passed in, however observer(<T extends unknown>(props: MyProps<T>) => null) relies on observer respecting whatever is passed in.

@mweststrate
Copy link
Copy Markdown
Member

Sorry for the late follow up! Completely dropped the ball here, but I think it is fine as it is!

@danielkcz
Copy link
Copy Markdown
Collaborator

Ok, since this is going to next branch we can afford bit experimenting. I will publish another prerelease later with this.

@danielkcz danielkcz merged commit dfcbb67 into mobxjs:next Feb 16, 2020
danielkcz pushed a commit that referenced this pull request Apr 6, 2020
* Schedule uncommitted reaction cleanup (#121)

* Test cleaning up reactions for uncommitted components

* First attempt at a fix for cleaning up reactions from uncommitted components

* Use debounce instead of fixed interval

* Add unit test for missing observable changes before useEffect runs

This is a test to check that observable changes made between the
first component render and commit are not lost.

It currently fails (and did so before the change in PR #119)

* Add test for cleanup timer firing too early for some components

This demonstrates (in a slightly contrived way) how, if the
cleanup timer fires between a recent component being
rendered and it being committed, that it would incorrectly
tidy up a reaction for a soon-to-be-committed component.

* Update test for missing changes to check Strict and non-Strict mode

We had an existing test to check that observable changes between render
and commit didn't go missing, but it only checked in Strict mode, and
there's a problem with non-Strict mode.

* Add cleanup tracking and more tests

This adds full cleanup tracking, and even more tests:

- we now track how long ago potentially leaked reactions
were created, and only clean those that were leaked 'a while ago'
- if a reaction is incorrectly disposed because a component
went away for a very long time and came back again later
(in a way React doesn't even do right now), we safely recreate it and re-render
- trap the situation where a change is made to a tracked observable
between first render and commit (where we couldn't force an update
because we hadn't _been_ committed) and force a re-render
- more unit tests

* Fix renamed test file

When I renamed this file, I forgot the .test. suffix. D'oh.

* Extract tracking and cleanup logic out to separate file

* Update src/useObserver.ts

Co-Authored-By: RoystonS <royston@shufflebotham.org>

* Move some more tracking internals into the tracking code

* 2.0.0-alpha.0

* 2.0.0-alpha.1

* 2.0.0-alpha.2

* Upgrade to React 16.9

* Add dedup script and run it

* Remove deprecated hooks

* Increase size limit

* Remove note about Next version

* Remove unused productionMode util

* Improve readme

* Pin dependencies

* Merge master properly

* Ignore build cache files

* Revert removal of tsdx dep

* Remove .browserlistrc

* 2.0.0-alpha.3

* Bundling need to use build tsconfig

* 2.0.0-alpha.4

* Remove object destructuring from optimizeForReactDOM/Native (#240)

* Preserve generics when using `observer` (#244)

* Preserve generics when using `observer`

* Remove any casting from statics as it's no longer needed

* Re-add overloads for explicitly specifying props type

* Allow for passing options without `forwardRef`

* Merge new `observer` overloads

* Remove copy of UMD bundle

* 2.0.0-alpha.5

* Batched updates are mandatory

* Replace .npmignore with files field

* Fix tests

* Increase size limit

Co-authored-by: Royston Shufflebotham <royston@shufflebotham.org>
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Tarvo R <TarvoReinpalu@gmail.com>
Co-authored-by: Lukáš Novotný <lukas@dramiel.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants