yarn add @coding4tomorrow/c4t-next
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.
You can find some examples in the folder /examples
of the project.
This hook configure the whole library.
The main parts are:
- API calls
- Authentication (cookies, tokens)
- Automatic token refresh when expired
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': '...',
},
},
})
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
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>
}
useApiQuery<T>(path, queryParams?, options?): useApiQueryResult<T>
-
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.
useApiQuery
returns an object containing the following properties:
-
data
:T | undefined | null
- The data returned from the API, will beundefined
. -
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.
For endpoints requiring route parameters:
const UserDetails = ({ userId }) => {
const { data, isLoading, error } = useApiQuery<UserType>([
'user.details',
{ userId },
])
// Render logic
}
For endpoints with query parameters:
const SearchResults = ({ query }) => {
const { data, isLoading } = useApiQuery<SearchResultsType>('search', {
query,
})
// Render logic
}
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
}
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
}
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
}
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.
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>
)
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
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>
)
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]
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
},
})