├── .prettierrc ├── .gitattributes ├── website ├── .babelrc ├── next.config.js ├── pages │ ├── index.js │ ├── reference │ │ ├── use-api.js │ │ ├── use-params.js │ │ └── use-inf-api.js │ ├── examples │ │ ├── basic.js │ │ ├── pagination.js │ │ ├── filter.js │ │ └── inf-scroll.js │ └── _document.js ├── constants.js ├── components │ ├── APIComponentWrapper.js │ ├── status │ │ ├── InlineLoading.js │ │ ├── NoResults.js │ │ ├── Error.js │ │ └── Loading.js │ ├── layout │ │ ├── Grid.js │ │ ├── Menu.js │ │ └── BaseLayout.js │ ├── ExampleComponent.js │ ├── ReferenceDisplay.js │ └── GoogleBooksList.js ├── examples │ ├── FilterExample │ │ ├── SearchInput.js │ │ ├── TypeSelect.js │ │ └── FilterExample.js │ ├── BasicExample.js │ ├── PaginationExample │ │ ├── OffsetPagination.js │ │ ├── PaginationExample.js │ │ └── Paginator.js │ └── InfScrolLExample │ │ ├── functions.js │ │ └── InfScrollExample.js ├── package.json └── reference │ ├── use-api.md │ ├── use-params.md │ └── use-inf-api.md ├── .editorconfig ├── .eslintrc ├── src ├── index.js ├── utils.js ├── useParams.js ├── useAPI.js └── useInfAPI.js ├── now.json ├── .gitignore ├── generate-reference.js ├── LICENSE ├── package.json └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true 4 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /website/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [["styled-components", { "ssr": true }]] 4 | } 5 | -------------------------------------------------------------------------------- /website/next.config.js: -------------------------------------------------------------------------------- 1 | // next.config.js 2 | const withCSS = require('@zeit/next-css'); 3 | 4 | module.exports = withCSS(); 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf -------------------------------------------------------------------------------- /website/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Basic from './examples/basic'; 3 | 4 | const Index = props => { 5 | return ( 6 | 7 | ); 8 | }; 9 | 10 | export default Index; 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb", 4 | "prettier" 5 | ], 6 | "plugins": [ 7 | "prettier" 8 | ], 9 | "rules": { 10 | "prettier/prettier": [ 11 | "error" 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /website/constants.js: -------------------------------------------------------------------------------- 1 | // https://coolors.co/dff8eb-2e4052-092327-0b5351-00a9a5 2 | export const booksURL = 'https://www.googleapis.com/books/v1/volumes'; 3 | export const booksInitialParams = { q: 'intitle:react', maxResults: 5 }; 4 | -------------------------------------------------------------------------------- /website/components/APIComponentWrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const APIComponentWrapper = styled.div` 4 | height: 400px; 5 | overflow-y: auto; 6 | border: 1px solid #eee; 7 | `; 8 | 9 | export default APIComponentWrapper; 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as useAPI } from './useAPI'; 2 | export { default as useParams } from './useParams'; 3 | export { default as useInfAPI } from './useInfAPI'; 4 | export { getOffsetPaginator } from './utils'; 5 | export { default as axios } from 'axios'; // Allow access to lib axios instance in order to override defaults 6 | -------------------------------------------------------------------------------- /website/components/status/InlineLoading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'spectre.css/dist/spectre-exp.css'; 3 | 4 | const InlineLoading = () => ( 5 |
6 |

Loading

7 | 8 |
9 | ); 10 | 11 | export default InlineLoading; 12 | -------------------------------------------------------------------------------- /website/pages/reference/use-api.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReferenceDisplay from '../../components/ReferenceDisplay'; 3 | import UseApiReference from '!!raw-loader!../../reference/use-api.md'; 4 | 5 | const UseApi = props => { 6 | return ( 7 | 8 | ); 9 | }; 10 | 11 | UseApi.propTypes = {}; 12 | 13 | export default UseApi; 14 | -------------------------------------------------------------------------------- /website/examples/FilterExample/SearchInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const SearchInput = ({ onChange, defaultValue, ...passProps }) => { 4 | return ( 5 |
6 | 8 |
9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "react-api-hooks", 4 | "alias": "react-api-hooks", 5 | "builds": [ 6 | { 7 | "src": "website/package.json", 8 | "use": "@now/static-build", 9 | "config": { 10 | "distDir": "out" 11 | } 12 | } 13 | ], 14 | "routes": [ 15 | { 16 | "src": "/(.*)", 17 | "dest": "/website/$1" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /website/pages/reference/use-params.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import UseParamsReference from '!!raw-loader!../../reference/use-params.md'; 3 | import ReferenceDisplay from '../../components/ReferenceDisplay'; 4 | 5 | const UseParams = props => { 6 | return ( 7 | 8 | ); 9 | }; 10 | 11 | UseParams.propTypes = {}; 12 | 13 | export default UseParams; 14 | -------------------------------------------------------------------------------- /website/pages/reference/use-inf-api.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import UseInfAPIReference from '!!raw-loader!../../reference/use-inf-api.md'; 4 | import ReferenceDisplay from '../../components/ReferenceDisplay'; 5 | 6 | const UseInfApi = () => { 7 | return ; 8 | }; 9 | 10 | UseInfApi.propTypes = {}; 11 | 12 | export default UseInfApi; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .next 26 | .idea 27 | dist 28 | out 29 | *.tgz 30 | -------------------------------------------------------------------------------- /generate-reference.js: -------------------------------------------------------------------------------- 1 | const jsdoc2md = require('jsdoc-to-markdown'); 2 | const fs = require('fs'); 3 | 4 | function generateReferenceDoc(inputFl, outputFl) { 5 | jsdoc2md 6 | .render({ 7 | files: inputFl, 8 | template: `{{>all-docs~}}` 9 | }) 10 | .then(markdown => { 11 | fs.writeFileSync(outputFl, markdown); 12 | }); 13 | } 14 | 15 | generateReferenceDoc('src/useAPI.js', 'website/reference/use-api.md'); 16 | generateReferenceDoc('src/useParams.js', 'website/reference/use-params.md'); 17 | generateReferenceDoc('src/useInfAPI.js', 'website/reference/use-inf-api.md'); 18 | -------------------------------------------------------------------------------- /website/examples/BasicExample.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useAPI } from 'react-api-hooks'; 3 | import GoogleBooksList from '../components/GoogleBooksList'; 4 | import { booksInitialParams, booksURL } from '../constants'; 5 | import Error from '../components/status/Error'; 6 | import Loading from '../components/status/Loading'; 7 | 8 | const BasicExample = () => { 9 | const { data = [], error, isLoading } = useAPI(booksURL, { params: booksInitialParams }); 10 | 11 | if (error) { 12 | return ; 13 | } 14 | 15 | return isLoading ? : ; 16 | }; 17 | 18 | export default BasicExample; 19 | -------------------------------------------------------------------------------- /website/components/layout/Grid.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import React from 'react'; 3 | 4 | const GridWrapper = styled.div` 5 | display: grid; 6 | grid-template-columns: 1fr auto 1fr; 7 | grid-template-areas: "menu content blank"; 8 | 9 | > :nth-child(2) { 10 | width: 100vw; 11 | max-width: 900px; 12 | } 13 | 14 | .menu{ 15 | grid-area: menu; 16 | } 17 | 18 | .content{ 19 | grid-area: content; 20 | } 21 | `; 22 | 23 | const Grid = ({ children }) => { 24 | return ( 25 | 26 | {children} 27 | 28 | ); 29 | }; 30 | 31 | Grid.propTypes = {}; 32 | 33 | export default Grid; 34 | -------------------------------------------------------------------------------- /website/components/status/NoResults.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import 'spectre.css/dist/spectre-exp.css'; 3 | import APIComponentWrapper from '../APIComponentWrapper'; 4 | 5 | const NoResults = () => ( 6 | 7 |
15 |
16 |
17 |

No Results

18 |
19 |
20 |
21 | 22 | ); 23 | 24 | export default NoResults; 25 | -------------------------------------------------------------------------------- /website/components/ExampleComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'prismjs'; 3 | import 'prismjs/components/prism-jsx'; 4 | import 'prismjs/themes/prism-coy.css'; 5 | import { PrismCode } from 'react-prism'; 6 | 7 | function removeImportFromSource(sourceStr){ 8 | return sourceStr.replace( 9 | /import.*;[\n\r]*/ig, 10 | '' 11 | ) 12 | } 13 | 14 | const ExampleComponent = ({ Component, componentSource }) => { 15 | return ( 16 |
17 | {Component && } 18 | 19 | {removeImportFromSource(componentSource)} 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default ExampleComponent; 26 | -------------------------------------------------------------------------------- /website/components/status/Error.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import APIComponentWrapper from '../APIComponentWrapper'; 3 | 4 | const Error = ({ error }) => { 5 | return ( 6 | 7 |
15 |
16 |
17 |

Error

18 |

An error occurred.

19 | {error.message || ''} 20 |
21 |
22 |
23 | 24 | ); 25 | }; 26 | 27 | export default Error; 28 | -------------------------------------------------------------------------------- /website/components/status/Loading.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import 'spectre.css/dist/spectre-exp.css'; 3 | import APIComponentWrapper from '../APIComponentWrapper'; 4 | 5 | const Loading = () => ( 6 | 7 |
15 |
16 |
17 |

Loading

18 | 19 |
20 |
21 |
22 | 23 | ); 24 | 25 | export default Loading; 26 | -------------------------------------------------------------------------------- /website/examples/PaginationExample/OffsetPagination.js: -------------------------------------------------------------------------------- 1 | class OffsetPagination { 2 | constructor(data, params, updateParams, pageSize) { 3 | this.data = data; 4 | this.params = params; 5 | this.updateParams = updateParams; 6 | this.pageSize = pageSize; 7 | } 8 | onNext = () => this.updateParams({ startIndex: this.getCurrentOffset() + this.pageSize }); 9 | onPrevious = () => this.updateParams({ startIndex: this.getCurrentOffset() - this.pageSize }); 10 | hasPreviousPage = () => this.params.startIndex || 0 > 0; 11 | hasNextPage = () => this.data.length === this.pageSize; 12 | getCurrentOffset = () => this.params.startIndex || 0; 13 | getPageCnt = () => Math.round(this.getCurrentOffset() / this.pageSize) + 1; 14 | } 15 | 16 | export default OffsetPagination; 17 | -------------------------------------------------------------------------------- /website/examples/FilterExample/TypeSelect.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const TypeSelect = ({ onChange, ...passProps }) => { 5 | return ( 6 |
7 | 15 |
16 | ); 17 | }; 18 | 19 | TypeSelect.propTypes = { 20 | onChange: PropTypes.func.isRequired 21 | }; 22 | 23 | export default TypeSelect; 24 | -------------------------------------------------------------------------------- /website/examples/InfScrolLExample/functions.js: -------------------------------------------------------------------------------- 1 | import { getOffsetPaginator } from 'react-api-hooks'; 2 | 3 | const pageSize = 5; 4 | 5 | /** 6 | * Google Books Paginator function. 7 | * 8 | * Alter the axios `config` object to fetch the next page. 9 | * 10 | * Update the `paginationState` object to keep track of page numbers internally. 11 | * 12 | * @param config {Object} - The axios config object passed to the hook. 13 | * @param paginationState {Object} - An object kept in state to keep track of pagination. 14 | */ 15 | export const paginator = getOffsetPaginator('startIndex', pageSize); 16 | 17 | /** 18 | * Google Books Item Extractor 19 | * 20 | * Return a list of items to the hook, given the axios response object. 21 | * 22 | * @param response {Object} - The axios response object. 23 | */ 24 | export function responseToItems(response) { 25 | const { items } = response.data; 26 | const hasMore = items.length === pageSize; 27 | return [items, hasMore]; 28 | } 29 | -------------------------------------------------------------------------------- /website/pages/examples/basic.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import BasicExampleSource from '!!raw-loader!../../examples/BasicExample'; 4 | import BasicExample from '../../examples/BasicExample'; 5 | import ExampleComponent from '../../components/ExampleComponent'; 6 | import BaseLayout from '../../components/layout/BaseLayout'; 7 | 8 | const description = ` 9 | # Basic Example 10 | 11 | Basic usage of the \`useAPI\` hook to fetch a list of books from the Google Books API. 12 | 13 | The \`data\` property provides the API response if the API request is successful. 14 | 15 | The \`isLoading\` and \`error\` properties can be used to indicate the request status to the user. 16 | `; 17 | 18 | const Basic = props => { 19 | return ( 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | Basic.propTypes = {}; 28 | 29 | export default Basic; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andrew 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-api-hooks-website", 3 | "version": "1.0.0", 4 | "description": "Website for the react-api-hooks library.", 5 | "main": "index.js", 6 | "repository": "https://github.com/ABWalters/react-api-hooks", 7 | "scripts": { 8 | "start": "next", 9 | "build": "next build", 10 | "production": "next start", 11 | "export": "npm run build && next export -o ../docs -f", 12 | "now-build": "next build && next export" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@zeit/next-css": "^1.0.1", 18 | "next": "^8.0.3", 19 | "prismjs": "^1.15.0", 20 | "query-string": "^6.4.0", 21 | "react-api-hooks": "../react-api-hooks-0.2.2.tgz", 22 | "react-dom": "^16.8.4", 23 | "react-github-corner": "^2.3.0", 24 | "react-markdown": "^4.0.8", 25 | "react-prism": "^4.3.2", 26 | "spectre.css": "^0.5.8", 27 | "styled-components": "^4.1.3" 28 | }, 29 | "devDependencies": { 30 | "babel-plugin-styled-components": "^1.10.0", 31 | "raw-loader": "^2.0.0", 32 | "react-infinite-scroller": "^1.2.4", 33 | "webpack": "^4.29.6", 34 | "webpack-dev-server": "^3.2.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /website/examples/PaginationExample/PaginationExample.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useAPI, useParams } from 'react-api-hooks'; 3 | import GoogleBooksList from '../../components/GoogleBooksList'; 4 | import { booksInitialParams, booksURL } from '../../constants'; 5 | import Error from '../../components/status/Error'; 6 | import Loading from '../../components/status/Loading'; 7 | import Paginator from './Paginator'; 8 | import OffsetPagination from './OffsetPagination'; 9 | 10 | const PaginationExample = () => { 11 | const { params, updateParams } = useParams(booksInitialParams); 12 | const { data = [], error, isLoading } = useAPI(booksURL, { params }); 13 | const pagination = new OffsetPagination(data.items || [], params, updateParams, 5); 14 | 15 | if (error) { 16 | return ; 17 | } 18 | 19 | return ( 20 | <> 21 | {isLoading ? : } 22 | 29 | 30 | ); 31 | }; 32 | 33 | export default PaginationExample; 34 | -------------------------------------------------------------------------------- /website/components/ReferenceDisplay.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | import BaseLayout from '../components/layout/BaseLayout'; 5 | 6 | const ReactMarkdown = require('react-markdown'); 7 | 8 | const MarkDownWrapper = styled.div` 9 | table { 10 | width: 100%; 11 | border-spacing: 0; 12 | } 13 | 14 | thead { 15 | background-color: rgba(0, 0, 0, 0.05); 16 | border-color: rgba(0, 0, 0, 0.05); 17 | } 18 | 19 | th { 20 | border-bottom-width: 0.1rem; 21 | } 22 | 23 | th, 24 | td { 25 | border-bottom: 0.05rem solid #dadee4; 26 | padding: 0.6rem 0.4rem; 27 | border-right: 1px solid rgba(0, 0, 0, 0.07); 28 | } 29 | 30 | th:first-child, 31 | td:first-child { 32 | border-left: 1px solid rgba(0, 0, 0, 0.07); 33 | } 34 | 35 | pre { 36 | white-space: normal; 37 | } 38 | `; 39 | 40 | const ReferenceDisplay = ({ source }) => { 41 | return ( 42 | 43 |
44 |

Reference

45 | 46 | 47 | 48 |
49 |
50 | ); 51 | }; 52 | 53 | ReferenceDisplay.propTypes = { 54 | source: PropTypes.string.isRequired 55 | }; 56 | 57 | export default ReferenceDisplay; 58 | -------------------------------------------------------------------------------- /website/examples/InfScrolLExample/InfScrollExample.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useInfAPI } from 'react-api-hooks'; 3 | import InfiniteScroll from 'react-infinite-scroller'; 4 | import { GoogleBooksListInner } from '../../components/GoogleBooksList'; 5 | import Error from '../../components/status/Error'; 6 | import InlineLoading from '../../components/status/InlineLoading'; 7 | import { booksInitialParams, booksURL } from '../../constants'; 8 | import { paginator, responseToItems } from './functions'; 9 | 10 | const InfScrollExample = () => { 11 | const { items, error, isPaging, hasMore, fetchPage } = useInfAPI( 12 | booksURL, 13 | { params: booksInitialParams }, 14 | paginator, 15 | responseToItems 16 | ); 17 | 18 | if (error) { 19 | return ; 20 | } 21 | 22 | return ( 23 |
24 | { 27 | if (!isPaging) { 28 | fetchPage(); 29 | } 30 | }} 31 | hasMore={hasMore} 32 | loader={} 33 | useWindow={false} 34 | > 35 | 36 | 37 |
38 | ); 39 | }; 40 | 41 | InfScrollExample.propTypes = {}; 42 | 43 | export default InfScrollExample; 44 | -------------------------------------------------------------------------------- /website/pages/examples/pagination.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import ExampleComponent from '../../components/ExampleComponent'; 4 | import BaseLayout from '../../components/layout/BaseLayout'; 5 | import APIWithPagination from '../../examples/PaginationExample/PaginationExample'; 6 | import APIWithPaginationSource from '!!raw-loader!../../examples/PaginationExample/PaginationExample'; 7 | import OffsetPaginationSource from '!!raw-loader!../../examples/PaginationExample/OffsetPagination'; 8 | 9 | const description = ` 10 | # Pagination Example 11 | 12 | An example showing how the \`useParams\` and \`useAPI\` hooks can be used together to paginate results from an API. 13 | 14 | The \`useParams\` hook keeps a params object in it's state, that can be used when making calls to the API. 15 | 16 | The API uses offset pagination, by updating the \`startIndex\` param, the component can paginate through the results. 17 | `; 18 | 19 | const Pagination = props => { 20 | return ( 21 | 22 | <> 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | Pagination.propTypes = {}; 32 | 33 | export default Pagination; 34 | -------------------------------------------------------------------------------- /website/pages/examples/filter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import ExampleComponent from '../../components/ExampleComponent'; 4 | import BaseLayout from '../../components/layout/BaseLayout'; 5 | import APIWithSearch from '../../examples/FilterExample/FilterExample'; 6 | import APIWithSearchSource from '!!raw-loader!../../examples/FilterExample/FilterExample'; 7 | 8 | const description = ` 9 | # Filter Example 10 | 11 | An example showing how the \`useParams\` and \`useAPI\` hooks can be used together to filter results from an API. 12 | 13 | The \`useParams\` hook keeps a params object in it's state, that can be used when making calls to the API. 14 | 15 | Use the \`updateParams\` method to immediately update the params object, and trigger a refresh. 16 | 17 | Use the \`debouncedUpdateParams\` method to delay the params update until \`wait\` ms have passed between function calls. 18 | ([Using the lodash debounce function](https://lodash.com/docs/4.17.11#debounce)). 19 | 20 | The \`isStale\` property indicates whether the is a debounced params update pending. 21 | `; 22 | 23 | const Pagination = props => { 24 | return ( 25 | 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | Pagination.propTypes = {}; 33 | 34 | export default Pagination; 35 | -------------------------------------------------------------------------------- /website/pages/examples/inf-scroll.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import ExampleComponent from '../../components/ExampleComponent'; 4 | import BaseLayout from '../../components/layout/BaseLayout'; 5 | import InfScrollExample from '../../examples/InfScrolLExample/InfScrollExample'; 6 | import InfScrollExampleSource from '!!raw-loader!../../examples/InfScrolLExample/InfScrollExample'; 7 | import InfScrollFunctionSource from '!!raw-loader!../../examples/InfScrolLExample/functions'; 8 | 9 | const description = ` 10 | # Infinite Scroll Example 11 | 12 | Basic usage of the \`useInfAPI\` hook to scroll through a list of books from the Google Books API. 13 | 14 | Use the \`react-infinite-scroller\` component to track scrolling, and call \`fetchPage\` when required. 15 | 16 | By default the hook expects the API to paginate using a parameter called \`offset\` and that the API returns an array of items. 17 | 18 | If these defaults do not work for your API, then you will need to provide your own \`paginator\` and \`responseToItems\` functions. 19 | `; 20 | 21 | const InfScroll = () => { 22 | return ( 23 | 24 | <> 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | InfScroll.propTypes = {}; 34 | 35 | export default InfScroll; 36 | -------------------------------------------------------------------------------- /website/examples/PaginationExample/Paginator.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import 'spectre.css/dist/spectre-icons.css'; 4 | import styled from 'styled-components'; 5 | 6 | const PaginatorWrapper = styled.div` 7 | display: flex; 8 | margin-top: 5px; 9 | 10 | .previous, 11 | .next { 12 | } 13 | 14 | .center { 15 | flex-grow: 1; 16 | text-align: center; 17 | line-height: 36px; 18 | } 19 | 20 | .btn, 21 | .btn:focus { 22 | background-color: #0b5351; 23 | border-color: #00a9a5; 24 | 25 | :hover, 26 | :active { 27 | background-color: #009793; 28 | border-color: #0b5351; 29 | } 30 | } 31 | `; 32 | 33 | const Paginator = ({ hasNext, hasPrevious, onNext, pageCnt, onPrevious, ...passProps }) => { 34 | return ( 35 | 36 |
37 | 41 |
42 |
Page #{pageCnt}
43 |
44 | 48 |
49 |
50 | ); 51 | }; 52 | 53 | Paginator.propTypes = { 54 | hasNext: PropTypes.bool.isRequired, 55 | hasPrevious: PropTypes.bool.isRequired 56 | }; 57 | 58 | export default Paginator; 59 | -------------------------------------------------------------------------------- /website/examples/FilterExample/FilterExample.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useAPI, useParams } from 'react-api-hooks'; 3 | import GoogleBooksList from '../../components/GoogleBooksList'; 4 | import { booksInitialParams, booksURL } from '../../constants'; 5 | import TypeSelect from './TypeSelect'; 6 | import Error from '../../components/status/Error'; 7 | import Loading from '../../components/status/Loading'; 8 | import { SearchInput } from './SearchInput'; 9 | 10 | const FilterExample = () => { 11 | const { params, updateParams, debouncedUpdateParams, isStale } = useParams(booksInitialParams); 12 | const { data = [], error, isLoading } = useAPI(booksURL, { params }); 13 | 14 | if (error) { 15 | return ; 16 | } 17 | 18 | return ( 19 | <> 20 |
21 | 24 | debouncedUpdateParams({ 25 | q: `intitle:${e.target.value.toLowerCase()}` 26 | }) 27 | } 28 | defaultValue="react" 29 | /> 30 | 33 | updateParams({ 34 | filter: e.target.value ? e.target.value : undefined 35 | }) 36 | } 37 | /> 38 |
39 | {isLoading ? ( 40 | 41 | ) : ( 42 | 43 | )} 44 | 45 | ); 46 | }; 47 | 48 | export default FilterExample; 49 | -------------------------------------------------------------------------------- /website/components/layout/Menu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | import styled from 'styled-components'; 4 | 5 | const MenuWrapper = styled.div` 6 | max-width: 300px; 7 | margin: 0 1rem; 8 | `; 9 | 10 | const MenuItem = ({ children, href, subTitle }) => { 11 | return ( 12 |
  • 13 | 14 | 15 | {children} 16 |
    17 | {subTitle} 18 |
    19 | 20 |
  • 21 | ); 22 | }; 23 | 24 | const Menu = () => { 25 | return ( 26 | 27 |
      28 |
    • 29 | 30 | Basic Example 31 | 32 | 33 | Pagination Example 34 | 35 | 36 | Filter Example 37 | 38 | 39 | Infinite Scroll Example 40 | 41 |
    • 42 | useAPI 43 | useParams 44 | useInfAPI 45 |
    46 |
    47 | ); 48 | }; 49 | 50 | Menu.propTypes = {}; 51 | 52 | export default Menu; 53 | -------------------------------------------------------------------------------- /website/pages/_document.js: -------------------------------------------------------------------------------- 1 | import Document, { Head, Html, Main, NextScript } from 'next/document'; 2 | import { ServerStyleSheet } from 'styled-components'; 3 | 4 | const googleAnalyticsInner = ` 5 | window.dataLayer = window.dataLayer || []; 6 | function gtag(){dataLayer.push(arguments);} 7 | gtag('js', new Date()); 8 | 9 | gtag('config', 'UA-53236741-7'); 10 | `; 11 | 12 | export default class MyDocument extends Document { 13 | static async getInitialProps(ctx) { 14 | const sheet = new ServerStyleSheet(); 15 | const originalRenderPage = ctx.renderPage; 16 | 17 | try { 18 | ctx.renderPage = () => 19 | originalRenderPage({ 20 | enhanceApp: App => props => sheet.collectStyles() 21 | }); 22 | 23 | const initialProps = await Document.getInitialProps(ctx); 24 | 25 | // Used by Google Analytics 26 | const isProduction = process.env.NODE_ENV === 'production'; 27 | 28 | return { 29 | ...initialProps, 30 | isProduction, 31 | styles: <>{initialProps.styles}{sheet.getStyleElement()} 32 | }; 33 | } finally { 34 | sheet.seal(); 35 | } 36 | } 37 | 38 | render() { 39 | const { isProduction } = this.props; 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 |
    47 | 48 | {isProduction && (<> 49 |