@coding4tomorrow/c4t-next v0.1.5
Coding 4 Tomorrow Next
TODO
- Fix errors showing up during tests. They're not blocking and don't fail the test but something might not be right.
Installation
yarn add @coding4tomorrow/c4t-next
or
npm install @coding4tomorrow/c4t-next --save
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'
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.
API Documentation
useSetup
This hook configure the whole library.
The main parts are:
- API calls
- Authentication (cookies, tokens)
- Automatic token refresh when expired
- Axios headers configuration (yes we use Axios under the hood).
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, defaults to 'auth.jwt.refresh'
refreshTokenRoute: 'auth.jwt.refresh',
// optional, defaults to 'auth.login'
loginRoute: 'auth.login',
// optional, defaults to 'users.me'
meRoute: 'users.me',
},
auth: {
// optional, defaults to '/login'
loginPageRoute: '/login',
// optional, defaults to 'c4t_token'
tokenCookieName: 'c4t_token',
// optional, defaults to 'c4t_refresh_token'
refreshTokenCookieName: 'c4t_refresh_token',
},
axios: {
// 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 useSWR
or useApi
or useFormApi
etc...
const apiRoutes = ({ get, post, patch, put, del }) => ({
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
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
},
})