Code Monkey home page Code Monkey logo

urql-wrapper's Introduction

urql-wrapper

A wrapper for urql that enables more type-safe and semantic data handlings.

Getting started

TBD

Problems of default urql useQuery implementation

Code readability

The default urql useQuery implementation returns a complex object that can be difficult to read and understand.

// urql 4.0.0
type UseQueryResponse<
  Data = any,
  Variables extends AnyVariables = AnyVariables
> = [UseQueryState<Data, Variables>, UseQueryExecute];

interface UseQueryState<
  Data = any,
  Variables extends AnyVariables = AnyVariables
> {
  fetching: boolean;
  stale: boolean;
  data?: Data;
  error?: CombinedError;
  extensions?: Record<string, any>;
  operation?: Operation<Data, Variables>;
}

For instance, it's unclear what the status is when both error and data have a value, or whether such a state can exist.

The primary concerns when fetching data are whether the data is currently being fetched, whether the data was successfully fetched, or whether it failed and why.

If it is the initial data fetching, it should be obvious that the data is not yet available. When fetching is successful, the data should be available. Using urql-wrapper's useQuery implementation, these concerns are addressed in a more readable and type-safe way. The implementation returns a status string union that can be used to check for three possible states: fetching, success, and error. This makes it easier to understand what state the data fetching process is in and how to handle each state as demonstrated below.

import {useQuery} from 'urql-wrapper';

function UserListContainer() {
  const query = useQuery({
    query: gql`
      query UserList {
        users {
          id
          name
        }
      }
    `,
  });

  switch (query.status) {
    case 'paused':
    case 'fetching':
      // you can't access to query.data as fetching is not yet finished.
      return <UserList isLoading />;
    case 'success': {
      // you can now access to query.data.
      return <UserList users={query.data.users} />;
    }
    case 'error':
      return <ErrorText />;
  }
}

With the default useQuery function provided by urql, the code for fetching data would look like this:

import {useQuery} from 'urql';

function UserListContainer() {
  const [{ data, fetching, error }] = useQuery({
    query: gql`
      query UserList {
        users {
          id
          name
        }
      }
    `,
  });

  if (fetching) {
    return <UserList isLoading />
  }

  if (error) {
    return <ErrorText />
  }

  if (!data) {
    // If the data is optional, it's unclear what should be done in this case
  }

  return <UserList users={data.users}>
}

Here, the code fetches data and checks for three possible states: if data is being fetched, if there is an error, and if data is missing. However, if the data is optional, it is not clear what should be done in that case.

Data handling

It is common to obtain entities in GraphQL using node(id:). Suppose we have two entities, Company and User, which can be accessed via node(id:).

query SomeQuery {
  company: node(id: $companyId) {
    ... on Company {
      name
    }
  }

  user: node(id: $userId) {
    ... on User {
      age
    }
  }
}

If you want to obtain the Company with ID 1, you can write the following code:

function CompanyContainer() {
    const [{ data, fetching, error }] = useQuery({
        query: gql`
            query Company($companyId: ID!) {
                company: node(id: $companyId) {
                    __typename
                    ... on Company {
                        name
                    }
                }
            }
        `,
        variables: {
            companyId: "1"
        }
    })

    if (fetching) {
        return <Company isLoading />
    }

    if (error) {
        return <ErrorText />
    }

    if (!data || data.__typename !== "Company") {
        // This code is basically unreachable, but we want to handle it as an error
        return <ErrorText />
    }

    // At this point, data is resolved to the Company object
    return <Company name={data.name}>
}

With urql-wrapper, you can transform and shape the raw GraphQL response using a selector as follows:

function CompanyContainer() {
  const query = useQuery({
    query: gql`
      query Company($companyId: ID!) {
        company: node(id: $companyId) {
          __typename
          ... on Company {
            name
          }
        }
      }
    `,
    variables: {
      companyId: '1',
    },
    selector: data => {
      // Throw an error, which can be accessed via query.error
      if (!data || data.__typename !== 'Company') {
        throw new Error();
      }

      return data.company;
    },
  });

  switch (query.status) {
    case 'paused':
    case 'fetcing':
      return <Company isLoading />;
    case 'success':
      return <Company name={query.data.company.name} />;
    case 'error':
      return <ErrorText />;
  }
}

This code is more straightforward, isn't it?

Sharing fetched data across multiple components.

It is now common to use the Container Presentational design pattern to separate concerns and write multiple container components that share the same query.

With urql's default implementation, you can easily achieve this by using useQuery with the cache-and-network request policy, which caches the data and returns it for the second query.

If you want to avoid caching the data, you need to specify the requestPolicy with network-only. In this case, you can use React Context to share data across multiple components. To avoid writing almost the same code multiple times, you can use the context factory queryProviderFactory.

Here's an example code:

export const [AboutPageQueryProvider, useAboutPageQuery] = queryProviderFactory(
  {
    query: gql`
      query CompanyListPage {
        company {
          name
          users {
            id
            name
            age
          }
        }
      }
    `,
    selector: data => {
      // You can write selector here
    },
    requestPolicy: 'network-only',
  }
);

function AboutPage() {
  return (
    <AboutPageQueryProvider variables={'pass some data as you want'}>
      <CompanyDetailContainer />
      <UserListContainer />
    </AboutPageQueryProvider>
  );
}

urql-wrapper's People

Contributors

mositori avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.