@coding4tomorrow/c4t-next
TypeScript icon, indicating that this package has built-in type declarations

0.1.6 • Public • Published

Coding 4 Tomorrow Next

Installation

yarn add @coding4tomorrow/c4t-next

Getting started

To use the library you'll have to call the useSetup hook first thing when your React app is loaded.

_app.js for NextJS

// complete configuration
import { useSetup } from '@coding4tomorrow/c4t-next'
import { apiRoutes } from './api-routes'

const App = ({ Component, pageProps }) => {
  useSetup({
    api: {
      baseURL: 'http://localhost:5000',
      routes: apiRoutes,
    },
  })

  return <Component {...pageProps} />
}

export default App

You can check all the available options for useSetup below.

Examples

You can find some examples in the folder /examples of the project.

Hooks Documentation

useSetup

This hook configure the whole library.

The main parts are:

  • API calls
  • Authentication (cookies, tokens)
  • Automatic token refresh when expired

Hook options

import apiRoutes from './apiRoutes'

useSetup({
  api: {
    // required, this is the base URL of the API
    baseURL: 'http://localhost:4000',
    // required if you want to use the API
    routes: apiRoutes,
    // optional
    headers: {
      // custom headers which will be appended on each request
      'X-API-Key': '...',
    },
  },
})

apiRoutes

This is where all the API routes are defined.

You can then use them in combinaison with useApiQuery or useApiRequest or useFormApi etc...

import { type RouteTypes } from 'c4t-react'

const apiRoutes = ({ get, post, patch, put, del }: RouteTypes) => ({
  users: {
    me: get('/v1/users/me'),
  },
  books: {
    getAll: get('/v1/books'),
    delete: del('/v1/books/:id'),
    update: patch('/v1/books/:id'),
    override: put('/v1/books/:id'),
    create: post('/v1/books'),
  },
  auth: {
    login: post('/v1/auth/login'),
    refreshToken: post('/v1/auth/refresh-token'),
  },
})

export default apiRoutes

useApiQuery

This hook is an implementation of swr to work with our api system, so we keep all the benefits of using swr while having an easy api integration system.

It's useful when doing a simple request that has to be executed right away.

// api-routes.ts
import { type RouteTypes } from 'c4t-react'

const apiRoutes = ({ post }: RouteTypes) => ({
  books: {
    get: get('/books/:id'),
  },
})

// MyComponent.tsx
import { useApiQuery } from 'c4t-react'

const MyComponent = () => {
  const { data, isLoading, error } = useApiQuery<MyDataType>('myApiEndpoint')

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return <div>{data && <DisplayComponent data={data} />}</div>
}

API Refrence

useApiQuery<T>(path, queryParams?, options?): useApiQueryResult<T>

Parameters

  • path: string | [string, RouteParamsType] | null - The API endpoint or a tuple containing the endpoint and route parameters.
  • queryParams: QueryParams | null - (Optional) An object containing query parameters.
  • options: object - (Optional) SWR configuration options.

Return Value

useApiQuery returns an object containing the following properties:

  • data: T | undefined | null - The data returned from the API, will be undefined.
  • isLoading: boolean - Indicates if the request is in the loading state.
  • isValidating: boolean - Indicates if the request is being revalidated.
  • error: Error | ApiError | undefined | null - Any error that occurred during the request.
  • mutate: KeyedMutator<any> - A function to manually trigger a revalidation.

Advanced Usage

Handling Route Parameters

For endpoints requiring route parameters:

const UserDetails = ({ userId }) => {
  const { data, isLoading, error } = useApiQuery<UserType>([
    'user.details',
    { userId },
  ])

  // Render logic
}

Using Query Parameters

For endpoints with query parameters:

const SearchResults = ({ query }) => {
  const { data, isLoading } = useApiQuery<SearchResultsType>('search', {
    query,
  })

  // Render logic
}

Custom SWR Options

useApiQuery leverages SWR under the hood, allowing you to pass custom SWR options:

const PollingComponent = () => {
  const { data } = useApiQuery<PollDataType>('poll', null, {
    refreshInterval: 5000, // Polls every 5 seconds
  })

  // Render logic
}

Error Handling

The hook provides an error object which can be used to render error messages or perform actions based on the error:

const MyComponent = () => {
  const { error } = useApiQuery<MyDataType>('endpoint')

  if (error) {
    console.error(error)
    return <div>An error occurred: {error.message}</div>
  }

  // Render logic
}

Mutating Data

The mutate function allows you to update the local data programmatically, which is useful for optimistic UI updates:

const UpdateComponent = () => {
  const { mutate } = useApiQuery<MyDataType>('endpoint')

  const updateData = async (newData) => {
    await mutate(newData, false) // Update the local data without re-fetching
  }

  // Render logic
}

TypeScript Support

useApiQuery is fully compatible with TypeScript, offering type safety for your API data and responses. You can define the expected data type as a generic parameter to the hook:

interface UserData {
  id: number
  name: string
  email: string
}

const UserComponent = ({ userId }) => {
  const { data, isLoading } = useApiQuery<UserData>([
    'user.details',
    { userId },
  ])

  if (isLoading) return <div>Loading user data...</div>
  if (!data) return <div>No user data found.</div>

  return (
    <div>
      <h1>{data.name}</h1>
      <p>Email: {data.email}</p>
    </div>
  )
}

This ensures that you can work with your API data in a type-safe manner, leveraging TypeScript's capabilities to catch errors at compile time.

useSWR

This hook is an implementation of swr (https://swr.vercel.app/docs/getting-started) to work with our api system, so we keep all the benefits of using swr while having an easy api integration system.

It's useful when doing a simple request that has to be executed right away.

// apiRoutes.js
const apiRoutes = ({ post }) => ({
  books: {
    get: get('/books/:id'),
  },
})

// useSWRExample.js
import { useSWR } from '@coding4tomorrow/c4t-next'

//...
const { data: book, isLoading } = useSWR('books.get', { id: 1 })

if (isLoading) {
  return <p>Loading...</p>
}

return (
  <div>
    <h2>{book.title}</h2>
    <h3>{book.author}</h3>
    <p>{book.description}</p>
  </div>
)

Hook options

const {
  data, // the loaded data
  error, // the error if there is
  isLoading, // used to
  mutate, // used to mutate the cached version of the resource
} = useSWR(apiRoute, params, options)

data

data is null until loaded.

If data had results before a second call, it'll keep its current value until the new value is loaded.


error

error holds the error returned from the call, if there is.

data will equal null if there's an error.


isLoading

While isLoading is true, data is null.


apiRoute

This is the route path to our API call, for example books.getAll


params

This is the params for the apiRoute, for example if your URL is: /v1/books/:id you'll have to pass { id: 1 } to transform it to /v1/books/1.


options

This is the options of swr.

More information at https://swr.vercel.app/docs/options#options


useApi

Sometimes you need to defer the api request, for example to send information after a click.

This is where useApi comes in play.

// apiRoutes.js
const apiRoutes = ({ post }) => ({
  books: {
    like: post('/books/:id/like'),
  },
})

// useApiExample.js
import { useApi } from '@coding4tomorrow/c4t-next'

//...
const { request, loading } = useApi('books.like', { id: 1 })

return (
  <div>
    <a
      onClick={() => {
        const [err, response] = request({
          fieldName: 'some field infos',
        })

        if (err) {
          // display error somewhere
          return
        }

        if (response.success) {
          // show feedback to the user
        }
      }}
    >
      Like
    </a>
  </div>
)

useFormApi

This hook is useful when working with Ant Design forms.

It's taking care of:

  • API call on submission
  • Automatic error handling based on fields name

Simple example:

import { useFormApi } from '@coding4tomorrow/c4t-next'
import { useForm, Form, Input, Button } from 'antd'

const [form] = useForm()

const { ErrorComponent, onSubmit, isLoading } = useFormApi({
  api: 'auth.login', // path as found in apiRoutes
  form, // instance to ant design form
})

return (
  <Form form={form} onSubmit={onSubmit}>
    <ErrorComponent />
    <Form.Item name="username">
      <Input />
    </Form.Item>
    <Form.Item name="password">
      <Input.Password />
    </Form.Item>
    <Button type="primary" htmlType="submit" loading={isLoading}>
      Sign in
    </Button>
  </Form>
)

In this example, when the user submit the form, it'll send to the API a post request with the following fields:

{
  username: '...',
  password: '...',
}

If the API returns any error in the username or in the password, they will be displayed under the corresponding field like Ant Design would do. If it's a global error (not related to a field), it'll use the <ErrorComponent /> from the library.

You can learn more about errors handling here [link]

Hook options

useFormApi({
  api,
  form,
  beforeApiCall = values => values,
  afterApiCall = response => response,
  beforeErrorsHandling = errors => errors,
})

api

This parameter can be a string, an object or a function depending on the use case.

A string is used for simple routes

// apiRoutes.js
const apiRoutes = ({ post }) => ({
  auth: {
    login: post('/auth/login'),
  },
})

// useFormApiExample.js
const { onSubmit } = useFormApi({
  api: 'auth.login', // will call POST /auth/login
})

An object is used for routes with params

// apiRoutes.js
const apiRoutes = ({ patch }) => ({
  users: {
    patch: patch('/users/:name'),
  },
})

// useFormApiExample.js
const { onSubmit } = useFormApi({
  api: {
    // will call PATCH /users/janick
    path: 'users.patch',
    params: { name: 'janick' },
  },
})

A function is used when you need to resolve the path just before the request

const apiRoutes = ({ patch }) => ({
  users: {
    patch: patch('/users/:name'),
  },
})

...

const [form] = useForm()

// here form.getFieldValue('name') is evaluated at
// the creation of the hook, returning an undefined value
const {
  onSubmit,
} = useFormApi({
  api: { // no function
    path: 'users.patch', // will call PATCH /users/undefined
    params: { name: form.getFieldValue('name') }, // form.getFieldValue('name') = undefined
  },
})

// --------------------------------

const {
  onSubmit,
} = useFormApi({
  api: () => ({ // note the function here
    path: 'users.patch', // will call PATCH /users/janick if form.getFieldValue('name') = 'janick'
    params: { name: form.getFieldValue('name') },
  }),
})

form

This parameter is the form variable generated by useForm from Ant Design.

import { useForm } from 'antd'

const [form] = useForm()

const { onSubmit } = useFormApi({
  form,
})

It is used to manage fields related errors.


beforeApiCall

This parameter is used to transform the data before it is sent to the API.

const { onSubmit } = useFormApi({
  beforeApiCall: (values) => {
    // some processing

    return {
      ...values,
      name: 'Overriden name',
    }
  },
})

afterApiCall

This parameter is usually used to add an action after the API call is successful.

const router = useRouter()

const { onSubmit } = useFormApi({
  afterApiCall: (response) => {
    // redirects to user's profile after editing
    router.push(`/users/${response.id}`)
  },
})

beforeErrorsHandling

This parameter is called just before dispatching the errors between the form and the <ErrorComponent />, this is the best time to do any needed transformation.

const { onSubmit } = useFormApi({
  beforeErrorsHandling: (errors) => {
    // do something with errors
  },
})

Readme

Keywords

none

Package Sidebar

Install

npm i @coding4tomorrow/c4t-next

Weekly Downloads

18

Version

0.1.6

License

MIT

Unpacked Size

505 kB

Total Files

55

Last publish

Collaborators

  • c4t-janick