-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8c3d145
Showing
20 changed files
with
32,876 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
name: Publish package to GitHub Packages | ||
on: | ||
release: | ||
types: [created] | ||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: read | ||
packages: write | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-node@v3 | ||
with: | ||
node-version: '18.x' | ||
registry-url: 'https://npm.pkg.github.com' | ||
scope: '@alnvdl' | ||
- run: npm ci | ||
- run: npm test | ||
- run: npm publish | ||
env: | ||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: Test | ||
|
||
on: | ||
push: | ||
branches: [ main ] | ||
pull_request: | ||
branches: [ main ] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
node-version: [18.x] | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Use Node.js ${{ matrix.node-version }} | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version: ${{ matrix.node-version }} | ||
cache: 'npm' | ||
- run: npm ci | ||
- run: npm run build --if-present | ||
- run: npm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
|
||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 Allan Vidal | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
# @alnvdl/request | ||
|
||
@alnvdl/request is an opinionated library with a fetch API wrapper and React | ||
hook for making HTTP requests automatically or on demand. | ||
|
||
It was built with a few goals in mind: | ||
- its use should result in extremely simple component code for basic GET and | ||
POST requests; | ||
- it should be simple to understand and use by people who just learned React | ||
hooks, with helpful debugging and error messages; | ||
- no external dependencies apart from the core React hooks; | ||
|
||
It also has an important anti-goal: it's not meant to be used for high-traffic, | ||
public-facing production websites. It was originaly built for GET-oriented, | ||
low-traffic websites and intranet tools, where caching and deduplication are | ||
not big concerns. For anything more serious, the React ecosystem is full of | ||
great alternatives like `useQuery` or Next.js. | ||
|
||
## Getting started | ||
@alnvdl/request is not available in the public NPM registry, but you can | ||
install it directly from GitHub: | ||
``` | ||
npm install github:alnvdl/request#semver:^1.0.0 | ||
``` | ||
|
||
If you prefer an npm registry, you can configure your environment to use the | ||
GitHub Packages[^1] registry and then install the | ||
[@alnvdl/request package](https://github.com/alnvdl/request/pkgs/npm/request). | ||
|
||
After installing, import it in your code: | ||
```js | ||
import { request, useRequest } from '@alnvdl/request'; | ||
``` | ||
|
||
[Example code is available](./src/index.tsx). The example code is in | ||
TypeScript to make it more complete, but this library is a pure JS library | ||
with optional TypeScript annotations. | ||
|
||
This repository is also a [CRA](https://create-react-app.dev/) project, so you | ||
can run `npm run start` to see a live demo, or `npm run test` to run the tests. | ||
|
||
## Reference | ||
This module exports two functions: | ||
|
||
### `useRequest` | ||
```js | ||
function useRequest(method, url, { | ||
headers, | ||
body, | ||
parseAs, | ||
idGenerator, | ||
adapter, | ||
lazy | ||
} = {}) | ||
``` | ||
|
||
`useRequest` should be **used as a React hook directly in component code** to | ||
make an HTTP request using the fetch API[^2] with the given `method` and `url`. | ||
The final `opts` argument is an entirely optional object that may contain: | ||
|
||
- a `headers` object with custom headers for the request; | ||
- a `body` for the request; | ||
- the `parseAs` string indicating the type of body expected in the response, | ||
which can be: `"arrayBuffer"`, `"blob"`, `"formData"`, `"json"` or `"text"`. | ||
If not given, the `Content-Type` header of the response will be used to | ||
determine it, and if that is not possible, `"text"` will be assumed. | ||
- an `idGenerator` function that returns a new request ID. If not present, a | ||
default random-string generator will be used. | ||
- an `adapter` function with signature | ||
`({reqID, status, data, error}) => {status, data, error}` | ||
This function can be used to override any regular or error condition returned | ||
in a response, and it's especially useful in case you want to treat certain | ||
error conditions differently or change state in your component when a | ||
response is received. For example, if a 404 error is not to be considered a | ||
problem for a certain endpoint, this function may set `error` to `null`. | ||
- an `lazy` boolean flag that indicates whether the fetch should only run when | ||
the returned `fetch` function is called. If you need a component to be able | ||
to make a request on mount and then later make the request on demand, use an | ||
effect that calls the returned `fetch` function, with a dependency on it (it | ||
is guaranteed to never change). However, in most cases you should probably | ||
use `request` instead. See [request vs useRequest](#request-vs-userequest). | ||
Returns an object: | ||
```js | ||
{reqID, status, data, error, loading, fetch, lastFetch} | ||
``` | ||
- `reqID` is a request identifier generated when the request was started; | ||
- `status` is the HTTP status code if the request concluded successfully, or | ||
REQUEST_FAILED (-2) if the request failed unexpectedly with a non-HTTP error, | ||
REQUEST_PENDING (-1) if the request wasn't made yet or | ||
REQUEST_ONGOING (0) is in progress; | ||
- `data` is the most recent response data or `null`; | ||
- `error` is a truthy value in case there are errors in making the request or | ||
in the response, and in case of regular HTTP error status codes, its content | ||
will be the same as data; | ||
- `loading` indicates whether the request is still under way; | ||
- `fetch` is a function that can be used to trigger a fetch at any time if | ||
`lazy` is set to true; | ||
- `lastFetch` is the timestamp for the last time the `fetch` function was | ||
called, or `null` if it was never called. | ||
|
||
### `request` | ||
```js | ||
function request(method, url, { | ||
headers, | ||
body, | ||
parseAs, | ||
idGenerator, | ||
callback | ||
} = {}) | ||
``` | ||
|
||
`request` **should be used in event handlers** to make an HTTP request using | ||
the fetch API[^2] with the given `method` and `url`. | ||
|
||
`headers`, `body`, `parseAs` and `idGenerator` are exactly as described in | ||
`useRequest`. `callback` is a function that will get called with: | ||
```js | ||
{reqID, status, data, error, loading} | ||
``` | ||
|
||
These values are exactly as documented as the return value of `useRequest`. | ||
|
||
A call to `request` returns no values, and instead only the `callback` is | ||
invoked whenever there's an update. | ||
|
||
The typical usage pattern is as follows: | ||
```js | ||
let [req, setReq] = useState(requestInitialState()); | ||
|
||
// And then within the event handler: | ||
request("POST", "...", {callback: setReq}); | ||
``` | ||
|
||
But this is only a suggestion: a component can freely define how the `callback` | ||
will affect its own internal state. | ||
|
||
### request vs. useRequest | ||
**TL;DR**: use `useRequest` directly in component code. Use `request` in event | ||
handlers. If after reviewing the React docs you feel like `useRequest` with | ||
`lazy` is a better approach, use that instead. | ||
|
||
Now the long explanation: `request` is an `async` function that wraps the | ||
`fetch` function provided by browsers. It is meant to be used when the hook is | ||
not the best option, such as when making requests after the user takes some | ||
action on the page (for example, in event handlers when a button is clicked to | ||
load some information, or when a form gets submitted). | ||
|
||
`useRequest` should be used when the page is loaded, as part of its natural | ||
flow. It is built on top of `useEffect`. | ||
|
||
However, `useRequest` provides an escape hatch: the `lazy` flag. It instructs | ||
`useRequest` to not make the request immediately, but rather to wait for a | ||
call to the `fetch` function it returns. The request is then repeated every | ||
time `fetch` is called. Within `useRequest`, `lazy` causes the `useEffect` hook | ||
to have an additional dependency: the timestamp in which a new request was | ||
ordered. Whenever that timestamp changes by calling `fetch` and a request is | ||
not already ongoing, a new request gets initiated. | ||
|
||
This means `lazy`+`fetch` can be used to make `useRequest` behave almost | ||
exactly like `request`, but not quite: it may look like it simplifies component | ||
code at first, but it complicates the logic underneath. In general, using | ||
`useEffect` to react to user-initiated events is discouraged (even if | ||
indirectly as in `useRequest`). The reasons for this are discussed extensively | ||
in the React docs[^3]. | ||
|
||
However, there might be cases when it makes sense to allow for that kind of | ||
control[^4], so `lazy` and `fetch` are provided. For example, it could be used | ||
together with `setInterval` to periodically refresh data displayed to the user. | ||
But use it with moderation: most of the time, you should prefer `request` in | ||
event handlers. | ||
|
||
Additionally, as a general guideline, one would usually use `useRequest` for | ||
`GET` requests, and `request` for `POST` and `PUT` requests. But this is not a | ||
hard rule, it will depend a lot on the API and the component. | ||
|
||
### Other definitions | ||
A few other consts and functions are exported: | ||
|
||
#### `const REQUEST_FAILED = -1` | ||
The value of the `status` field set by `useRequest`/`request` to indicate that | ||
the request failed due to an unexpected error (e.g., a network issue). | ||
|
||
#### `const REQUEST_PENDING = 0` | ||
The value of the `status` field set by `useRequest`/`request` to indicate that | ||
a request still needs to be made. This is the initial status, and it indicates | ||
that the request was not started yet. | ||
|
||
#### `const REQUEST_ONGOING = 1` | ||
The value of the `status` field set by `useRequest`/`request` to indicate that | ||
a request is in progress. | ||
|
||
#### `function requestInitialState()` | ||
Returns the initial bundle state for use with `useState` in components that are | ||
using `request`. To see it in action, check out the `requestDemo` component in | ||
the [example code](./src/index.tsx). | ||
|
||
[^1]: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry | ||
[^2]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch | ||
[^3]: https://beta.reactjs.org/learn/you-might-not-need-an-effect#sending-a-post-request | ||
[^4]: https://beta.reactjs.org/learn/you-might-not-need-an-effect#fetching-data |
Oops, something went wrong.