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

fix: snapshot typing for ref #146

Merged
merged 1 commit into from
Apr 28, 2021
Merged

fix: snapshot typing for ref #146

merged 1 commit into from
Apr 28, 2021

Conversation

dai-shi
Copy link
Member

@dai-shi dai-shi commented Apr 26, 2021

fix #145

  • tag ref type with const enum
  • rename NonPromise to DeepResolveType (BREAKING CHANGE in types)
  • use typescript instead of babel for cjs build
  • use ts-jest for test

@dai-shi
Copy link
Member Author

dai-shi commented Apr 26, 2021

@italodeandra Please try if this fixes your issue.

@codesandbox-ci
Copy link

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit f288379:

Sandbox Source
React Configuration
React Typescript Configuration
valtio ref with React.Ref Issue #145
valtio ref with React.Ref (workaround) Issue #145

@italodeandra
Copy link
Contributor

italodeandra commented Apr 26, 2021

I'm amazed on how I didn't have to do anything for testing your change. Codesandbox bot already grabbed my cb, updated it with the commit release, and forked with it. It's working flawlessly. Thanks!

And I think it was nice that you liked my idea of the naming. Don't know how you feel about it but for me it feels nice.

@italodeandra
Copy link
Contributor

Off-topic question though: Why use ts-jest for test? Isn't stock jest with babel-jest better?

@dai-shi
Copy link
Member Author

dai-shi commented Apr 26, 2021

Yeah, codesandbox ci is nice.

As for naming, I wasn't happy with NonPromise so your idea seems better. It's not nice to rename too often, but I don't think it's often used explicitly. I will explicitly write about the type name change in the release note.

Why use ts-jest for test?

Because babel doesn't support const enum.

@dai-shi
Copy link
Member Author

dai-shi commented Apr 26, 2021

@Aslemammad Do you want to review?

@@ -15,7 +15,7 @@ import {

import { getVersion, subscribe, snapshot } from './vanilla'
import { createMutableSource, useMutableSource } from './useMutableSource'
import type { NonPromise } from './vanilla'
import type { DeepResolveType } from './vanilla'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it's a great type name, I don't have any problem with it, but could it be shorter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeepResolve? ResolveType?
DeepResolveType feels better as it looks special.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, no problem with that, let's go with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a complex name for a complex utility type 😂

And by complex I mean special like @dai-shi said.

@dai-shi dai-shi merged commit c05ceef into master Apr 28, 2021
@dai-shi dai-shi deleted the fix/deep-resolve-type branch April 28, 2021 12:30
@italodeandra
Copy link
Contributor

@dai-shi I use an external typing for the proxy. But the AsRef is not export and I had to do this:

export type IMessage = {
  id: string
  content: string
  nodeRef: Ref<HTMLDivElement> & ReturnType<typeof ref>
}

Because this nodeRef doesn't trigger the AsRef from the generic type DeepResolveType, so for this case in particular it didn't work for me without adding that & ReturnType<typeof ref>.
Do you think it's possible to improve this?

Here's the CB with the problem: https://codesandbox.io/s/valtio-ref-with-reactref-problem-2-25lxs?file=/src/App.tsx

@italodeandra
Copy link
Contributor

italodeandra commented Apr 28, 2021

I'm also getting a strange TS error that I can't reproduce on CB:

image

But the workaround from my previous comment worked:

image

@dai-shi
Copy link
Member Author

dai-shi commented Apr 29, 2021

So, this doesn't work for you? (can't see it in the first screenshot.)

export const drawerState = proxy({
  currentPosition: 0,
  isOpen: false,
  isRendering: false,
  ref: ref(createRef<HTMLDivElement>()),
  startPosition: 0,
  with: 0,
})

We don't want to export AsRef, as it's an implementation detail and can change.
If you really need to get the ref type on your end, this is a hack:

const dummyRef =
  process.env.NODE_ENV === 'development'
    ? ref(createRef<HTMLButtonElement>())                               
    : undefined                                                               
type MyRef = NonNullable<typeof dummyRef>                                   

@italodeandra
Copy link
Contributor

So, this doesn't work for you? (can't see it in the first screenshot.)

Yes, you guessed the code right. I couldn't replicate in CB, so this could be related to yarn or WebStorm. Do you have any suggestion for me to help you fix it?

We don't want to export AsRef, as it's an implementation detail and can change.

I agree with you. But in this case, I think we should aim to fix it without even needing to use this type, right?

@dai-shi
Copy link
Member Author

dai-shi commented May 2, 2021

Yeah. Oh, I missed the error code TS4023.
In this case, we'd need to export it anyway...

Or, if there were a better solution than const enum hack...

@dai-shi
Copy link
Member Author

dai-shi commented May 3, 2021

I was able to repro the error with tsc --declaration with your codesandbox downloaded locally.

Let's collect more feedbacks/ideas and see what options we have.

Meanwhile, if you need to create type files with --declaration. The workaround for now is this, which is something you already tried before this PR.

export const state = proxy({
  buttonRef: ref(createRef<HTMLButtonElement>()) as Ref<HTMLButtonElement>
});

export default function App() {
  const { buttonRef } = useSnapshot(state);

  useEffect(() => {
    console.log(buttonRef);
  }, [buttonRef]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
        <button ref={buttonRef as Ref<HTMLButtonElement>}></button>
    </div>
  );
}

@italodeandra
Copy link
Contributor

But it feels weird to cast it like that.

createRef<HTMLButtonElement>() is exacly Ref<HTMLButtonElement>.

I didn't fix the problem, I just switched to a different one.

This solution #145 (comment) looked better to me. So I think this AsRef should be removed and I can implement the solution I mentioned on a TypeScript module augmentation. Looks better and doesn't "plaster" Valtio to a very specific use case. Because the real culprit here is how TypeScript compares two types that are the same but one is an alias and the other was deeply transpelled (I don't understand why he doesn't consider the same thing, because nothing is changed. I think it's because React's Ref is a very complex type).

@dai-shi
Copy link
Member Author

dai-shi commented May 7, 2021

Solution with module augmentation sounds nice. Can you do it with the current version?
I guess we want to change exported functions from arrow functions to function declarations.

AsRef is actually correct because we don't resolve promise for ref().

@italodeandra
Copy link
Contributor

AsRef is actually correct because we don't resolve promise for ref().

Yes, you're right. I didn't think about it. But it breaks type comparisons, because every property that used ref will become PropertyValueType & AsRef and for TypeScript this makes it a completely different type from PropertyValueType. Not sure why though.

@italodeandra
Copy link
Contributor

italodeandra commented May 7, 2021

Solution with module augmentation sounds nice. Can you do it with the current version?

I edit this answer a couple times. It doesn't work with the AsRef so I had to roll back to 1.0.3.

export { ref, snapshot, getVersion, proxy, subscribe } from "valtio"

declare module "valtio" {
  type Options = {
    sync?: boolean
  }

  export declare type DeepResolveType<T> = T extends Function
    ? T
    : T extends { current: unknown }
    ? T
    : T extends Promise<infer V>
    ? V
    : T extends object
    ? {
        [K in keyof T]: DeepResolveType<T[K]>
      }
    : T

  export declare const useSnapshot: <T extends object>(
    proxyObject: T,
    options?: Options | undefined
  ) => DeepResolveType<T>
}

@dai-shi
Copy link
Member Author

dai-shi commented May 7, 2021

Oh, I didn't know you could augment const arrow function.
Do you also need to augment ref()?

I wonder if there's better way of AsRef than const enum hack.

@italodeandra
Copy link
Contributor

italodeandra commented May 7, 2021

Do you also need to augment ref()?

No, my entire augmentation is only that. But to make it work on 1.0.4 I need to remove AsRef completely on the augmentation, so I stayed on 1.0.3.

I wonder if there's better way of AsRef than const enum hack.

I'll try to think of something. This enum hack is only typing, so I can make some tests on my own augmentation and see if anything breaks on the entire code.

@dai-shi
Copy link
Member Author

dai-shi commented May 7, 2021

But to make it work on 1.0.4 I need to remove AsRef completely on the augmentation

This is probably what I meant.

This enum hack is only typing

Yes. The point of AsRef is type only. We don't want to change the JS code.

@italodeandra
Copy link
Contributor

This is probably what I meant.

Oh, right. I'm currently using 1.0.3, so that's why my answer was weird. I'm currently not working on it but as soon as I have something impacted by this again I'll let you know and I'll also try to help think of something that can replace AsRef. For now let's just keep as is.

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

Successfully merging this pull request may close these issues.

NonPromise utility type breaks React.Ref type
3 participants