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
???
*/
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.
Here it gets interesting. $data and $key are not implemented. Shall we implement this?
You guys could have a reason not to do soo.
Here it gets interesting.
$dataand$keyare 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:
Most helpful comment
@alloy, in particular the important type here is the
$keytype, which is used to declare the type of the fragment reference prop for a component that usesuseFragment:By passing
props.userof typeUserComponent_user$keytouseFragment, the type of the returneddatawill automatically inferred to be the$datatype (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
datareturned byuseFragmentto be automatically inferred to the correct$datatype without having to annotate it.The way we achieve this in
useFragmentis with this crazy Flow declaration, where we're extracting the$datatype 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: