React is a great tool for building amazing web and mobile experiences. Today, we’re excited to announce a set of rich, strongly typed React hooks for working with your Gadget applications.
In a React application however, we need a data fetching pattern that provides a better user experience by displaying a spinner when the data is loading, or displaying errors when a network call fails. Using the Gadget generated client, our code becomes:
For example, we can make an explicit selection when querying the posts list, and we’ll get an error if we try to access a property we didn’t select:
And, with or without an explicit selection, we get editor autocomplete when accessing fields of the selected records:
The <inline-code>@gadgetinc/react<inline-code> package includes hooks for everything you can do in with your app's API, like <inline-code>useFindOne<inline-code>, <inline-code>useAction<inline-code>, <inline-code>useBulkAction<inline-code>, and more.
The Gadget React bindings use the same featureful GraphQL API that Gadget generates for each application. Both for React and the API client, we use the popular and battle-tested <inline-code>urql<inline-code> GraphQL client. We’ve been using <inline-code>urql<inline-code> in production for many years, and appreciate its robustness and extensibility. It is performant, well maintained, has a small code footprint, and is designed with extensibility in mind. We also really like the default caching mode for <inline-code>urql<inline-code> (document caching), which works great out of the box and requires no extra developer work to set up.
Making <inline-code>@gadgetinc/react<inline-code> feel great to use means having extensive TypeScript support, and like the base API client, this is tricky business to deliver out of the box. The schema for your application is completely controlled by you, and we'd rather your API client really feel like yours as well, which means its types should be strict and specific to your app. Because each call to a <inline-code>@gadgetinc/react<inline-code> hook can pass a different or dynamic <inline-code>select<inline-code> parameter, the return type is generic over the selection. For both <inline-code>@gadgetinc/react<inline-code> and the base API client, Gadget isn't generating simple static types based on your schema, it's generating an entire representation of the schema for the higher-kinded types to map over for each concrete call.
Because all this type-time stuff is just types, we're able to "export" the base generated types at type-time from the base API client, and "import" them in the react package. The trick is not making you write actual <inline-code>import<inline-code> statements for these types, and instead allowing them to be inferred from a simple <inline-code>useFindOne(api.widget)<inline-code> call or similar. If we didn't do this, you'd have to write overly verbose code like <inline-code>useFindOne<Widget>(api.widget)<inline-code> or worse.
So, in order to make the types inferable, we had to put them in a spot on the passed in argument to the function, which is that property of the base API client, <inline-code>api.widget<inline-code>. The types have to be reachable with TypeScript's type access operator <inline-code>SomeType["someProperty"]<inline-code>, and so, we did just that! <inline-code>@gadgetinc/react<inline-code> works in collaboration with the base API client by accessing type-time only properties exported on each model's property on the base API client. These type-time only exports don't add any bundle size to the compiled JS, but provide quick and easy inference without extra imports or parameters.
These virtual properties were a strange pattern we hadn’t seen before, but ended up working great, and are a testament to the power of TypeScript’s flexibility and out of band approach.