Relay-compiler-language-typescript: Missing $data and $key implementation

Created on 6 Oct 2019  Â·  11Comments  Â·  Source: relay-tools/relay-compiler-language-typescript

Comparing Todos

When working with fragments on Flow, we have this:

import type { ReaderFragment } from 'relay-runtime';
import type { TodoListFooter_user$ref } from "./TodoListFooter_user.graphql";
import type { TodoList_user$ref } from "./TodoList_user.graphql";
import type { FragmentReference } from "relay-runtime";
declare export opaque type TodoApp_user$ref: FragmentReference;
declare export opaque type TodoApp_user$fragmentType: TodoApp_user$ref;
export type TodoApp_user = {|
  +id: string,
  +userId: string,
  +totalCount: number,
  +$fragmentRefs: TodoListFooter_user$ref & TodoList_user$ref,
  +$refType: TodoApp_user$ref,
|};
export type TodoApp_user$data = TodoApp_user;
export type TodoApp_user$key = {
  +$data?: TodoApp_user$data,
  +$fragmentRefs: TodoApp_user$ref,
};

When working with TS, the output generated is:

import { ReaderFragment } from "relay-runtime";
import { TodoListFooter_user$ref } from "./TodoListFooter_user.graphql";
import { TodoList_user$ref } from "./TodoList_user.graphql";
declare const _TodoApp_user$ref: unique symbol;
export type TodoApp_user$ref = typeof _TodoApp_user$ref;
export type TodoApp_user = {
    readonly id: string;
    readonly userId: string;
    readonly totalCount: number;
    readonly " $fragmentRefs": TodoListFooter_user$ref & TodoList_user$ref;
    readonly " $refType": TodoApp_user$ref;
};

The difference between these two snippets are:

// fist part
import type { FragmentReference } from "relay-runtime";
declare export opaque type TodoApp_user$ref: FragmentReference;
declare export opaque type TodoApp_user$fragmentType: TodoApp_user$ref;
/* equivalent
    declare const _TodoApp_user$ref: unique symbol;
*/

// second part
export type TodoApp_user$data = TodoApp_user;
export type TodoApp_user$key = {
  +$data?: TodoApp_user$data,
  +$fragmentRefs: TodoApp_user$ref,
};
/* equivalent
    ???
*/

First part

I guess this is how you guys pass the idea that each fragment is unique, since the equivalent its done with unique symbol

This is okay, I don't see any problem here, since the FragmentReference of @types/relay-runtime its unkown and Flow version did the same.

Second part

Here it gets interesting. $data and $key are not implemented. Shall we implement this?

You guys could have a reason not to do soo.

enhancement released

Most helpful comment

@alloy, in particular the important type here is the $key type, which is used to declare the type of the fragment reference prop for a component that uses useFragment:

import type {UserComponent_user$key} from 'UserComponent_user.graphql';

const React = require('React');
const {graphql, useFragment} = require('react-relay/hooks');

type Props = {|
  user: UserComponent_user$key,
|};

import type {UserComponent_user$key} from 'UserComponent_user.graphql';

const React = require('React');

const {graphql, useFragment} = require('react-relay/hooks');

type Props = {|
  user: UserComponent_user$key,
|};

function UserComponent(props: Props) {  
  const data = useFragment(
    graphql`
      fragment UserComponent_user on User {
        name
        profile_picture(scale: 2) {
          uri
        }
      }
    `,
    props.user,
  );

  return (... );
}

module.exports = UserComponent;

By passing props.user of type UserComponent_user$key to useFragment, the type of the returned data will automatically inferred to be the $data type (i.e. it will match the shape/type of the fragment).

Internally we use the lint rule @sibelius pointed to to make sure that we're always using the generated type to declare the fragment reference prop.


@renanmav ideally this is what we'd also achieve with the TS types; that is, a way for the data returned by useFragment to be automatically inferred to the correct $data type without having to annotate it.

The way we achieve this in useFragment is with this crazy Flow declaration, where we're extracting the $data type embedded in the $key type, and using that as the return type; but I'm not sure if this is something we can do with typescript? this would also mean updating the TS declaration for useFragment (which I'm not sure how to go about doing)

Specifically what, what this looks like in Flow is:

// NOTE: These declares ensure that the type of the returned data is:
//   - non-nullable if the provided ref type is non-nullable
//   - nullable if the provided ref type is nullable
//   - array of non-nullable if the privoided ref type is an array of
//     non-nullable refs
//   - array of nullable if the privoided ref type is an array of nullable refs

declare function useFragment<TKey: {+$data?: mixed}>(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: TKey,
): $Call<<TFragmentData>({+$data?: TFragmentData}) => TFragmentData, TKey>;

declare function useFragment<TKey: ?{+$data?: mixed}>(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: TKey,
): $Call<<TFragmentData>(?{+$data?: TFragmentData}) => ?TFragmentData, TKey>;

declare function useFragment<TKey: $ReadOnlyArray<{+$data?: mixed}>>(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: TKey,
): $Call<
  <TFragmentData>($ReadOnlyArray<{+$data?: TFragmentData}>) => TFragmentData,
  TKey,
>;

declare function useFragment<TKey: ?$ReadOnlyArray<{+$data?: mixed}>>(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: TKey,
): $Call<
  <TFragmentData>(?$ReadOnlyArray<{+$data?: TFragmentData}>) => ?TFragmentData,
  TKey,
>;

function useFragment(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: ?$ReadOnlyArray<{+$data?: mixed}> | ?{+$data?: mixed},
): mixed { ... }

All 11 comments

Here it gets interesting. $data and $key are not implemented. Shall we implement this?

You guys could have a reason not to do soo.

Literally _zero_ reason, at least on my part… I don’t even know what they are meant for 😬

Can you provide a description/example of what they are for?

This is used by relay experimental hooks

It probably makes sense to implement something like this at some point. But I think we'd want to wait on stabilization of the experimental hooks to see which direction this should take. While I can see some types in that codebase somewhat using this I'm having trouble seeing exactly which problems this is solving.

All I'm hinting at here is that before we know what the TypeScript types for this API will look like - it is a bit difficult knowing which types we should emit here. Nice work on pointing this out though - there's almost certainly some work to be done here in the future :)

(Can somebody point me at a doc/test/example that shows how these are used?)

Here too https://github.com/facebook/relay/pull/2899/files#diff-1bbe2f7402be20148c7d70dc85609161R163

@alloy, in particular the important type here is the $key type, which is used to declare the type of the fragment reference prop for a component that uses useFragment:

import type {UserComponent_user$key} from 'UserComponent_user.graphql';

const React = require('React');
const {graphql, useFragment} = require('react-relay/hooks');

type Props = {|
  user: UserComponent_user$key,
|};

import type {UserComponent_user$key} from 'UserComponent_user.graphql';

const React = require('React');

const {graphql, useFragment} = require('react-relay/hooks');

type Props = {|
  user: UserComponent_user$key,
|};

function UserComponent(props: Props) {  
  const data = useFragment(
    graphql`
      fragment UserComponent_user on User {
        name
        profile_picture(scale: 2) {
          uri
        }
      }
    `,
    props.user,
  );

  return (... );
}

module.exports = UserComponent;

By passing props.user of type UserComponent_user$key to useFragment, the type of the returned data will automatically inferred to be the $data type (i.e. it will match the shape/type of the fragment).

Internally we use the lint rule @sibelius pointed to to make sure that we're always using the generated type to declare the fragment reference prop.


@renanmav ideally this is what we'd also achieve with the TS types; that is, a way for the data returned by useFragment to be automatically inferred to the correct $data type without having to annotate it.

The way we achieve this in useFragment is with this crazy Flow declaration, where we're extracting the $data type embedded in the $key type, and using that as the return type; but I'm not sure if this is something we can do with typescript? this would also mean updating the TS declaration for useFragment (which I'm not sure how to go about doing)

Specifically what, what this looks like in Flow is:

// NOTE: These declares ensure that the type of the returned data is:
//   - non-nullable if the provided ref type is non-nullable
//   - nullable if the provided ref type is nullable
//   - array of non-nullable if the privoided ref type is an array of
//     non-nullable refs
//   - array of nullable if the privoided ref type is an array of nullable refs

declare function useFragment<TKey: {+$data?: mixed}>(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: TKey,
): $Call<<TFragmentData>({+$data?: TFragmentData}) => TFragmentData, TKey>;

declare function useFragment<TKey: ?{+$data?: mixed}>(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: TKey,
): $Call<<TFragmentData>(?{+$data?: TFragmentData}) => ?TFragmentData, TKey>;

declare function useFragment<TKey: $ReadOnlyArray<{+$data?: mixed}>>(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: TKey,
): $Call<
  <TFragmentData>($ReadOnlyArray<{+$data?: TFragmentData}>) => TFragmentData,
  TKey,
>;

declare function useFragment<TKey: ?$ReadOnlyArray<{+$data?: mixed}>>(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: TKey,
): $Call<
  <TFragmentData>(?$ReadOnlyArray<{+$data?: TFragmentData}>) => ?TFragmentData,
  TKey,
>;

function useFragment(
  fragmentInput: GraphQLTaggedNode,
  fragmentRef: ?$ReadOnlyArray<{+$data?: mixed}> | ?{+$data?: mixed},
): mixed { ... }

I've already seen how this looks like in Flow @jstejada

In fact, I've already started the @types of relay-experimental. I can see it here.
_Edit: this is outdated, missing some things like useLazyLoadQuery_

This is how I did the equivalent $Call in TS. I'm not sure if this works out because the $key and $data were missing in relay-compiler-language-typescript. When #149 gets merged, I'll test if ReturnType is the correct fit for useFragment hook.

awesome, thanks @renanmav !


:rocket: Issue was released in v10.1.0 :rocket:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

unludo picture unludo  Â·  9Comments

artola picture artola  Â·  14Comments

wongmjane picture wongmjane  Â·  7Comments

sorenhoyer picture sorenhoyer  Â·  3Comments

cberkom picture cberkom  Â·  15Comments