Ways to enable Urql (typescript) to accept Vue reactive variables for queries generated using graphql-codegen

I'm currently developing a Vue project that involves using urql and graphql-codegen.

When utilizing useQuery() in urql, it allows for the use of Vue reactive variables to make the query reactive and update accordingly when the variables change.

The issue arises with graphql-codegen generating types that only accept scalars (e.g., string) for the variables parameter. This causes TypeScript errors when trying to use different types such as Ref.

This is an excerpt from my codegen.ts file:

import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: 'http://localhost:5001/graphql',
  documents: ['src/**/*.vue', 'src/**/*.ts'],
  ignoreNoDocuments: true,
  generates: {
    './src/gql/': {
      preset: 'client',
      config: {
        useTypeImports: true,
        scalars: {
          CustomDate: 'string',
          ObjectID: 'string',
        },
            },
      plugins: [],
    },
  },
};

export default config;

One of the generated variable types looks like this:

export type Scalars = {
  String: string;
  ObjectID: string;
};

export type GetItemQueryVariables = Exact<{
  _id: Scalars['ObjectID'];
}>;

A potential scenario might be:

const id = ref('123');

const queryResult = useQuery({
  query: queryGQL,
  variables: { _id: id },
});

Attempting to assign a Ref<string> to a string results in a type mismatch error.

Is there a way to configure graphql-codegen to resolve this issue? Alternatively, are there methods to automatically modify the generated TypeScript definitions to address this problem?

Answer №1

Utilize the reactive function to transform reference types into properties:

let r = ref(123)    // Ref<number>
let a = { r }       // { r: Ref<number> }
let b = reactive(a) // { r: number }

For instance:

const { data } = useQuery({
  query,
  variables: reactive({
    orderBy: ArticlesOrderBy.ViewedAt,
    order: Order.Desc,
    first: 2,
    after  // Ref<string>
  })
})

Answer №2

I encountered a similar issue while using @graphql-codegen and urql for the first time. If there's a better solution out there, please share it with me! 😁

Setup

I referred to the official documentation provided by the codegen website.

The Challenge

URQL

With urql, we can pass ref objects as variables. This feature is particularly useful when working with conditional queries. You have the ability to pause the query and pass in a ref variable.

const exampleQuery = useQuery({
  query: GqlQueryString,
  variables: {
    someVariable, // <- reference
  },
  pause: isQueryPaused,
});

Code Generation

When code generation creates types, it treats variables as simple types. Consequently, you might encounter a type error in the code due to variables not being of type Ref<...>.

The Resolution

After doing some research online, I couldn't find an immediate solution. The documentation for codegen is comprehensive but lacks examples for this specific scenario.

We need to specify the scalars property to accept both Ref and type definitions.

config: {
  useTypeImports: true,
  scalars: {
    String: {
      input: "string | Ref<string | undefined>",
      output: "string",
    },
    Boolean: {
      input: "boolean | Ref<boolean | undefined>",
      output: "boolean",
    },
    Int: {
      input: "number | Ref<number | undefined>",
      output: "number",
    },
    Float: {
      input: "number | Ref<number | undefined>",
      output: "number",
    },
  },
},

However, simply setting that won't suffice as the generated file doesn't import Ref.

To address this, I had to create a custom plugin:

// codegen-import-plugin.cjs
module.exports = {
  plugin(schema, documents, config) {
    return {
      prepend: config.imports,
      content: "",
    };
  },
  validate(schema, documents, config) {
    if (!Array.isArray(config.imports) || config.imports.length === 0) {
      throw new Error("codegen-import-plugin requires imports to be set");
    }
  },
};

Add the plugin to the configuration:

plugins: [
  {
    "codegen-import-plugin.cjs": {
      imports: ["import type { Ref } from 'vue';"],
    },
  },
],

Subsequently, the generated file will include the necessary import statements:

import type { Ref } from 'vue';
// Other generated type declarations...

Update #1

Upon discussion with Google's Bard, I discovered that defining custom scalars is an option. There's a documentation page outlining this process. However, I find this approach cumbersome as it entails adding new scalars to each query. Additionally, Refs are more closely associated with urql and the useQuery function types rather than the queries themselves.

Update #2

It's like magic! 😁🪄 After tidying up my dependencies, I observed that useQuery now functions as intended. However, useMutation still doesn't utilize the MaybeRef$1 type from urql.

# package.json
...
Dependencies section & Dev Dependencies details...
# codegen.ts
// Codegen configuration script...

Answer №3

I have come across 2 issues in the generated file.

  1. Incorrect -> import * as Urql from 'urql'; Correct ->
    import * as Urql from '@urql/vue';
  2. There are problems with types in the queries after generation.
export function useFindUserByUrlQuery(options: Omit<Urql.UseQueryArgs<FindUserByUrlQueryVariables>, 'query'>) {
     return Urql.useQuery<FindUserByUrlQuery, FindUserByUrlQueryVariables>({ query: FindUserByUrlDocument, ...options });
} 

This will result in a query-options type error.

The correct implementation should be:

export function useFindUserByUrlQuery(options: Omit<Urql.UseQueryArgs<FindUserByUrlQueryVariables>, 'query'> & { variables: { url: string } }) {
      return Urql.useQuery<FindUserByUrlQuery, FindUserByUrlQueryVariables>({ query: FindUserByUrlDocument, ...options });
    }

If there are any discrepancies, kindly leave a comment.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to trigger a re-render of a Vue component after a change in Vuex

I'm interested in developing a custom component to modify the basemap of a leaflet map without relying on L.control.layers. To achieve this, I created a sidebar component that offers options to switch between different basemaps. In my implementation, ...

Using a function as a prop in Vue js to retrieve data from an API

I am facing an issue with a component that I want to decouple from the data fetching implementation. My goal is to be able to pass a data fetching callback as a prop. The reason for this is so that I can easily mock the data fetching process in storybook. ...

What is the best way to pass an array of markers through props?

Hello, I need assistance on how to pass data through props for displaying points. I have used Map in this scenario. This is my code: In the components maps section: const marker = new H.map.Marker({lat: this.coords lng: this.coords]); map.a ...

typescriptCreating a custom useFetch hook with TypeScript and Axios

I have a query regarding the utilization of the useFetch hook with TypeScript and Axios. I came across an example of the useFetch hook in JavaScript, but I need help adapting it for TypeScript. The JavaScript implementation only handles response and error ...

What is the best approach for assigning a value to the action URL in el-upload?

<el-upload class="upload" action="http://127.0.0.1:5000/upload/email" :file-list="fileList" > Utilizing the Element UI library, I am attempting to pass an email value to the 'action' URL in the format of base_ ...

Collaborative service involves objects passing through reference

I am encountering an issue with a shared service in angular. Upon application startup, the init function is triggered, fetching and returning data that is vital across the entire application. Components have the ability to inject this service and retrieve ...

The name 'Map' cannot be located. Is it necessary to alter your target library?

After running the command tsc app.ts, an error occurs showing: Error TS2583: 'Map' is not recognized. Should the target library be changed? Consider updating the lib compiler option to es2015 or newer. I want the code to compile without any issu ...

Angular 5: Issue with Module Resolution: Unable to locate '@angular/forms/src/validators'

Struggling to develop a unique validator using a directive, but encountering the following error: ERROR in ./src/app/CustomValidators/white-space-validator.directive.ts Module not found: Error: Can't resolve '@angular/forms/src/validators' ...

What is the correct way to construct this query?

I've been attempting to run this query with Sequelize but keep encountering an error Query LineItem.findAll( { attributes: [ "orderId", [fn("sum", col("quantity")), &qu ...

When passing a prop to v-html, it may not render properly in Vue when dealing with SVG elements

I have a string that contains a sequence of elements as a prop, and I'm trying to render it using :v-html="prop": <template> <svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" viewBox="0 ...

Tips on downloading an image using the URL in nestjs

I'm trying to retrieve a link and save the associated image in the static folder, but I seem to be encountering some issues with my code. Here's what I have so far: @Injectable() export class FilesService { createFileFromUrl(url: string) { t ...

Is it more beneficial to utilize multiple Vue apps or just one app per page?

Our website is primarily built using Razor/MVC, but we have been incorporating Vue gradually over the past few years. Currently, we have multiple Vue apps integrated separately into different sections of our site - such as modals, forms, and widgets in the ...

Issue with typing error in datetime.d.ts file that is missing

I initially installed the typings for luxon using npm install --save-dev @types/luxon. However, upon further evaluation, I realized that it was unnecessary and decided to remove it manually: deleted the folder node_modules/@types/luxon removed entries in ...

How to Reload the Active Tab in Angular 5?

In my project, I have noticed that a listener in one of the tabs causes the entire tab to refresh, resulting in it displaying information from the first tab until I switch to another tab and then go back. 1) Is there a way to refresh only the current tab? ...

Is it possible to implement the splice() method within a forEach loop in Vue for mutation

Hey there! I'm looking for a more efficient way to replace objects that match a specific index with my response data. Currently, I'm attempting to use the splice() method within a forEach() loop in Vue.js. However, it seems to only remove the obj ...

What is the best way to efficiently import multiple variables from a separate file in Vue.JS?

When working with a Vue.JS application and implementing the Vuex Store, I encountered an issue in my state.js file where I needed to import configurations from another custom file, specifically config.js. Upon running it, I received the following warning ...

In order to streamline our production environment, I must deactivate Vue.js devtools, while ensuring they remain active during development mode

Is there a way to prevent users from inspecting the app using devtools in production mode while keeping it enabled for local/development mode? I've tried setting up .env and .env.production files with the VUE_APP_ROOT_API variable but it doesn't ...

I will evaluate two arrays of objects based on two distinct keys and then create a nested object that includes both parent and child elements

I'm currently facing an issue with comparing 2 arrays of objects and I couldn't find a suitable method in the lodash documentation. The challenge lies in comparing objects using different keys. private parentArray: {}[] = [ { Id: 1, Name: &ap ...

Potential uncertainty in Angular FormControl email validation due to potential null Object

Whenever I run the command ng s --aot, I encounter this message: Object is possibly 'null'. I've been trying various solutions all morning to resolve it, but without success. The issue seems to be related to objects like email.valid, dirty, ...

Developing maintenance logic in Angular to control subsequent API requests

In our Angular 9 application, we have various components, some of which have parent-child relationships while others are independent. We begin by making an initial API call that returns a true or false flag value. Depending on this value, we decide whether ...