Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
coverage
dist
20 changes: 20 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"env": {
"browser": true,
"node": true,
"es6": true,
"jest": true
},
"extends": ["eslint:recommended", "plugin:react/recommended"],
"plugins": ["react", "react-hooks"],
"parser": "babel-eslint",
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
},
"settings": {
"react": {
"version": "detect"
}
}
}
3 changes: 2 additions & 1 deletion .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ jobs:
- name: Install latest npm
run: npm install --global npm@latest

- name: npm install and test
- name: npm install, lint and test
run: |
npm install
npm run lint
npm test -- --ci
env:
CI: true
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
coverage
dist
public
tmp
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to this project are documented in this file.

## Unreleased

- Added: `changed` callback argument.

## 0.1.3

- Added: `payload` and `method` arguments.
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ If you need a simple way to load data quickly this is for you. It allows you to
You can optionally specify a polling interval and manually trigger a refresh. It also gracefully cancels any open requests if you decide to change the URL and restarts timers if the polling interval changes.

```javascript
const { data, loading, error, refresh } = useApi("https://some-api.com", 10000);
const { data, loading, changed, error, refresh } = useApi(
"https://some-api.com",
10000
);
```

## Installation
Expand Down Expand Up @@ -80,6 +83,7 @@ const PeopleSearch = () = {
- `pollInterval` - How often to re-request updated data. Pass 0 to disable polling (the default behaviour).
- `payload` - A data object to send in the request. If we are performing a GET request, it is appended into the querystring (e.g. `?keywords=hello`). If it is a POST request it is sent in the request body as JSON.
- `method` - Set the request type, either `get` or `post`. (defaults to `get`)
- `changed`: A function that is called if the data actually changed during the request. If this is specified, use-api does extra checking and compares old and new data. If data does not change, new data is not propagated and a redraw is saved. Please note, this may have performance repercussions if the data is large as it performs a deep comparison between new and old data to determine if they are equivalent.

### Output

Expand Down
47 changes: 36 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
const { useEffect, useState } = require("react");
const { useEffect, useState, useRef } = require("react");
const axios = require("axios");
const isEqual = require("lodash.isequal");

const { CancelToken } = axios;

const useApi = (apiEndpoint, pollInterval, payload, method = "get") => {
const useApi = (
apiEndpoint,
pollInterval,
payload,
method = "get",
changed
) => {
const [data, setData] = useState({});
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
const [poll, setPoll] = useState(0);
const lastData = useRef(data);

// Function to force a refresh
const refresh = () => setPoll(poll + 1);
if (method.toLowerCase) method = method.toLowerCase();

useEffect(() => {
let timeout;

if (method.toLowerCase) method = method.toLowerCase();

if (!["get", "post"].includes(method)) {
setLoading(false);
setError("Invalid request method type, must be either post or get.");
Expand All @@ -39,8 +44,18 @@ const useApi = (apiEndpoint, pollInterval, payload, method = "get") => {
.then(response => {
// Make sure there are no errors reported
setError(null);
// Set the recieved data
setData(response.data);

// Only do change detection if change is defined.
if (changed) {
if (!isEqual(response.data, lastData.current)) {
// Set the received data ONLY IF its changed, redraw performance gain!
lastData.current = response.data;
setData(response.data);
changed(response.data);
}
} else {
setData(response.data);
}
})
.catch(thrown => {
// Only error on genuine errors, not cancellations
Expand All @@ -51,17 +66,27 @@ const useApi = (apiEndpoint, pollInterval, payload, method = "get") => {
setLoading(false);

// Poll if specified to do so
if (pollInterval) timeout = setTimeout(refresh, pollInterval);
if (pollInterval)
timeout = setTimeout(() => setPoll(poll + 1), pollInterval);
});

// Cleanup, clear a timeout and cancel the request.
return () => {
if (timeout) clearTimeout(timeout);
source.cancel();
};
}, [poll, apiEndpoint, pollInterval]);
}, [
poll,
setPoll,
apiEndpoint,
pollInterval,
payload,
method,
lastData,
changed
]);

return { data, loading, error, refresh };
return { data, loading, changed, error, refresh: () => setPoll(poll + 1) };
};

module.exports = useApi;
Loading