├── website ├── .gitignore ├── static │ ├── img │ │ ├── code.mp4 │ │ ├── json.png │ │ ├── code.webm │ │ ├── component.png │ │ ├── resource.png │ │ ├── favicon │ │ │ └── favicon.ico │ │ ├── rest_hooks_logo.png │ │ ├── dice-d6-solid.svg │ │ ├── coinbase-logo.svg │ │ ├── spa-solid.svg │ │ ├── space-shuttle-solid.svg │ │ ├── rest_hooks_logo.svg │ │ └── github-brands.svg │ ├── font │ │ ├── Graphik-Medium-Web.woff2 │ │ ├── Graphik-Regular-Web.woff2 │ │ ├── Graphik-Semibold-Web.woff2 │ │ ├── Graphik-MediumItalic-Web.woff2 │ │ ├── Graphik-RegularItalic-Web.woff2 │ │ └── Graphik-SemiboldItalic-Web.woff2 │ ├── .circleci │ │ └── config.yml │ ├── scripts │ │ └── sidebarScroll.js │ └── css │ │ ├── code-block-buttons.css │ │ └── font.css ├── versions.json ├── package.json ├── versioned_docs │ ├── version-1.6.9 │ │ ├── guides │ │ │ ├── immutability.md │ │ │ ├── multi-pk.md │ │ │ ├── jsonp.md │ │ │ ├── auth.md │ │ │ ├── computed-properties.md │ │ │ ├── README.md │ │ │ ├── resource-lifetime.md │ │ │ └── fetch-multiple.md │ │ └── api │ │ │ ├── NetworkManager.md │ │ │ ├── PollingSubscription.md │ │ │ ├── RestProvider.md │ │ │ ├── useRetrieve.md │ │ │ ├── useCache.md │ │ │ ├── makeRestProvider.md │ │ │ ├── README.md │ │ │ ├── makeExternalCacheProvider.md │ │ │ ├── useResultCache.md │ │ │ ├── ExternalCacheProvider.md │ │ │ └── NetworkErrorBoundary.md │ ├── version-3.0 │ │ ├── guides │ │ │ ├── multi-pk.md │ │ │ ├── jsonp.md │ │ │ ├── computed-properties.md │ │ │ └── resource-lifetime.md │ │ └── api │ │ │ ├── useResetter.md │ │ │ ├── README.md │ │ │ ├── CacheProvider.md │ │ │ └── useCacheLegacy.md │ ├── version-2.0 │ │ ├── api │ │ │ ├── PollingSubscription.md │ │ │ ├── useCache.md │ │ │ ├── makeCacheProvider.md │ │ │ ├── README.md │ │ │ ├── useResultCache.md │ │ │ ├── ExternalCacheProvider.md │ │ │ ├── NetworkErrorBoundary.md │ │ │ ├── CacheProvider.md │ │ │ └── useRetrieve.md │ │ └── guides │ │ │ ├── resource-lifetime.md │ │ │ └── fetch-multiple.md │ ├── version-4.0 │ │ ├── api │ │ │ ├── PollingSubscription.md │ │ │ └── README.md │ │ └── guides │ │ │ └── auth.md │ ├── version-4.1 │ │ ├── guides │ │ │ └── computed-properties.md │ │ └── api │ │ │ └── README.md │ ├── version-2.1 │ │ └── api │ │ │ ├── makeCacheProvider.md │ │ │ ├── makeExternalCacheProvider.md │ │ │ ├── useCache.md │ │ │ └── useResultCache.md │ ├── version-2.2 │ │ └── api │ │ │ ├── useResetter.md │ │ │ ├── README.md │ │ │ └── useCache.md │ └── version-4.5 │ │ └── guides │ │ └── binary-fetches.md ├── blog │ └── 2020-01-06-Rest-Hooks-4.1-Released.md └── pages │ └── en │ ├── users.js │ └── help.js ├── .dockerignore ├── packages ├── test │ ├── .gitignore │ ├── tsconfig.compile.json │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── MockProvider.tsx │ │ ├── managers.ts │ │ ├── mockState.ts │ │ └── makeRenderRestHook.tsx │ ├── README.md │ ├── rollup.config.js │ └── package.json ├── legacy │ ├── .gitignore │ ├── tsconfig.compile.json │ ├── tsconfig.json │ └── src │ │ └── index.ts ├── rest-hooks │ ├── .gitignore │ ├── src │ │ ├── errors.ts │ │ ├── state │ │ │ ├── isOnline.ts │ │ │ ├── RIC.ts │ │ │ ├── selectors │ │ │ │ ├── index.ts │ │ │ │ ├── getEntityPath.ts │ │ │ │ └── __tests__ │ │ │ │ │ └── __snapshots__ │ │ │ │ │ └── useDenormalized.ts.snap │ │ │ ├── __tests__ │ │ │ │ ├── isOnline.ts │ │ │ │ └── __snapshots__ │ │ │ │ │ ├── reducer.ts.snap │ │ │ │ │ └── pollingSubscription.ts.snap │ │ │ ├── applyUpdatersToResults.ts │ │ │ └── merge │ │ │ │ └── isMergeable.ts │ │ ├── react-integration │ │ │ ├── index.ts │ │ │ ├── provider │ │ │ │ ├── index.ts │ │ │ │ ├── PromiseifyMiddleware.ts │ │ │ │ ├── __tests__ │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── provider.tsx.snap │ │ │ │ └── ExternalCacheProvider.tsx │ │ │ ├── hooks │ │ │ │ ├── hasUsableData.ts │ │ │ │ ├── useResetter.ts │ │ │ │ ├── index.ts │ │ │ │ ├── useCache.ts │ │ │ │ ├── useMeta.ts │ │ │ │ ├── useError.ts │ │ │ │ ├── useInvalidator.ts │ │ │ │ ├── useSelection.ts │ │ │ │ └── useRetrieve.ts │ │ │ ├── context.ts │ │ │ ├── __tests__ │ │ │ │ └── __snapshots__ │ │ │ │ │ ├── useCache.tsx.snap │ │ │ │ │ └── useResource.tsx.snap │ │ │ └── NetworkErrorBoundary.tsx │ │ ├── resource │ │ │ ├── paramsToString.ts │ │ │ ├── paramsToString.native.ts │ │ │ ├── index.ts │ │ │ ├── publicTypes.ts │ │ │ ├── __tests__ │ │ │ │ └── __snapshots__ │ │ │ │ │ └── resource.ts.snap │ │ │ ├── types.ts │ │ │ ├── normal.ts │ │ │ └── shapes.ts │ │ └── actionTypes.ts │ ├── tsconfig.compile.json │ └── tsconfig.json ├── use-enhanced-reducer │ ├── .gitignore │ ├── tsconfig.compile.json │ ├── tsconfig.json │ └── src │ │ ├── index.ts │ │ ├── __tests__ │ │ └── __snapshots__ │ │ │ └── middleware.tsx.snap │ │ ├── types.ts │ │ └── usePromisifiedDispatch.ts └── normalizr │ ├── examples │ ├── redux │ │ ├── .gitignore │ │ ├── .babelrc │ │ ├── usage.gif │ │ ├── src │ │ │ ├── api │ │ │ │ ├── index.js │ │ │ │ └── schema.js │ │ │ └── redux │ │ │ │ ├── index.js │ │ │ │ ├── selectors.js │ │ │ │ ├── actions.js │ │ │ │ ├── modules │ │ │ │ ├── repos.js │ │ │ │ ├── users.js │ │ │ │ ├── issues.js │ │ │ │ ├── labels.js │ │ │ │ ├── commits.js │ │ │ │ ├── milestones.js │ │ │ │ └── pull-requests.js │ │ │ │ └── reducer.js │ │ ├── README.md │ │ └── package.json │ ├── .eslintrc │ ├── relationships │ │ ├── index.js │ │ ├── README.md │ │ ├── schema.js │ │ └── input.json │ └── github │ │ ├── README.md │ │ ├── schema.js │ │ └── index.js │ ├── .eslintrc │ ├── jest.config.js │ ├── tsconfig.json │ ├── src │ ├── index.js │ └── schemas │ │ ├── Union.js │ │ ├── Values.js │ │ └── ImmutableUtils.js │ ├── .babelrc.js │ ├── docs │ ├── faqs.md │ ├── README.md │ ├── jsonapi.md │ └── quickstart.md │ ├── typescript-tests │ ├── values.ts │ ├── union.ts │ ├── object.ts │ ├── array.ts │ ├── array_schema.ts │ ├── github.ts │ ├── entity.ts │ └── relationships.ts │ └── LICENSE ├── .eslintignore ├── scripts ├── babel-jest.js └── testSetup.js ├── tsconfig.test.json ├── .prettierrc ├── docs ├── getting-started │ └── README.md ├── guides │ ├── immutability.md │ ├── multi-pk.md │ ├── computed-properties.md │ ├── README.md │ ├── resource-lifetime.md │ ├── fetch-multiple.md │ ├── binary-fetches.md │ └── network-errors.md ├── ROADMAP.md └── api │ ├── NetworkManager.md │ ├── PollingSubscription.md │ ├── makeCacheProvider.md │ ├── makeExternalCacheProvider.md │ ├── useResetter.md │ ├── ExternalCacheProvider.md │ ├── NetworkErrorBoundary.md │ ├── README.md │ ├── CacheProvider.md │ ├── MockProvider.md │ └── useRetrieve.md ├── Dockerfile ├── __tests__ ├── tsconfig.json └── utils.ts ├── .eslintrc ├── salus.yaml ├── .editorconfig ├── .commitlintrc.js ├── tsconfig.json ├── babel.config.js ├── lerna.json ├── .github ├── workflows │ └── main.yml ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── docker-compose.yml ├── .vscode └── settings.json └── .gitignore /website/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | */node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /packages/test/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /dist 3 | -------------------------------------------------------------------------------- /packages/legacy/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /dist 3 | -------------------------------------------------------------------------------- /packages/rest-hooks/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /dist 3 | -------------------------------------------------------------------------------- /packages/use-enhanced-reducer/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /dist 3 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | *.log 3 | -------------------------------------------------------------------------------- /packages/normalizr/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": {"no-unused-vars":"off"} 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/lib*/* 2 | **/dist*/* 3 | **/node_modules*/* 4 | node_modules/* 5 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/normalizr/examples/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/normalizr/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ['**/__tests__/**/*.test.js'] 3 | }; 4 | -------------------------------------------------------------------------------- /website/static/img/code.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/img/code.mp4 -------------------------------------------------------------------------------- /website/static/img/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/img/json.png -------------------------------------------------------------------------------- /scripts/babel-jest.js: -------------------------------------------------------------------------------- 1 | module.exports = require("babel-jest").createTransformer({ 2 | rootMode: "upward", 3 | }); 4 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-base", 3 | "include": ["packages/*/src", "__tests__"] 4 | } 5 | -------------------------------------------------------------------------------- /website/static/img/code.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/img/code.webm -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /packages/test/tsconfig.compile.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { "paths": {} } 4 | } 5 | -------------------------------------------------------------------------------- /website/static/img/component.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/img/component.png -------------------------------------------------------------------------------- /website/static/img/resource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/img/resource.png -------------------------------------------------------------------------------- /docs/getting-started/README.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | - [Installation](./installation.md) 4 | - [Usage](./usage.md) 5 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/errors.ts: -------------------------------------------------------------------------------- 1 | export class NotImplementedError extends Error { 2 | message = 'Not Implemented'; 3 | } 4 | -------------------------------------------------------------------------------- /website/static/img/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/img/favicon/favicon.ico -------------------------------------------------------------------------------- /website/static/img/rest_hooks_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/img/rest_hooks_logo.png -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/packages/normalizr/examples/redux/usage.gif -------------------------------------------------------------------------------- /website/static/font/Graphik-Medium-Web.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/font/Graphik-Medium-Web.woff2 -------------------------------------------------------------------------------- /website/static/font/Graphik-Regular-Web.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/font/Graphik-Regular-Web.woff2 -------------------------------------------------------------------------------- /website/static/font/Graphik-Semibold-Web.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/font/Graphik-Semibold-Web.woff2 -------------------------------------------------------------------------------- /website/static/font/Graphik-MediumItalic-Web.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/font/Graphik-MediumItalic-Web.woff2 -------------------------------------------------------------------------------- /website/static/font/Graphik-RegularItalic-Web.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/font/Graphik-RegularItalic-Web.woff2 -------------------------------------------------------------------------------- /website/static/font/Graphik-SemiboldItalic-Web.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroutine/rest-hooks/master/website/static/font/Graphik-SemiboldItalic-Web.woff2 -------------------------------------------------------------------------------- /packages/normalizr/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-base", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /scripts/testSetup.js: -------------------------------------------------------------------------------- 1 | require('whatwg-fetch'); 2 | require('core-js/stable'); 3 | window.requestIdleCallback = jest.fn().mockImplementation(cb => { 4 | cb(); 5 | }); 6 | -------------------------------------------------------------------------------- /website/versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | "4.5", 3 | "4.3", 4 | "4.2", 5 | "4.1", 6 | "4.0", 7 | "3.0", 8 | "2.2", 9 | "2.1", 10 | "2.0", 11 | "1.6.9" 12 | ] 13 | -------------------------------------------------------------------------------- /packages/legacy/tsconfig.compile.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { "paths": {} }, 4 | "exclude": ["node_modules", "dist", "lib", "**/__tests__"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/normalizr/src/index.js: -------------------------------------------------------------------------------- 1 | import { denormalize } from './denormalize'; 2 | import { schema, normalize } from './normalize'; 3 | 4 | export { denormalize, schema, normalize }; 5 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/isOnline.ts: -------------------------------------------------------------------------------- 1 | export default function isOnline(): boolean { 2 | if (navigator.onLine !== undefined) { 3 | return navigator.onLine; 4 | } 5 | return true; 6 | } 7 | -------------------------------------------------------------------------------- /packages/use-enhanced-reducer/tsconfig.compile.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { "paths": {} }, 4 | "exclude": ["node_modules", "dist", "lib", "**/__tests__"] 5 | } 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.13 2 | 3 | WORKDIR /app/website 4 | 5 | EXPOSE 3000 35729 6 | COPY ./docs /app/docs 7 | COPY ./website /app/website 8 | RUN yarn install 9 | 10 | CMD ["yarn", "start"] 11 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/api/index.js: -------------------------------------------------------------------------------- 1 | import GitHubApi from 'github'; 2 | 3 | export default new GitHubApi({ 4 | headers: { 5 | 'user-agent': 'Normalizr Redux Example' 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /packages/use-enhanced-reducer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": ".", 5 | }, 6 | "include": ["."], 7 | "references": [ 8 | { "path": "../packages/rest-hooks" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"], 8 | "references": [{ "path": "../rest-hooks" }] 9 | } 10 | -------------------------------------------------------------------------------- /packages/use-enhanced-reducer/src/index.ts: -------------------------------------------------------------------------------- 1 | import useEnhancedReducer from './useEnhancedReducer'; 2 | export * from './types'; 3 | export { default as usePromisifiedDispatch } from './usePromisifiedDispatch'; 4 | 5 | export default useEnhancedReducer; 6 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/index.ts: -------------------------------------------------------------------------------- 1 | import NetworkErrorBoundary from './NetworkErrorBoundary'; 2 | 3 | export * from './provider'; 4 | export * from './hooks'; 5 | export * from './NetworkErrorBoundary'; 6 | export { NetworkErrorBoundary }; 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@anansi/typescript" 4 | ], 5 | "rules": { 6 | "@typescript-eslint/no-empty-function": "warn" 7 | }, 8 | "settings": { 9 | "import/resolver": { 10 | "node": {} 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/RIC.ts: -------------------------------------------------------------------------------- 1 | const RIC: (cb: (...args: any[]) => void, options: any) => void = 2 | typeof (global as any).requestIdleCallback === 'function' 3 | ? (global as any).requestIdleCallback 4 | : cb => global.setTimeout(cb, 0); 5 | export default RIC; 6 | -------------------------------------------------------------------------------- /packages/rest-hooks/tsconfig.compile.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "paths": { 5 | "rest-hooks/*": ["packages/rest-hooks/src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist", "lib", "**/__tests__"] 9 | } 10 | -------------------------------------------------------------------------------- /salus.yaml: -------------------------------------------------------------------------------- 1 | scanner_configs: 2 | YarnAudit: 3 | exclude_groups: 4 | - devDependencies 5 | active_scanners: 6 | - RepoNotEmpty 7 | - Brakeman 8 | - BundleAudit 9 | - PatternSearch 10 | - Gosec 11 | - NPMAudit 12 | - NodeAudit 13 | - YarnAudit 14 | -------------------------------------------------------------------------------- /packages/normalizr/.babelrc.js: -------------------------------------------------------------------------------- 1 | const { NODE_ENV, BABEL_ENV } = process.env; 2 | 3 | const cjs = BABEL_ENV === 'cjs' || NODE_ENV === 'test'; 4 | 5 | module.exports = { 6 | presets: [ 7 | ['@anansi/babel-preset', { useESModules: !cjs, loose: true }] 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/resource/paramsToString.ts: -------------------------------------------------------------------------------- 1 | export default function paramsToString( 2 | searchParams: Readonly>, 3 | ) { 4 | const params = new URLSearchParams(searchParams as any); 5 | params.sort(); 6 | return params.toString(); 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | quote_type = single 12 | 13 | [*.py] 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /packages/legacy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"], 8 | "references": [ 9 | { "path": "../rest-hooks" }, 10 | { "path": "../test" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/provider/index.ts: -------------------------------------------------------------------------------- 1 | import PromiseifyMiddleware from './PromiseifyMiddleware'; 2 | import CacheProvider from './CacheProvider'; 3 | import ExternalCacheProvider from './ExternalCacheProvider'; 4 | 5 | export { CacheProvider, ExternalCacheProvider, PromiseifyMiddleware }; 6 | -------------------------------------------------------------------------------- /packages/normalizr/docs/faqs.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | If you are having trouble with Normalizr, try [StackOverflow](http://stackoverflow.com/questions/tagged/normalizr). There is a larger community there that will help you solve issues a lot quicker than opening an Issue on the Normalizr GitHub page. 4 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/selectors/index.ts: -------------------------------------------------------------------------------- 1 | import { State } from 'rest-hooks/types'; 2 | 3 | import useDenormalized from './useDenormalized'; 4 | 5 | export { useDenormalized }; 6 | 7 | export function selectMeta(state: State, fetchKey: string) { 8 | return state.meta[fetchKey]; 9 | } 10 | -------------------------------------------------------------------------------- /packages/normalizr/typescript-tests/values.ts: -------------------------------------------------------------------------------- 1 | import { normalize, schema } from '../src' 2 | 3 | const data = { firstThing: { id: 1 }, secondThing: { id: 2 } }; 4 | 5 | const item = new schema.Entity('items'); 6 | const valuesSchema = new schema.Values(item); 7 | 8 | const normalizedData = normalize(data, valuesSchema); 9 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | ['feat', 'enhance', 'fix', 'pkg', 'internal', 'docs'], 8 | ], 9 | 'subject-case': [2, 'never', ['pascal-case', 'upper-case']], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /website/static/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # CircleCI 2.0 Config File 2 | # This config file will prevent tests from being run on the gh-pages branch. 3 | version: 2 4 | jobs: 5 | build: 6 | machine: true 7 | branches: 8 | ignore: gh-pages 9 | steps: 10 | - run: echo "Skipping tests on gh-pages branch" 11 | -------------------------------------------------------------------------------- /packages/use-enhanced-reducer/src/__tests__/__snapshots__/middleware.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`createEnhancedReducerHook warns when dispatching during middleware setup 1`] = `"Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch."`; 4 | -------------------------------------------------------------------------------- /packages/rest-hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"], 8 | "references": [ 9 | { "path": "../../__tests__" }, 10 | { "path": "../normalizr" }, 11 | { "path": "../use-enhanced-reducer" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/index.js: -------------------------------------------------------------------------------- 1 | import * as schema from '../api/schema'; 2 | import api from '../api'; 3 | import reducer from './reducer'; 4 | import thunk from 'redux-thunk'; 5 | import { applyMiddleware, createStore } from 'redux'; 6 | 7 | export default createStore(reducer, applyMiddleware(thunk.withExtraArgument({ api, schema }))); 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "include": [], 4 | "references": [ 5 | { "path": "./packages/use-enhanced-reducer/tsconfig.compile.json" }, 6 | { "path": "./packages/rest-hooks/tsconfig.compile.json" }, 7 | { "path": "./packages/test/tsconfig.compile.json" }, 8 | { "path": "./packages/legacy/tsconfig.compile.json" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/resource/paramsToString.native.ts: -------------------------------------------------------------------------------- 1 | export default function paramsToString( 2 | searchParams: Readonly>, 3 | ) { 4 | const params = new URLSearchParams(searchParams as any); 5 | try { 6 | params.sort(); 7 | // eslint-disable-next-line no-empty 8 | } catch {} 9 | return params.toString(); 10 | } 11 | -------------------------------------------------------------------------------- /docs/guides/immutability.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Understanding Immutability 3 | sidebar_label: Understanding Immutability 4 | --- 5 | ## Benefits 6 | 7 | * Simplicity 8 | * Predictability 9 | * Performance 10 | * `===` checks allow frequent short-circuiting in React 11 | 12 | ## [The case for immutability](https://github.com/immutable-js/immutable-js#the-case-for-immutability) 13 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache.using(() => process.env.NODE_ENV); 3 | return { 4 | presets: [ 5 | [ 6 | '@anansi/babel-preset', 7 | { 8 | typing: 'typescript', 9 | loose: true, 10 | }, 11 | ], 12 | ], 13 | // allows us to load .babelrc in addition to this 14 | babelrcRoots: ['packages/*'], 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/selectors.js: -------------------------------------------------------------------------------- 1 | export { selectHydrated as selectcommit } from './modules/commits'; 2 | export { selectHydrated as selectissue } from './modules/issues'; 3 | export { selectHydrated as selectlabel } from './modules/labels'; 4 | export { selectHydrated as selectmilestone } from './modules/milestones'; 5 | export { selectHydrated as selectpullRequest } from './modules/pull-requests'; 6 | -------------------------------------------------------------------------------- /packages/normalizr/examples/relationships/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import input from './input.json'; 3 | import { normalize } from '../../src'; 4 | import path from 'path'; 5 | import postsSchema from './schema'; 6 | 7 | const normalizedData = normalize(input, postsSchema); 8 | const output = JSON.stringify(normalizedData, null, 2); 9 | fs.writeFileSync(path.resolve(__dirname, './output.json'), output); 10 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/resource/index.ts: -------------------------------------------------------------------------------- 1 | import Resource from './Resource'; 2 | import Entity from './Entity'; 3 | import SimpleResource from './SimpleResource'; 4 | import SimpleRecord from './SimpleRecord'; 5 | export * from './shapes'; 6 | export * from './types'; 7 | export * from './publicTypes'; 8 | export * from './normal'; 9 | export * from './Entity'; 10 | 11 | export { Resource, SimpleResource, SimpleRecord, Entity }; 12 | -------------------------------------------------------------------------------- /packages/normalizr/typescript-tests/union.ts: -------------------------------------------------------------------------------- 1 | import { normalize, schema } from '../src' 2 | 3 | const data = { owner: { id: 1, type: 'user' } }; 4 | 5 | const user = new schema.Entity('users'); 6 | const group = new schema.Entity('groups'); 7 | const unionSchema = new schema.Union( 8 | { 9 | user: user, 10 | group: group 11 | }, 12 | 'type' 13 | ); 14 | 15 | const normalizedData = normalize(data, { owner: unionSchema }); 16 | -------------------------------------------------------------------------------- /packages/test/src/index.ts: -------------------------------------------------------------------------------- 1 | import makeRenderRestHook from './makeRenderRestHook'; 2 | import { makeExternalCacheProvider, makeCacheProvider } from './providers'; 3 | import MockProvider from './MockProvider'; 4 | import mockInitialState from './mockState'; 5 | export * from './managers'; 6 | 7 | export { 8 | makeRenderRestHook, 9 | makeExternalCacheProvider, 10 | makeCacheProvider, 11 | MockProvider, 12 | mockInitialState, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/hooks/hasUsableData.ts: -------------------------------------------------------------------------------- 1 | import { FetchShape } from 'rest-hooks/resource'; 2 | 3 | /** If the invalidIfStale option is set we suspend if resource has expired */ 4 | export default function hasUsableData( 5 | cacheReady: boolean, 6 | fetchShape: Pick, 'options'>, 7 | ) { 8 | return !( 9 | (fetchShape.options && fetchShape.options.invalidIfStale) || 10 | !cacheReady 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "examples": "docusaurus-examples", 4 | "start": "docusaurus-start", 5 | "build": "docusaurus-build", 6 | "publish-gh-pages": "docusaurus-publish", 7 | "write-translations": "docusaurus-write-translations", 8 | "version": "docusaurus-version", 9 | "rename-version": "docusaurus-rename-version" 10 | }, 11 | "devDependencies": { 12 | "docusaurus": "1.14.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/normalizr/docs/README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | - [Read Me](/README.md) 4 | - [Introduction](/docs/introduction.md) 5 | - [Quick Start](/docs/quickstart.md) 6 | - [API](/docs/api.md) 7 | - [normalize](/docs/api.md#normalizedata-schema) 8 | - [denormalize](/docs/api.md#denormalizeinput-schema-entities) 9 | - [schema](/docs/api.md#schema) 10 | - [Frequently Asked Questions](/docs/faqs.md) 11 | - [Using with JSONAPI](/docs/jsonapi.md) 12 | -------------------------------------------------------------------------------- /packages/normalizr/typescript-tests/object.ts: -------------------------------------------------------------------------------- 1 | import { normalize, schema } from '../src' 2 | 3 | const data = { 4 | /* ...*/ 5 | }; 6 | const user = new schema.Entity('users'); 7 | 8 | const responseSchema = new schema.Object({ users: new schema.Array(user) }); 9 | const normalizedData = normalize(data, responseSchema); 10 | 11 | const responseSchemaAlt = { users: new schema.Array(user) }; 12 | const normalizedDataAlt = normalize(data, responseSchemaAlt); 13 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/provider/PromiseifyMiddleware.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { MiddlewareAPI, Dispatch } from '@rest-hooks/use-enhanced-reducer'; 3 | 4 | const PromiseifyMiddleware = >( 5 | _: MiddlewareAPI, 6 | ) => (next: Dispatch) => (action: React.ReducerAction): Promise => { 7 | next(action); 8 | return Promise.resolve(); 9 | }; 10 | export default PromiseifyMiddleware; 11 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "0.0.1", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "version": "independent", 6 | "command": { 7 | "publish": { 8 | "conventionalCommits": true, 9 | "preid": "beta", 10 | "preDistTag": "beta", 11 | "changelogPreset": "anansi", 12 | "createRelease": "github", 13 | "message": "internal: publish", 14 | "ignoreChanges": ["**/__tests__/**", "**/*.md"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/guides/immutability.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Understanding Immutability 3 | sidebar_label: Understanding Immutability 4 | id: version-1.6.9-immutability 5 | original_id: immutability 6 | --- 7 | ## Benefits 8 | 9 | * Simplicity 10 | * Predictability 11 | * Performance 12 | * `===` checks allow frequent short-circuiting in React 13 | 14 | ## [The case for immutability](https://github.com/immutable-js/immutable-js#the-case-for-immutability) 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Compressed Size 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2-beta 12 | with: 13 | fetch-depth: 1 14 | - name: compressed-size-action 15 | uses: preactjs/compressed-size-action@v1 16 | continue-on-error: true 17 | with: 18 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 19 | build-script: "build:bundle" 20 | -------------------------------------------------------------------------------- /packages/test/README.md: -------------------------------------------------------------------------------- 1 | # ![🛌🎣 Rest Hooks Testing](https://raw.githubusercontent.com/coinbase/rest-hooks/master/packages/rest-hooks/rest_hooks_logo_and_text.svg?sanitize=true) 2 | 3 |
4 | 5 | **[🏁Guides](https://resthooks.io/docs/guides/storybook)**  |  [🏁API Reference](https://resthooks.io/docs/api/MockProvider) 6 | 7 |
8 | 9 | ## Features 10 | 11 | - [x] Mocking for Storybook 12 | - [x] Fixtures for component tests 13 | - [x] Hook unit testing utility 14 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/actions.js: -------------------------------------------------------------------------------- 1 | export { getCommits } from './modules/commits'; 2 | export { getIssues } from './modules/issues'; 3 | export { getLabels } from './modules/labels'; 4 | export { getMilestones } from './modules/milestones'; 5 | export { getPullRequests } from './modules/pull-requests'; 6 | export { setRepo } from './modules/repos'; 7 | 8 | export const ADD_ENTITIES = 'ADD_ENTITIES'; 9 | export const addEntities = (entities) => ({ 10 | type: ADD_ENTITIES, 11 | payload: entities 12 | }); 13 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/__tests__/isOnline.ts: -------------------------------------------------------------------------------- 1 | import isOnline from '../isOnline'; 2 | 3 | describe('isOnline', () => { 4 | it('should be true when navigator is not set', () => { 5 | const oldValue = navigator.onLine; 6 | Object.defineProperty(navigator, 'onLine', { 7 | value: undefined, 8 | writable: true, 9 | }); 10 | expect(isOnline()).toBe(true); 11 | Object.defineProperty(navigator, 'onLine', { 12 | value: oldValue, 13 | writable: false, 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/test/src/MockProvider.tsx: -------------------------------------------------------------------------------- 1 | import { __INTERNAL__ } from 'rest-hooks'; 2 | 3 | import React from 'react'; 4 | 5 | import mockState, { Fixture } from './mockState'; 6 | const { StateContext } = __INTERNAL__; 7 | 8 | export default function MockProvider({ 9 | children, 10 | results, 11 | }: { 12 | children: React.ReactChild; 13 | results: Fixture[]; 14 | }) { 15 | const state = mockState(results); 16 | return ( 17 | {children} 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/actionTypes.ts: -------------------------------------------------------------------------------- 1 | export const FETCH_TYPE = 'rest-hooks/fetch' as const; 2 | export const RECEIVE_TYPE = 'rest-hooks/receive' as const; 3 | export const RECEIVE_MUTATE_TYPE = 'rest-hooks/rpc' as const; 4 | export const RECEIVE_DELETE_TYPE = 'rest-hooks/purge' as const; 5 | export const RESET_TYPE = 'rest-hooks/reset' as const; 6 | export const SUBSCRIBE_TYPE = 'rest-hooks/subscribe' as const; 7 | export const UNSUBSCRIBE_TYPE = 'rest-hook/unsubscribe' as const; 8 | export const INVALIDATE_TYPE = 'rest-hooks/invalidate' as const; 9 | -------------------------------------------------------------------------------- /packages/use-enhanced-reducer/src/types.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface MiddlewareAPI< 4 | R extends React.Reducer = React.Reducer 5 | > { 6 | getState: () => React.ReducerState; 7 | dispatch: Dispatch; 8 | } 9 | 10 | export type Dispatch> = ( 11 | action: React.ReducerAction, 12 | ) => Promise; 13 | 14 | export type Middleware = >({ 15 | dispatch, 16 | }: MiddlewareAPI) => (next: Dispatch) => Dispatch; 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | Fixes # . 6 | 7 | ### Motivation 8 | 11 | 12 | ### Solution 13 | 16 | 17 | ### Open questions 18 | 21 | -------------------------------------------------------------------------------- /packages/normalizr/typescript-tests/array.ts: -------------------------------------------------------------------------------- 1 | import { denormalize, normalize, schema } from '../src' 2 | 3 | const data = [{ id: '123', name: 'Jim' }, { id: '456', name: 'Jane' }]; 4 | const userSchema = new schema.Entity('users'); 5 | 6 | const userListSchema = new schema.Array(userSchema); 7 | const normalizedData = normalize(data, userListSchema); 8 | 9 | const userListSchemaAlt = [userSchema]; 10 | const normalizedDataAlt = normalize(data, userListSchemaAlt); 11 | 12 | const denormalizedData = denormalize(normalizedData.result, userListSchema, normalizedData.entities); 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | docusaurus: 5 | build: . 6 | ports: 7 | - 3000:3000 8 | - 35729:35729 9 | volumes: 10 | - ./docs:/app/docs 11 | - ./website/blog:/app/website/blog 12 | - ./website/core:/app/website/core 13 | - ./website/i18n:/app/website/i18n 14 | - ./website/pages:/app/website/pages 15 | - ./website/static:/app/website/static 16 | - ./website/sidebars.json:/app/website/sidebars.json 17 | - ./website/siteConfig.js:/app/website/siteConfig.js 18 | working_dir: /app/website 19 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/README.md: -------------------------------------------------------------------------------- 1 | # Redux 2 | 3 | This is a simple example of using Normalizr with Redux and Redux-Thunk. The command-line utility allows you to pull some data from public GitHub repos and browse/display the normalized data as saved to the Redux state tree. 4 | 5 | ![redux example in use](/examples/redux/usage.gif) 6 | 7 | ## Running 8 | 9 | From this directory, run the following and follow the on-screen options: 10 | 11 | ```sh 12 | # from the root directory: 13 | yarn # or npm install 14 | # from this directory: 15 | yarn # or npm install 16 | npm run start 17 | ``` 18 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/hooks/useResetter.ts: -------------------------------------------------------------------------------- 1 | import { DispatchContext } from 'rest-hooks/react-integration/context'; 2 | import { RESET_TYPE } from 'rest-hooks/actionTypes'; 3 | import { useContext, useCallback } from 'react'; 4 | 5 | /** Returns a function to completely clear the cache of all entries */ 6 | export default function useResetter(): () => void { 7 | const dispatch = useContext(DispatchContext); 8 | 9 | const resetDispatcher = useCallback(() => { 10 | dispatch({ 11 | type: RESET_TYPE, 12 | }); 13 | }, [dispatch]); 14 | 15 | return resetDispatcher; 16 | } 17 | -------------------------------------------------------------------------------- /packages/normalizr/typescript-tests/array_schema.ts: -------------------------------------------------------------------------------- 1 | import { denormalize, normalize, schema } from '../src' 2 | 3 | const data = [{ id: 1, type: 'admin' }, { id: 2, type: 'user' }]; 4 | const userSchema = new schema.Entity('users'); 5 | const adminSchema = new schema.Entity('admins'); 6 | 7 | const myArray = new schema.Array( 8 | { 9 | admins: adminSchema, 10 | users: userSchema 11 | }, 12 | (input, parent, key) => `${input.type}s` 13 | ); 14 | 15 | const normalizedData = normalize(data, myArray); 16 | 17 | const denormalizedData = denormalize(normalizedData.result, myArray, normalizedData.entities); 18 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "normalizr-redux-example", 3 | "version": "0.0.0", 4 | "description": "And example of using Normalizr with Redux", 5 | "main": "index.js", 6 | "author": "Paul Armstrong", 7 | "license": "MIT", 8 | "private": true, 9 | "scripts": { 10 | "start": "babel-node ./" 11 | }, 12 | "dependencies": { 13 | "babel-cli": "^6.18.0", 14 | "babel-preset-es2015": "^6.18.0", 15 | "babel-preset-stage-1": "^6.16.0", 16 | "github": "^14.0.0", 17 | "inquirer": "^6.3.1", 18 | "redux": "^4.0.5", 19 | "redux-thunk": "^2.1.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import useFetcher from './useFetcher'; 2 | import useCache from './useCache'; 3 | import useRetrieve from './useRetrieve'; 4 | import useResource from './useResource'; 5 | import useSubscription from './useSubscription'; 6 | import useMeta from './useMeta'; 7 | import useError from './useError'; 8 | import useInvalidator from './useInvalidator'; 9 | import useResetter from './useResetter'; 10 | 11 | export { 12 | useFetcher, 13 | useCache, 14 | useError, 15 | useRetrieve, 16 | useResource, 17 | useSubscription, 18 | useMeta, 19 | useInvalidator, 20 | useResetter, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/hooks/useCache.ts: -------------------------------------------------------------------------------- 1 | import { StateContext } from 'rest-hooks/react-integration/context'; 2 | import { ReadShape, ParamsFromShape } from 'rest-hooks/resource'; 3 | import { useDenormalized } from 'rest-hooks/state/selectors'; 4 | 5 | import { useContext } from 'react'; 6 | 7 | /** Access a resource if it is available. */ 8 | export default function useCache< 9 | Shape extends Pick, 'getFetchKey' | 'schema'> 10 | >(fetchShape: Shape, params: ParamsFromShape | null) { 11 | const state = useContext(StateContext); 12 | return useDenormalized(fetchShape, params, state)[0]; 13 | } 14 | -------------------------------------------------------------------------------- /packages/normalizr/examples/github/README.md: -------------------------------------------------------------------------------- 1 | # Normalizing GitHub Issues 2 | 3 | This is a barebones example for node to illustrate how normalizing the GitHub Issues API endpoint could work. 4 | 5 | ## Running 6 | 7 | ```sh 8 | # from the root directory: 9 | yarn 10 | # from this directory: 11 | ../../node_modules/.bin/babel-node ./index.js 12 | ``` 13 | 14 | ## Files 15 | 16 | * [index.js](/examples/github/index.js): Pulls live data from the GitHub API for this project's issues and normalizes the JSON. 17 | * [output.json](/examples/github/output.json): A sample of the normalized output. 18 | * [schema.js](/examples/github/schema.js): The schema used to normalize the GitHub issues. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **React version** (e.g., 16.8.5) 11 | 12 | **Concurrent mode** yes/no 13 | 14 | **Describe the bug** 15 | A clear and concise description of what the bug is. 16 | 17 | **To Reproduce** 18 | Steps to reproduce the behavior: 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Additional context** 28 | Any console errors? What does the networking inspector show? 29 | -------------------------------------------------------------------------------- /website/static/img/dice-d6-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/static/scripts/sidebarScroll.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', () => { 2 | // Find the active nav item in the sidebar 3 | const item = document.getElementsByClassName('navListItemActive')[0]; 4 | if (!item) { return; } 5 | const bounding = item.getBoundingClientRect(); 6 | if ( 7 | bounding.top >= 0 && 8 | bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) 9 | ) { 10 | // Already visible. Do nothing. 11 | } else { 12 | // Not visible. Scroll sidebar. 13 | item.scrollIntoView({block: 'center', inline: 'nearest'}); 14 | document.body.scrollTop = document.documentElement.scrollTop = 0; 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /website/static/img/coinbase-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | export function mockEventHandlers() { 2 | const eventMap: { 3 | [k: string]: Set; 4 | } = {}; 5 | window.addEventListener = jest.fn((event, cb) => { 6 | if (!(event in eventMap)) { 7 | eventMap[event] = new Set(); 8 | } 9 | eventMap[event].add(cb as any); 10 | }); 11 | window.removeEventListener = jest.fn((event, cb) => { 12 | if (event in eventMap) { 13 | eventMap[event].delete(cb as any); 14 | } 15 | }); 16 | const triggerEvent = (name: string, event: Event) => { 17 | if (eventMap[name] === undefined) return; 18 | for (const cb of eventMap[name]) { 19 | cb(event); 20 | } 21 | }; 22 | return triggerEvent; 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /docs/ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## Next 4 | 5 | - [x] Schema to build selectors 6 | - [x] DELETE purges optimistically 7 | - [ ] Pluggable garbage collection 8 | - [x] Easy storybook data mocking 9 | 10 | ## Soon 11 | 12 | - [x] Optimistic query update on create 13 | - [x] Optional redux-integration 14 | - [x] Polling based subscriptions 15 | - [ ] Automatic batching 16 | 17 | ## Future 18 | 19 | - [ ] Support https://developers.google.com/web/updates/2017/09/abortable-fetch 20 | - [ ] CLI Resource stub generator 21 | - [ ] Server Side Rendering 22 | - [ ] Configurable cache presistance (PWA) 23 | - [ ] Immutable.js support as optional peerdep 24 | - [ ] Symbol as unique identifier instead of urlRoot 25 | - [ ] GraphqlResource 26 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/hooks/useMeta.ts: -------------------------------------------------------------------------------- 1 | import { FetchShape } from 'rest-hooks/resource'; 2 | import { StateContext } from 'rest-hooks/react-integration/context'; 3 | import { selectMeta } from 'rest-hooks/state/selectors'; 4 | import { useContext, useMemo } from 'react'; 5 | 6 | /** Gets meta for a url. */ 7 | export default function useMeta>( 8 | { getFetchKey }: Pick, 'getFetchKey'>, 9 | params: Params | null, 10 | ) { 11 | const state = useContext(StateContext); 12 | const url = params ? getFetchKey(params) : ''; 13 | 14 | return useMemo(() => { 15 | if (!url) return null; 16 | return selectMeta(state, url); 17 | }, [url, state]); 18 | } 19 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/modules/repos.js: -------------------------------------------------------------------------------- 1 | export const STATE_KEY = 'repo'; 2 | 3 | export default function reducer(state = {}, action) { 4 | switch (action.type) { 5 | case Action.SET_REPO: 6 | return { 7 | ...state, 8 | ...action.payload 9 | }; 10 | 11 | default: 12 | return state; 13 | } 14 | } 15 | 16 | const Action = { 17 | SET_REPO: 'SET_REPO' 18 | }; 19 | 20 | export const setRepo = (slug) => { 21 | const [owner, repo] = slug.split('/'); 22 | return { 23 | type: Action.SET_REPO, 24 | payload: { owner, repo } 25 | }; 26 | }; 27 | 28 | export const selectOwner = (state) => state[STATE_KEY].owner; 29 | export const selectRepo = (state) => state[STATE_KEY].repo; 30 | -------------------------------------------------------------------------------- /website/static/css/code-block-buttons.css: -------------------------------------------------------------------------------- 1 | /* "Copy" code block button */ 2 | pre { 3 | position: relative; 4 | } 5 | 6 | pre .btnIcon { 7 | position: absolute; 8 | top: 4px; 9 | z-index: 2; 10 | cursor: pointer; 11 | border: 1px solid transparent; 12 | padding: 0; 13 | color: #fff; 14 | background-color: transparent; 15 | height: 30px; 16 | transition: all .25s ease-out; 17 | } 18 | 19 | pre .btnIcon:hover { 20 | text-decoration: none; 21 | } 22 | 23 | .btnIcon__body { 24 | align-items: center; 25 | display: flex; 26 | } 27 | 28 | .btnIcon svg { 29 | fill: currentColor; 30 | margin-right: .4em; 31 | } 32 | 33 | .btnIcon__label { 34 | font-size: 11px; 35 | } 36 | 37 | .btnClipboard { 38 | right: 10px; 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.nodePath": "node_modules", 3 | "typescript.format.enable": true, 4 | "typescript.validate.enable": true, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 7 | }, 8 | "[typescript]": { 9 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 10 | }, 11 | "[typescriptreact]": { 12 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 13 | }, 14 | "eslint.validate": [ 15 | "javascript", 16 | "javascriptreact", 17 | "typescript", 18 | "typescriptreact" 19 | ], 20 | "typescript.tsdk": "node_modules/typescript/lib", 21 | "editor.defaultFormatter": "esbenp.prettier-vscode", 22 | "editor.codeActionsOnSave": { 23 | "source.fixAll.eslint": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/normalizr/src/schemas/Union.js: -------------------------------------------------------------------------------- 1 | import PolymorphicSchema from './Polymorphic'; 2 | 3 | export default class UnionSchema extends PolymorphicSchema { 4 | constructor(definition, schemaAttribute) { 5 | if (!schemaAttribute) { 6 | throw new Error( 7 | 'Expected option "schemaAttribute" not found on UnionSchema.', 8 | ); 9 | } 10 | super(definition, schemaAttribute); 11 | } 12 | 13 | normalize(input, parent, key, visit, addEntity, visitedEntities) { 14 | return this.normalizeValue( 15 | input, 16 | parent, 17 | key, 18 | visit, 19 | addEntity, 20 | visitedEntities, 21 | ); 22 | } 23 | 24 | denormalize(input, unvisit) { 25 | return this.denormalizeValue(input, unvisit); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/modules/users.js: -------------------------------------------------------------------------------- 1 | import { ADD_ENTITIES } from '../actions'; 2 | import { denormalize } from '../../../../../src'; 3 | import { user } from '../../api/schema'; 4 | 5 | export const STATE_KEY = 'users'; 6 | 7 | export default function reducer(state = {}, action) { 8 | switch (action.type) { 9 | case ADD_ENTITIES: 10 | return Object.entries(action.payload.users).reduce((mergedUsers, [id, user]) => { 11 | return { 12 | ...mergedUsers, 13 | [id]: { 14 | ...(mergedUsers[id] || {}), 15 | ...user 16 | } 17 | }; 18 | }, state); 19 | 20 | default: 21 | return state; 22 | } 23 | } 24 | 25 | export const selectHydrated = (state, id) => denormalize(id, user, state); 26 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/selectors/getEntityPath.ts: -------------------------------------------------------------------------------- 1 | import { isEntity } from 'rest-hooks/resource/types'; 2 | import { Schema, schemas } from 'rest-hooks/resource/normal'; 3 | 4 | export default function getEntityPath(schema: Schema): string[] | false { 5 | if ( 6 | isEntity(schema) || 7 | schema instanceof schemas.Array || 8 | Array.isArray(schema) 9 | ) { 10 | return []; 11 | } 12 | const o: Record = 13 | schema instanceof schemas.Object ? (schema as any).schema : schema; 14 | for (const k in o) { 15 | if (!o[k]) continue; 16 | const path = getEntityPath(o[k]); 17 | if (path !== false) { 18 | // mutation is ok because there is only one path 19 | path.unshift(k); 20 | return path; 21 | } 22 | } 23 | return false; 24 | } 25 | -------------------------------------------------------------------------------- /website/static/img/spa-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/provider/__tests__/__snapshots__/provider.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` should change state 1`] = ` 4 | Object { 5 | "entities": Object { 6 | "http://test.com/article-cooler/": Object { 7 | "5": CoolerArticleResource { 8 | "author": null, 9 | "content": "more things here", 10 | "id": 5, 11 | "tags": Array [], 12 | "title": "hi", 13 | }, 14 | }, 15 | }, 16 | "indexes": Object {}, 17 | "meta": Object { 18 | "http://test.com/article-cooler/5": Object { 19 | "date": 50, 20 | "expiresAt": 55, 21 | }, 22 | }, 23 | "optimistic": Array [], 24 | "results": Object { 25 | "http://test.com/article-cooler/5": "5", 26 | }, 27 | } 28 | `; 29 | -------------------------------------------------------------------------------- /website/static/img/space-shuttle-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/guides/multi-pk.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Multi-column primary key 3 | --- 4 | Sometimes you have a resource that doesn't have its own primary key. This is typically 5 | found in `join tables` that express `many-to-many` relationships. 6 | 7 | Since the pk() method must return either a number, string or undefined, make sure to 8 | do a simple serialization. A simple join on the values should work. Be care to 9 | make sure your join value can't be a part of the id. 10 | 11 | ```typescript 12 | export class VoteResource extends Resource { 13 | readonly userId: number | undefined = undefined; 14 | readonly postId: number | undefined = undefined; 15 | readonly createdAt: string = '1900-01-01T01:01:01Z'; 16 | 17 | pk() { 18 | return [this.userId, this.postId].join(','); 19 | } 20 | static urlRoot = 'https://example.com/votes/'; 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /packages/normalizr/examples/github/schema.js: -------------------------------------------------------------------------------- 1 | import { schema } from '../../src'; 2 | 3 | export const user = new schema.Entity('users'); 4 | 5 | export const label = new schema.Entity('labels'); 6 | 7 | export const milestone = new schema.Entity('milestones', { 8 | creator: user 9 | }); 10 | 11 | export const issue = new schema.Entity('issues', { 12 | assignee: user, 13 | assignees: [user], 14 | labels: label, 15 | milestone, 16 | user 17 | }); 18 | 19 | export const pullRequest = new schema.Entity('pullRequests', { 20 | assignee: user, 21 | assignees: [user], 22 | labels: label, 23 | milestone, 24 | user 25 | }); 26 | 27 | export const issueOrPullRequest = new schema.Array( 28 | { 29 | issues: issue, 30 | pullRequests: pullRequest 31 | }, 32 | (entity) => (entity.pull_request ? 'pullRequests' : 'issues') 33 | ); 34 | -------------------------------------------------------------------------------- /docs/api/NetworkManager.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NetworkManager implements Manager 3 | sidebar_label: NetworkManager 4 | --- 5 | NetworkManager orchestrates asynchronous fetches. By keeping track of all in-flight requests 6 | it is able to dedupe identical requests if they are made using the throttle flag. 7 | 8 | ## constructor(dataExpiryLength: number = 60000, errorExpiryLength: number = 1000) 9 | 10 | Arguments represent the default time (in miliseconds) before a resource is considered 'stale'. 11 | 12 | ## Consumed Actions 13 | 14 | - 'rest-hooks/fetch' 15 | 16 | Will initiate network request and then dispatch upon completion. 17 | 18 | ## Processed Actions 19 | 20 | - 'rest-hooks/purge' 21 | - 'rest-hooks/rpc' 22 | - 'rest-hooks/receive' 23 | 24 | Marks request as complete. 25 | 26 | ## Dispatched Actions 27 | 28 | - 'rest-hooks/purge' 29 | - 'rest-hooks/rpc' 30 | - 'rest-hooks/receive' 31 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/guides/multi-pk.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Multi-column primary key 3 | id: version-1.6.9-multi-pk 4 | original_id: multi-pk 5 | --- 6 | Sometimes you have a resource that doesn't have its own primary key. This is typically 7 | found in `join tables` that express `many-to-many` relationships. 8 | 9 | Since the pk() method must return either a number, string or null, make sure to 10 | do a simple serialization. A simple join on the values should work. Be care to 11 | make sure your join value can't be a part of the id. 12 | 13 | ```typescript 14 | export class VoteResource extends Resource { 15 | readonly userId: number | null = null; 16 | readonly postId: number | null = null; 17 | readonly createdAt: string = '1900-01-01T01:01:01Z'; 18 | 19 | pk() { 20 | return [this.userId, this.postId].join(','); 21 | } 22 | static urlRoot = 'https://example.com/votes/'; 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { ActionTypes } from 'rest-hooks/types'; 3 | import { initialState } from 'rest-hooks/state/reducer'; 4 | 5 | export const StateContext = createContext(initialState); 6 | 7 | export const DispatchContext = createContext((value: ActionTypes) => { 8 | /* istanbul ignore next */ 9 | if (process.env.NODE_ENV !== 'production') { 10 | console.error( 11 | 'It appears you are trying to use Rest Hooks without a provider.\nFollow instructions: https://resthooks.io/docs/getting-started/installation#add-provider-at-top-level-component', 12 | ); 13 | /* istanbul ignore next */ 14 | if (process.env.NODE_ENV === 'test') { 15 | console.error( 16 | 'If you are trying to test: https://resthooks.io/docs/guides/unit-testing-hooks', 17 | ); 18 | } 19 | } 20 | return Promise.resolve(); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/resource/publicTypes.ts: -------------------------------------------------------------------------------- 1 | import { FetchShape } from './shapes'; 2 | 3 | /** Sets a FetchShape's Param type. 4 | * Useful to constrain acceptable params (second arg) in hooks like useResource(). 5 | * 6 | * @param [Shape] FetchShape to act upon 7 | * @param [Params] what to set the Params to 8 | */ 9 | export type SetShapeParams< 10 | Shape extends FetchShape, 11 | Params extends Readonly 12 | > = { 13 | [K in keyof Shape]: Shape[K]; 14 | } & 15 | (Shape['fetch'] extends (first: any, ...rest: infer Args) => infer Return 16 | ? { fetch: (first: Params, ...rest: Args) => Return } 17 | : never); 18 | 19 | /** Get the Params type for a given Shape */ 20 | export type ParamsFromShape = S extends { 21 | fetch: (first: infer A, ...rest: any) => any; 22 | } 23 | ? A 24 | : S extends { getFetchKey: (first: infer A, ...rest: any) => any } 25 | ? A 26 | : never; 27 | -------------------------------------------------------------------------------- /packages/normalizr/examples/relationships/README.md: -------------------------------------------------------------------------------- 1 | # Dealing with Relationships 2 | 3 | Occasionally, it is useful to have all one-to-one, one-to-many, and many-to-many relationship data on entities. Normalizr does not handle this automatically, but this example shows a simple way of adding relationship handling on a special-case basis. 4 | 5 | ## Running 6 | 7 | ```sh 8 | # from the root directory: 9 | yarn 10 | # from this directory: 11 | ../../node_modules/.bin/babel-node ./index.js 12 | ``` 13 | 14 | ## Files 15 | 16 | * [index.js](/examples/relationships/index.js): Pulls live data from the GitHub API for this project's issues and normalizes the JSON. 17 | * [input.json](/examples/relationships/input.json): The raw JSON data before normalization. 18 | * [output.json](/examples/relationships/output.json): The normalized output. 19 | * [schema.js](/examples/relationships/schema.js): The schema used to normalize the GitHub issues. 20 | -------------------------------------------------------------------------------- /website/versioned_docs/version-3.0/guides/multi-pk.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Multi-column primary key 3 | id: version-3.0-multi-pk 4 | original_id: multi-pk 5 | --- 6 | Sometimes you have a resource that doesn't have its own primary key. This is typically 7 | found in `join tables` that express `many-to-many` relationships. 8 | 9 | Since the pk() method must return either a number, string or undefined, make sure to 10 | do a simple serialization. A simple join on the values should work. Be care to 11 | make sure your join value can't be a part of the id. 12 | 13 | ```typescript 14 | export class VoteResource extends Resource { 15 | readonly userId: number | undefined = undefined; 16 | readonly postId: number | undefined = undefined; 17 | readonly createdAt: string = '1900-01-01T01:01:01Z'; 18 | 19 | pk() { 20 | return [this.userId, this.postId].join(','); 21 | } 22 | static urlRoot = 'https://example.com/votes/'; 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /packages/test/src/managers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NetworkManager, 3 | FetchAction, 4 | ReceiveAction, 5 | Dispatch, 6 | PollingSubscription, 7 | } from 'rest-hooks'; 8 | 9 | import { act } from '@testing-library/react-hooks'; 10 | 11 | export class MockNetworkManager extends NetworkManager { 12 | handleFetch(action: FetchAction, dispatch: Dispatch) { 13 | const mockDispatch: typeof dispatch = (v: any) => { 14 | act(() => { 15 | dispatch(v); 16 | }); 17 | return Promise.resolve(); 18 | }; 19 | return super.handleFetch(action, mockDispatch); 20 | } 21 | 22 | handleReceive(action: ReceiveAction) { 23 | act(() => { 24 | super.handleReceive(action); 25 | }); 26 | } 27 | } 28 | 29 | export class MockPollingSubscription extends PollingSubscription { 30 | /** Trigger request for latest resource */ 31 | protected update() { 32 | act(() => { 33 | super.update(); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/test/src/mockState.ts: -------------------------------------------------------------------------------- 1 | import { ReadShape, Schema, reducer, __INTERNAL__ } from 'rest-hooks'; 2 | const { initialState } = __INTERNAL__; 3 | 4 | export interface Fixture { 5 | request: ReadShape; 6 | params: object; 7 | result: object | string | number; 8 | } 9 | 10 | export default function mockInitialState< 11 | S extends Schema, 12 | Params extends Readonly = Readonly, 13 | Body extends Readonly | void = Readonly | undefined 14 | >(results: Fixture[]) { 15 | const now = Date.now(); 16 | const mockState = results.reduce((acc, { request, params, result }) => { 17 | const { schema, getFetchKey } = request; 18 | const url = getFetchKey(params); 19 | return reducer(acc, { 20 | type: 'rest-hooks/receive', 21 | payload: result, 22 | meta: { schema, url, date: now, expiresAt: now * 2 }, 23 | }); 24 | }, initialState); 25 | return mockState; 26 | } 27 | -------------------------------------------------------------------------------- /packages/normalizr/docs/jsonapi.md: -------------------------------------------------------------------------------- 1 | # Normalizr and JSONAPI 2 | 3 | If you're using JSONAPI, you're ahead of the curve, but also in a bit of a tough spot. JSONAPI is a great spec, but doesn't play nicely with the way that you want to manage data in Redux/Flux style state management applications. 4 | 5 | Just as well, Normalizr was not written for JSONAPI and really doesn't work well. Instead, stop what you're doing now and check out some of the other great libraries and packages available that are written specifically for normalizing JSONAPI data\*: 6 | 7 | - [stevenpetryk/jsonapi-normalizer](https://github.com/stevenpetryk/jsonapi-normalizer) 8 | - [yury-dymov/json-api-normalizer](https://github.com/yury-dymov/json-api-normalizer) 9 | - [JSONAPI client libraries](http://jsonapi.org/implementations/#client-libraries-javascript) 10 | 11 | **Note:** These are in no particular order. Review all libraries on your own before deciding which is best for your particular use-case. 12 | -------------------------------------------------------------------------------- /packages/normalizr/examples/github/index.js: -------------------------------------------------------------------------------- 1 | import * as schema from './schema'; 2 | import fs from 'fs'; 3 | import https from 'https'; 4 | import { normalize } from '../../src'; 5 | import path from 'path'; 6 | 7 | let data = ''; 8 | const request = https.request( 9 | { 10 | host: 'api.github.com', 11 | path: '/repos/ntucker/normalizr/issues', 12 | method: 'get', 13 | headers: { 14 | 'user-agent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)' 15 | } 16 | }, 17 | (res) => { 18 | res.on('data', (d) => { 19 | data += d; 20 | }); 21 | 22 | res.on('end', () => { 23 | const normalizedData = normalize(JSON.parse(data), schema.issueOrPullRequest); 24 | const out = JSON.stringify(normalizedData, null, 2); 25 | fs.writeFileSync(path.resolve(__dirname, './output.json'), out); 26 | }); 27 | 28 | res.on('error', (e) => { 29 | console.log(e); 30 | }); 31 | } 32 | ); 33 | 34 | request.end(); 35 | -------------------------------------------------------------------------------- /packages/normalizr/typescript-tests/github.ts: -------------------------------------------------------------------------------- 1 | import { normalize, schema } from '../src' 2 | 3 | const user = new schema.Entity('users'); 4 | 5 | const label = new schema.Entity('labels'); 6 | 7 | const milestone = new schema.Entity('milestones', { 8 | creator: user 9 | }); 10 | 11 | const issue = new schema.Entity('issues', { 12 | assignee: user, 13 | assignees: [user], 14 | labels: label, 15 | milestone, 16 | user 17 | }); 18 | 19 | const pullRequest = new schema.Entity('pullRequests', { 20 | assignee: user, 21 | assignees: [user], 22 | labels: label, 23 | milestone, 24 | user 25 | }); 26 | 27 | const issueOrPullRequest = new schema.Array( 28 | { 29 | issues: issue, 30 | pullRequests: pullRequest 31 | }, 32 | (entity: any) => (entity.pull_request ? 'pullRequests' : 'issues') 33 | ); 34 | 35 | const data = { 36 | /* ...*/ 37 | }; 38 | const normalizedData = normalize(data, issueOrPullRequest); 39 | console.log(normalizedData); 40 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/guides/jsonp.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cross-orgin requests with JSONP 3 | id: version-1.6.9-jsonp 4 | original_id: jsonp 5 | --- 6 | 7 | JSONP is a method for sending JSON data without worrying about cross-domain issues. This 8 | is sometimes needed when calling third-party APIs that don't come with appropriate 9 | [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) settings. 10 | 11 | ```tsx 12 | import jsonp from 'superagent-jsonp'; 13 | import { Resource } from 'rest-hooks'; 14 | 15 | export default class ArticleResource extends Resource { 16 | readonly id: number | null = null; 17 | readonly content: string = ''; 18 | 19 | pk() { 20 | return this.id; 21 | } 22 | static urlRoot = 'http://test.com/article/'; 23 | 24 | // OPERATIVE LINE HERE 25 | static fetchPlugin = jsonp; 26 | } 27 | ``` 28 | 29 | Using the [jsonp plugin](https://www.npmjs.com/package/superagent-jsonp) for superagent makes this quite easy. 30 | -------------------------------------------------------------------------------- /website/versioned_docs/version-3.0/guides/jsonp.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cross-orgin requests with JSONP 3 | id: version-3.0-jsonp 4 | original_id: jsonp 5 | --- 6 | 7 | JSONP is a method for sending JSON data without worrying about cross-domain issues. This 8 | is sometimes needed when calling third-party APIs that don't come with appropriate 9 | [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) settings. 10 | 11 | ```tsx 12 | import jsonp from 'superagent-jsonp'; 13 | import { Resource } from 'rest-hooks'; 14 | 15 | export default class ArticleResource extends Resource { 16 | readonly id: number | undefined = undefined; 17 | readonly content: string = ''; 18 | 19 | pk() { 20 | return this.id; 21 | } 22 | static urlRoot = 'http://test.com/article/'; 23 | 24 | // OPERATIVE LINE HERE 25 | static fetchPlugin = jsonp; 26 | } 27 | ``` 28 | 29 | Using the [jsonp plugin](https://www.npmjs.com/package/superagent-jsonp) for superagent makes this quite easy. 30 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/guides/auth.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Authentication 3 | id: version-1.6.9-auth 4 | original_id: auth 5 | --- 6 | All network requests are run through the `static fetchPlugin` optionally 7 | defined in your `Resource`. 8 | 9 | Here's an example using simple cookie auth: 10 | 11 | ```typescript 12 | import { Request } from 'rest-hooks'; 13 | 14 | class AuthdResource extends Resource { 15 | static fetchPlugin = (request: Request) => request.withCredentials(); 16 | } 17 | ``` 18 | 19 | You can also do more complex flows (like adding arbitrary headers) to 20 | the request. Every `fetchPlugin` will take in a [superagent](http://visionmedia.github.io/superagent/) 21 | `Request` and return a new `Request`. `Superagent` uses the builder 22 | pattern so this is quite easy. 23 | 24 | ## Code organization 25 | 26 | If much of your `Resources` share a similar auth mechanism, you might 27 | try extending from a base class that defines such common customizations. 28 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/selectors/__tests__/__snapshots__/useDenormalized.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`useDenormalized() List paginated results should referentially change when the result extends 1`] = ` 4 | Object { 5 | "results": Array [ 6 | PaginatedArticleResource { 7 | "author": null, 8 | "content": "", 9 | "id": 5, 10 | "tags": Array [], 11 | "title": "", 12 | }, 13 | PaginatedArticleResource { 14 | "author": null, 15 | "content": "", 16 | "id": 6, 17 | "tags": Array [], 18 | "title": "", 19 | }, 20 | PaginatedArticleResource { 21 | "author": null, 22 | "content": "", 23 | "id": 34, 24 | "tags": Array [], 25 | "title": "five", 26 | }, 27 | PaginatedArticleResource { 28 | "author": null, 29 | "content": "", 30 | "id": 5, 31 | "tags": Array [], 32 | "title": "", 33 | }, 34 | ], 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/NetworkManager.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: NetworkManager implements Manager 3 | sidebar_label: NetworkManager 4 | id: version-1.6.9-NetworkManager 5 | original_id: NetworkManager 6 | --- 7 | NetworkManager orchestrates asynchronous fetches. By keeping track of all in-flight requests 8 | it is able to dedupe identical requests if they are made using the throttle flag. 9 | 10 | ## constructor(dataExpiryLength: number = 60000, errorExpiryLength: number = 1000) 11 | 12 | Arguments represent the default time (in miliseconds) before a resource is considered 'stale'. 13 | 14 | ## Consumed Actions 15 | 16 | - 'rest-hooks/fetch' 17 | 18 | Will initiate network request and then dispatch upon completion. 19 | 20 | ## Processed Actions 21 | 22 | - 'rest-hooks/purge' 23 | - 'rest-hooks/rpc' 24 | - 'rest-hooks/receive' 25 | 26 | Marks request as complete. 27 | 28 | ## Dispatched Actions 29 | 30 | - 'rest-hooks/purge' 31 | - 'rest-hooks/rpc' 32 | - 'rest-hooks/receive' 33 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import commits, { STATE_KEY as COMMITS_STATE_KEY } from './modules/commits'; 3 | import issues, { STATE_KEY as ISSUES_STATE_KEY } from './modules/issues'; 4 | import labels, { STATE_KEY as LABELS_STATE_KEY } from './modules/labels'; 5 | import milestones, { STATE_KEY as MILESTONES_STATE_KEY } from './modules/milestones'; 6 | import pullRequests, { STATE_KEY as PULLREQUESTS_STATE_KEY } from './modules/pull-requests'; 7 | import repos, { STATE_KEY as REPO_STATE_KEY } from './modules/repos'; 8 | import users, { STATE_KEY as USERS_STATE_KEY } from './modules/users'; 9 | 10 | const reducer = combineReducers({ 11 | [COMMITS_STATE_KEY]: commits, 12 | [ISSUES_STATE_KEY]: issues, 13 | [LABELS_STATE_KEY]: labels, 14 | [MILESTONES_STATE_KEY]: milestones, 15 | [PULLREQUESTS_STATE_KEY]: pullRequests, 16 | [REPO_STATE_KEY]: repos, 17 | [USERS_STATE_KEY]: users 18 | }); 19 | 20 | export default reducer; 21 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/PollingSubscription.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: PollingSubscription implements Subscription 3 | sidebar_label: PollingSubscription 4 | hide_title: true 5 | id: version-1.6.9-PollingSubscription 6 | original_id: PollingSubscription 7 | --- 8 | 9 | # PollingSubscription implements [Subscription](./SubscriptionManager.md) 10 | 11 | Will dispatch a `fetch` action at the minimum interval of all subscriptions to this 12 | resource. 13 | 14 | ```tsx 15 | import { SubscriptionManager, PollingSubscription, RestProvider } from 'rest-hooks'; 16 | import ReactDOM from 'react-dom'; 17 | 18 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 19 | 20 | ReactDOM.render( 21 | 22 | 23 | , 24 | document.body 25 | ); 26 | ``` 27 | 28 | ## Dispatched Actions 29 | 30 | - 'rest-hooks/fetch' 31 | 32 | > #### Note: 33 | > 34 | > This is already used by `RestProvider` by default. 35 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/api/PollingSubscription.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: PollingSubscription implements Subscription 3 | sidebar_label: PollingSubscription 4 | hide_title: true 5 | id: version-2.0-PollingSubscription 6 | original_id: PollingSubscription 7 | --- 8 | 9 | # PollingSubscription implements [Subscription](./SubscriptionManager.md) 10 | 11 | Will dispatch a `fetch` action at the minimum interval of all subscriptions to this 12 | resource. 13 | 14 | ```tsx 15 | import { SubscriptionManager, PollingSubscription, CacheProvider } from 'rest-hooks'; 16 | import ReactDOM from 'react-dom'; 17 | 18 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 19 | 20 | ReactDOM.render( 21 | 22 | 23 | , 24 | document.body 25 | ); 26 | ``` 27 | 28 | ## Dispatched Actions 29 | 30 | - 'rest-hooks/fetch' 31 | 32 | > #### Note: 33 | > 34 | > This is already used by `CacheProvider` by default. 35 | -------------------------------------------------------------------------------- /packages/legacy/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useRetrieve, 3 | useError, 4 | Schema, 5 | ReadShape, 6 | useDenormalized, 7 | __INTERNAL__, 8 | } from 'rest-hooks'; 9 | 10 | import { useContext } from 'react'; 11 | 12 | /** Ensure a resource is available; loading and error returned explicitly. */ 13 | export function useStatefulResource< 14 | Params extends Readonly, 15 | S extends Schema 16 | >(fetchShape: ReadShape, params: Params | null) { 17 | const maybePromise = useRetrieve(fetchShape, params); 18 | const state = useContext(__INTERNAL__.StateContext); 19 | const [denormalized, ready] = useDenormalized(fetchShape, params, state); 20 | 21 | const loading = Boolean( 22 | !__INTERNAL__.hasUsableData(ready, fetchShape) && 23 | maybePromise && 24 | typeof maybePromise.then === 'function', 25 | ); 26 | 27 | const error = useError(fetchShape, params, ready); 28 | 29 | return { 30 | data: denormalized, 31 | loading, 32 | error, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/api/schema.js: -------------------------------------------------------------------------------- 1 | import { schema } from '../../../../src'; 2 | 3 | export const user = new schema.Entity('users'); 4 | 5 | export const commit = new schema.Entity( 6 | 'commits', 7 | { 8 | author: user, 9 | committer: user 10 | }, 11 | { idAttribute: 'sha' } 12 | ); 13 | 14 | export const label = new schema.Entity('labels'); 15 | 16 | export const milestone = new schema.Entity('milestones', { 17 | creator: user 18 | }); 19 | 20 | export const issue = new schema.Entity('issues', { 21 | assignee: user, 22 | assignees: [user], 23 | labels: [label], 24 | milestone, 25 | user 26 | }); 27 | 28 | export const pullRequest = new schema.Entity('pullRequests', { 29 | assignee: user, 30 | assignees: [user], 31 | labels: [label], 32 | milestone, 33 | user 34 | }); 35 | 36 | export const issueOrPullRequest = new schema.Array( 37 | { 38 | issues: issue, 39 | pullRequests: pullRequest 40 | }, 41 | (entity) => (entity.pull_request ? 'pullRequests' : 'issues') 42 | ); 43 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/applyUpdatersToResults.ts: -------------------------------------------------------------------------------- 1 | import { UpdateFunction } from 'rest-hooks/types'; 2 | import { Normalize, Schema } from 'rest-hooks/resource'; 3 | 4 | type ResultStateFromUpdateFunctions< 5 | SourceSchema extends Schema, 6 | UpdateFunctions extends { 7 | [key: string]: UpdateFunction; 8 | } 9 | > = { [K in keyof UpdateFunctions]: any }; 10 | 11 | export default function applyUpdatersToResults< 12 | SourceSchema extends Schema, 13 | UpdateFunctions extends { 14 | [key: string]: UpdateFunction; 15 | } 16 | >( 17 | results: ResultStateFromUpdateFunctions, 18 | result: Normalize, 19 | updaters: UpdateFunctions | undefined, 20 | ) { 21 | if (!updaters) return results; 22 | return { 23 | ...results, 24 | ...Object.fromEntries( 25 | Object.entries(updaters).map(([fetchKey, updater]) => [ 26 | fetchKey, 27 | updater(result, results[fetchKey]), 28 | ]), 29 | ), 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/merge/isMergeable.ts: -------------------------------------------------------------------------------- 1 | // copied from https://github.com/TehShrike/is-mergeable-object 2 | 3 | export default function isMergeableObject(value: any) { 4 | return isNonNullObject(value) && !isSpecial(value); 5 | } 6 | 7 | function isNonNullObject(value: any) { 8 | return !!value && typeof value === 'object'; 9 | } 10 | 11 | function isSpecial(value: any) { 12 | const stringValue = Object.prototype.toString.call(value); 13 | 14 | return ( 15 | stringValue === '[object RegExp]' || 16 | stringValue === '[object Date]' || 17 | isReactElement(value) 18 | ); 19 | } 20 | 21 | // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25 22 | const canUseSymbol = typeof Symbol === 'function' && Symbol.for; 23 | /* istanbul ignore next */ 24 | const REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7; 25 | 26 | function isReactElement(value: any) { 27 | return value.$$typeof === REACT_ELEMENT_TYPE; 28 | } 29 | -------------------------------------------------------------------------------- /docs/api/PollingSubscription.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: PollingSubscription implements Subscription 3 | sidebar_label: PollingSubscription 4 | hide_title: true 5 | --- 6 | 7 | # PollingSubscription implements [Subscription](./SubscriptionManager.md) 8 | 9 | Will dispatch a `fetch` action at the minimum interval of all subscriptions to this 10 | resource. 11 | 12 | - Pauses when offline. 13 | - Immediately fetches when online status returns. 14 | - Immediately fetches any new subscriptions. 15 | 16 | ```tsx 17 | import { 18 | SubscriptionManager, 19 | PollingSubscription, 20 | CacheProvider, 21 | } from 'rest-hooks'; 22 | import ReactDOM from 'react-dom'; 23 | 24 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 25 | 26 | ReactDOM.render( 27 | 28 | 29 | , 30 | document.body, 31 | ); 32 | ``` 33 | 34 | ## Dispatched Actions 35 | 36 | - 'rest-hooks/fetch' 37 | 38 | > #### Note: 39 | > 40 | > This is already used by `CacheProvider` by default. 41 | -------------------------------------------------------------------------------- /docs/guides/computed-properties.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Computed Properties 3 | --- 4 | `Resource` classes are just normal classes, so any common derived data can just be added as 5 | getters to the class itself. 6 | 7 | ```typescript 8 | import { Resource } from 'rest-hooks'; 9 | 10 | class User extends Resource { 11 | readonly id: number | undefined = undefined; 12 | readonly firstName: string = ''; 13 | readonly lastName: string = ''; 14 | readonly username: string = ''; 15 | readonly email: string = ''; 16 | 17 | pk() { 18 | return this.id?.toString(); 19 | } 20 | 21 | static urlRoot = '/users/'; 22 | 23 | get fullName() { 24 | return `${this.firstName} ${this.lastName}`; 25 | } 26 | } 27 | ``` 28 | 29 | If the computations are expensive feel free to add some 30 | memoization. 31 | 32 | ```typescript 33 | import { Resource } from 'rest-hooks'; 34 | import { memoize } from 'lodash'; 35 | 36 | class User extends Resource { 37 | truelyExpensiveValue = memoize(() => { 38 | // compute that expensive thing! 39 | }); 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/hooks/useError.ts: -------------------------------------------------------------------------------- 1 | import { ReadShape, Schema } from 'rest-hooks/resource'; 2 | 3 | import useMeta from './useMeta'; 4 | 5 | type UseErrorReturn

= P extends null ? undefined : Error; 6 | 7 | /** Access a resource or error if failed to get it */ 8 | export default function useError< 9 | Params extends Readonly, 10 | S extends Schema 11 | >( 12 | fetchShape: ReadShape, 13 | params: Params | null, 14 | cacheReady: boolean, 15 | ): UseErrorReturn { 16 | const meta = useMeta(fetchShape, params); 17 | if (!params) return; 18 | if (!cacheReady) { 19 | if (!meta) return; 20 | if (!meta.error) { 21 | // this means we probably deleted the entity found in this result 22 | const err: any = new Error( 23 | `Resource not found in cache ${ 24 | params ? fetchShape.getFetchKey(params) : '' 25 | }`, 26 | ); 27 | err.status = 404; 28 | return err; 29 | } else { 30 | return meta.error as any; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/api/makeCacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: makeCacheProvider() 3 | --- 4 | 5 | ```typescript 6 | declare const makeCacheProvider: ( 7 | managers: Manager[], 8 | initialState?: State, 9 | ) => ({ children }: { children: React.ReactNode }) => JSX.Element; 10 | ``` 11 | 12 | Used to build a [\](./CacheProvider.md) for [makeRenderRestHook()](./makeRenderRestHook.md) 13 | 14 | ## Arguments 15 | 16 | ### managers 17 | 18 | [Manager](./Manager.md) 19 | 20 | ### initialState 21 | 22 | Can be used to prime the cache if test expects cache values to already be filled. 23 | 24 | ## Returns 25 | 26 | Simple wrapper component that only has child as prop. 27 | 28 | ```tsx 29 | const manager = new MockNetworkManager(); 30 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 31 | const Provider = makeCacheProvider([manager, subscriptionManager]); 32 | 33 | function renderRestHook(callback: () => T) { 34 | return renderHook(callback, { 35 | wrapper: ({ children }) => {children}, 36 | }); 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /website/static/css/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Graphik'; 3 | src: url('../font/Graphik-Regular-Web.woff2') format('woff2'); 4 | font-weight: 400; 5 | } 6 | 7 | @font-face { 8 | font-family: 'Graphik'; 9 | src: url('../font/Graphik-RegularItalic-Web.woff2') format('woff2'); 10 | font-weight: 400; 11 | font-style: italic; 12 | } 13 | 14 | @font-face { 15 | font-family: 'Graphik'; 16 | src: url('../font/Graphik-Medium-Web.woff2') format('woff2'); 17 | font-weight: 500; 18 | } 19 | 20 | @font-face { 21 | font-family: 'Graphik'; 22 | src: url('../font/Graphik-MediumItalic-Web.woff2') format('woff2'); 23 | font-weight: 500; 24 | font-style: italic; 25 | } 26 | 27 | @font-face { 28 | font-family: 'Graphik'; 29 | src: url('../font/Graphik-Semibold-Web.woff2') format('woff2'); 30 | font-weight: 700; 31 | } 32 | 33 | @font-face { 34 | font-family: 'Graphik'; 35 | src: url('../font/Graphik-SemiboldItalic-Web.woff2') format('woff2'); 36 | font-weight: 700; 37 | font-style: italic; 38 | } 39 | 40 | body { 41 | font-family: $sansFont; 42 | } 43 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/hooks/useInvalidator.ts: -------------------------------------------------------------------------------- 1 | import { useContext, useCallback, useRef } from 'react'; 2 | import { ReadShape, Schema } from 'rest-hooks/resource'; 3 | import { DispatchContext } from 'rest-hooks/react-integration/context'; 4 | import { INVALIDATE_TYPE } from 'rest-hooks/actionTypes'; 5 | 6 | /** Invalidate a certain item within the cache */ 7 | export default function useInvalidator< 8 | Params extends Readonly, 9 | S extends Schema 10 | >(fetchShape: ReadShape): (params: Params | null) => void { 11 | const dispatch = useContext(DispatchContext); 12 | const getFetchKeyRef = useRef(fetchShape.getFetchKey); 13 | getFetchKeyRef.current = fetchShape.getFetchKey; 14 | 15 | const invalidateDispatcher = useCallback( 16 | (params: Params | null) => { 17 | if (!params) return; 18 | dispatch({ 19 | type: INVALIDATE_TYPE, 20 | meta: { 21 | url: getFetchKeyRef.current(params), 22 | }, 23 | }); 24 | }, 25 | [dispatch], 26 | ); 27 | 28 | return invalidateDispatcher; 29 | } 30 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/resource/__tests__/__snapshots__/resource.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Resource getEntitySchema() merging should match snapshot 1`] = ` 4 | Object { 5 | "entities": Object { 6 | "http://test.com/article-cooler/": Object { 7 | "3": CoolerArticleResource { 8 | "author": "23", 9 | "content": "whatever", 10 | "id": 3, 11 | "tags": Array [], 12 | "title": "the next time", 13 | }, 14 | "5": CoolerArticleResource { 15 | "author": "23", 16 | "content": "whatever", 17 | "id": 5, 18 | "tags": Array [ 19 | "a", 20 | "best", 21 | "react", 22 | ], 23 | "title": "hi ho", 24 | }, 25 | }, 26 | "http://test.com/user/": Object { 27 | "23": UserResource { 28 | "email": "bob@bob.com", 29 | "id": 23, 30 | "isAdmin": false, 31 | "username": "charles", 32 | }, 33 | }, 34 | }, 35 | "indexes": Object {}, 36 | "result": Array [ 37 | "5", 38 | "3", 39 | ], 40 | } 41 | `; 42 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/hooks/useSelection.ts: -------------------------------------------------------------------------------- 1 | import { useContext, useMemo } from 'react'; 2 | import { StateContext } from 'rest-hooks/react-integration/context'; 3 | import { State } from 'rest-hooks/types'; 4 | 5 | /** Use selector to access part of state */ 6 | export default function useSelectionUnstable< 7 | Params extends Readonly | Readonly[], 8 | F extends (state: State, params: Params) => any 9 | >( 10 | select: F, 11 | params: Params | null, 12 | paramSerializer: (p: Params) => string, 13 | ): ReturnType | null { 14 | const state = useContext(StateContext); 15 | // TODO: if this is identical to before and render was triggered by state update, 16 | // we should short-circuit entire rest of render 17 | 18 | // params must be serialized in check 19 | // eslint-disable-next-line react-hooks/exhaustive-deps 20 | const resource = useMemo(() => params && select(state, params), [ 21 | // eslint-disable-next-line react-hooks/exhaustive-deps 22 | params && paramSerializer(params), 23 | select, 24 | state, 25 | ]); 26 | return resource; 27 | } 28 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/guides/computed-properties.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Computed Properties 3 | id: version-1.6.9-computed-properties 4 | original_id: computed-properties 5 | --- 6 | `Resource` classes are just normal classes, so any common derived data can just be added as 7 | getters to the class itself. 8 | 9 | ```typescript 10 | import { Resource } from 'rest-hooks'; 11 | 12 | class User extends Resource { 13 | readonly id: number | null = null; 14 | readonly firstName: string = ''; 15 | readonly lastName: string = ''; 16 | readonly username: string = ''; 17 | readonly email: string = ''; 18 | 19 | pk() { 20 | return this.id; 21 | } 22 | static urlRoot = '/users/'; 23 | 24 | get fullName() { 25 | return `${this.firstName} ${this.lastName}`; 26 | } 27 | } 28 | ``` 29 | 30 | If the computations are expensive feel free to add some 31 | memoization. 32 | 33 | ```typescript 34 | import { Resource } from 'rest-hooks'; 35 | import { memoize } from 'lodash'; 36 | 37 | class User extends Resource { 38 | truelyExpensiveValue = memoize(() => { 39 | // compute that expensive thing! 40 | }); 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /website/versioned_docs/version-3.0/guides/computed-properties.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Computed Properties 3 | id: version-3.0-computed-properties 4 | original_id: computed-properties 5 | --- 6 | `Resource` classes are just normal classes, so any common derived data can just be added as 7 | getters to the class itself. 8 | 9 | ```typescript 10 | import { Resource } from 'rest-hooks'; 11 | 12 | class User extends Resource { 13 | readonly id: number | undefined = undefined; 14 | readonly firstName: string = ''; 15 | readonly lastName: string = ''; 16 | readonly username: string = ''; 17 | readonly email: string = ''; 18 | 19 | pk() { 20 | return this.id; 21 | } 22 | static urlRoot = '/users/'; 23 | 24 | get fullName() { 25 | return `${this.firstName} ${this.lastName}`; 26 | } 27 | } 28 | ``` 29 | 30 | If the computations are expensive feel free to add some 31 | memoization. 32 | 33 | ```typescript 34 | import { Resource } from 'rest-hooks'; 35 | import { memoize } from 'lodash'; 36 | 37 | class User extends Resource { 38 | truelyExpensiveValue = memoize(() => { 39 | // compute that expensive thing! 40 | }); 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /packages/normalizr/typescript-tests/entity.ts: -------------------------------------------------------------------------------- 1 | import { denormalize, normalize, schema } from '../src' 2 | 3 | type User = { 4 | id_str: string; 5 | name: string; 6 | }; 7 | 8 | type Tweet = { 9 | id_str: string; 10 | url: string; 11 | user: User; 12 | }; 13 | 14 | const data = { 15 | /* ...*/ 16 | }; 17 | const user = new schema.Entity('users', {}, { idAttribute: 'id_str' }); 18 | const tweet = new schema.Entity( 19 | 'tweets', 20 | { user: user }, 21 | { 22 | idAttribute: 'id_str', 23 | // Apply everything from entityB over entityA, except for "favorites" 24 | mergeStrategy: (entityA, entityB) => ({ 25 | ...entityA, 26 | ...entityB, 27 | favorites: entityA.favorites 28 | }), 29 | // Remove the URL field from the entity 30 | processStrategy: (entity: Tweet, parent, key) => { 31 | const { url, ...entityWithoutUrl } = entity; 32 | return entityWithoutUrl; 33 | } 34 | } 35 | ); 36 | 37 | const normalizedData = normalize(data, tweet); 38 | const denormalizedData = denormalize(normalizedData.result, tweet, normalizedData.entities); 39 | 40 | const isTweet = tweet.key === 'tweets'; 41 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.0/api/PollingSubscription.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: PollingSubscription implements Subscription 3 | sidebar_label: PollingSubscription 4 | hide_title: true 5 | id: version-4.0-PollingSubscription 6 | original_id: PollingSubscription 7 | --- 8 | 9 | # PollingSubscription implements [Subscription](./SubscriptionManager.md) 10 | 11 | Will dispatch a `fetch` action at the minimum interval of all subscriptions to this 12 | resource. 13 | 14 | - Pauses when offline. 15 | - Immediately fetches when online status returns. 16 | - Immediately fetches any new subscriptions. 17 | 18 | ```tsx 19 | import { 20 | SubscriptionManager, 21 | PollingSubscription, 22 | CacheProvider, 23 | } from 'rest-hooks'; 24 | import ReactDOM from 'react-dom'; 25 | 26 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 27 | 28 | ReactDOM.render( 29 | 30 | 31 | , 32 | document.body, 33 | ); 34 | ``` 35 | 36 | ## Dispatched Actions 37 | 38 | - 'rest-hooks/fetch' 39 | 40 | > #### Note: 41 | > 42 | > This is already used by `CacheProvider` by default. 43 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/__tests__/__snapshots__/useCache.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`useCache() should select paginated results 1`] = ` 4 | Array [ 5 | PaginatedArticleResource { 6 | "author": null, 7 | "content": "the best things in life com efree", 8 | "id": 23, 9 | "tags": Array [ 10 | "one", 11 | "two", 12 | ], 13 | "title": "the first draft", 14 | }, 15 | PaginatedArticleResource { 16 | "author": null, 17 | "content": "the best things in life com efree", 18 | "id": 44, 19 | "tags": Array [ 20 | "hbh", 21 | "wew", 22 | ], 23 | "title": "the second book", 24 | }, 25 | PaginatedArticleResource { 26 | "author": null, 27 | "content": "the best things in life com efree", 28 | "id": 2, 29 | "tags": Array [ 30 | "free", 31 | "honey", 32 | ], 33 | "title": "the third novel", 34 | }, 35 | PaginatedArticleResource { 36 | "author": null, 37 | "content": "the best things in life com efree", 38 | "id": 643, 39 | "tags": Array [], 40 | "title": "a long time ago", 41 | }, 42 | ] 43 | `; 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.1/guides/computed-properties.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Computed Properties 3 | id: version-4.1-computed-properties 4 | original_id: computed-properties 5 | --- 6 | `Resource` classes are just normal classes, so any common derived data can just be added as 7 | getters to the class itself. 8 | 9 | ```typescript 10 | import { Resource } from 'rest-hooks'; 11 | 12 | class User extends Resource { 13 | readonly id: number | undefined = undefined; 14 | readonly firstName: string = ''; 15 | readonly lastName: string = ''; 16 | readonly username: string = ''; 17 | readonly email: string = ''; 18 | 19 | pk() { 20 | return this.id?.toString(); 21 | } 22 | 23 | static urlRoot = '/users/'; 24 | 25 | get fullName() { 26 | return `${this.firstName} ${this.lastName}`; 27 | } 28 | } 29 | ``` 30 | 31 | If the computations are expensive feel free to add some 32 | memoization. 33 | 34 | ```typescript 35 | import { Resource } from 'rest-hooks'; 36 | import { memoize } from 'lodash'; 37 | 38 | class User extends Resource { 39 | truelyExpensiveValue = memoize(() => { 40 | // compute that expensive thing! 41 | }); 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.1/api/makeCacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: makeCacheProvider() 3 | id: version-2.1-makeCacheProvider 4 | original_id: makeCacheProvider 5 | --- 6 | 7 | ```typescript 8 | declare const makeCacheProvider: ( 9 | managers: Manager[], 10 | initialState?: State, 11 | ) => ({ children }: { children: React.ReactNode }) => JSX.Element; 12 | ``` 13 | 14 | Used to build a [\](./CacheProvider.md) for [makeRenderRestHook()](./makeRenderRestHook.md) 15 | 16 | ## Arguments 17 | 18 | ### managers 19 | 20 | [Manager](./Manager.md) 21 | 22 | ### initialState 23 | 24 | Can be used to prime the cache if test expects cache values to already be filled. 25 | 26 | ## Returns 27 | 28 | Simple wrapper component that only has child as prop. 29 | 30 | ```tsx 31 | const manager = new MockNetworkManager(); 32 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 33 | const Provider = makeCacheProvider([manager, subscriptionManager]); 34 | 35 | function renderRestHook(callback: () => T) { 36 | return renderHook(callback, { 37 | wrapper: ({ children }) => {children}, 38 | }); 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/api/makeExternalCacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: makeExternalCacheProvider() 3 | --- 4 | 5 | ```typescript 6 | declare const makeExternalCacheProvider: ( 7 | managers: Manager[], 8 | initialState?: State, 9 | ) => ({ children }: { children: React.ReactNode }) => JSX.Element; 10 | ``` 11 | 12 | Used to build a [\](./ExternalCacheProvider.md) for [makeRenderRestHook()](./makeRenderRestHook.md) 13 | 14 | Internally constructs a redux store attaching the middlwares. 15 | 16 | ## Arguments 17 | 18 | ### managers 19 | 20 | [Manager](./Manager.md) 21 | 22 | ### initialState 23 | 24 | Can be used to prime the cache if test expects cache values to already be filled. 25 | 26 | ## Returns 27 | 28 | Simple wrapper component that only has child as prop. 29 | 30 | ```tsx 31 | const manager = new MockNetworkManager(); 32 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 33 | const Provider = makeExternalCacheProvider([manager, subscriptionManager]); 34 | 35 | function renderRestHook(callback: () => T) { 36 | return renderHook(callback, { 37 | wrapper: ({ children }) => {children}, 38 | }); 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/RestProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | id: version-1.6.9-RestProvider 4 | original_id: RestProvider 5 | --- 6 | Manages state, providing all context needed to use the hooks. Should be placed as high as possible 7 | in application tree as any usage of the hooks is only possible for components below the provider 8 | in the React tree. 9 | 10 | `index.tsx` 11 | 12 | ```tsx 13 | import { RestProvider } from 'rest-hooks'; 14 | import ReactDOM from 'react-dom'; 15 | 16 | ReactDOM.render( 17 | 18 | 19 | , 20 | document.body 21 | ); 22 | ``` 23 | 24 | ## initialState?: State 25 | 26 | ```typescript 27 | type State = Readonly<{ 28 | entities: Readonly<{ [k: string]: { [id: string]: T } | undefined }>; 29 | results: Readonly<{ [url: string]: unknown | PK[] | PK | undefined }>; 30 | meta: Readonly<{ 31 | [url: string]: { date: number; error?: Error; expiresAt: number }; 32 | }>; 33 | }>; 34 | ``` 35 | 36 | Instead of starting with an empty cache, you can provide your own initial state. This can 37 | be useful for testing, or rehydrating the cache state when using server side rendering. 38 | -------------------------------------------------------------------------------- /packages/normalizr/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dan Abramov, Paul Armstrong, Nathaniel Tucker 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. 22 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/__tests__/__snapshots__/reducer.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`reducer should set error in meta for "receive" 1`] = ` 4 | Object { 5 | "entities": Object {}, 6 | "indexes": Object {}, 7 | "meta": Object { 8 | "http://test.com/article/20": Object { 9 | "date": 5000000000, 10 | "error": [Error: hi], 11 | "expiresAt": 5000500000, 12 | }, 13 | }, 14 | "optimistic": Array [], 15 | "results": Object {}, 16 | } 17 | `; 18 | 19 | exports[`reducer singles should update state correctly 1`] = ` 20 | Object { 21 | "entities": Object { 22 | "http://test.com/article/": Object { 23 | "20": ArticleResource { 24 | "author": null, 25 | "content": "this is the content", 26 | "id": 20, 27 | "tags": Array [], 28 | "title": "hi", 29 | }, 30 | }, 31 | }, 32 | "indexes": Object {}, 33 | "meta": Object { 34 | "http://test.com/article/20": Object { 35 | "date": 5000000000, 36 | "expiresAt": 5000500000, 37 | }, 38 | }, 39 | "optimistic": Array [], 40 | "results": Object { 41 | "http://test.com/article/20": "20", 42 | }, 43 | } 44 | `; 45 | -------------------------------------------------------------------------------- /packages/normalizr/src/schemas/Values.js: -------------------------------------------------------------------------------- 1 | import PolymorphicSchema from './Polymorphic'; 2 | 3 | export default class ValuesSchema extends PolymorphicSchema { 4 | normalize(input, parent, key, visit, addEntity, visitedEntities) { 5 | return Object.keys(input).reduce((output, key, index) => { 6 | const value = input[key]; 7 | return value !== undefined && value !== null 8 | ? { 9 | ...output, 10 | [key]: this.normalizeValue( 11 | value, 12 | input, 13 | key, 14 | visit, 15 | addEntity, 16 | visitedEntities, 17 | ), 18 | } 19 | : output; 20 | }, {}); 21 | } 22 | 23 | denormalize(input, unvisit) { 24 | let found = true; 25 | return [ 26 | Object.keys(input).reduce((output, key) => { 27 | const entityOrId = input[key]; 28 | const [value, foundItem] = this.denormalizeValue(entityOrId, unvisit); 29 | if (!foundItem) { 30 | found = false; 31 | } 32 | return { 33 | ...output, 34 | [key]: value, 35 | }; 36 | }, {}), 37 | found, 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/guides/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Guides 3 | --- 4 | 5 | - 🔰 Basics 6 | - [Handling loading state](guides/loading-state.md) 7 | - [Dealing with network errors](guides/network-errors.md) 8 | - [Fetching multiple resources at once](guides/fetch-multiple.md) 9 | - [Pagination](guides/pagination.md) 10 | - [Understanding Immutability](guides/immutability.md) 11 | - [Mocking unfinished endpoints](guides/mocking-unfinished.md) 12 | - 🖧 Defining your network interface 13 | - [Defining your Resource types](guides/resource-types.md) 14 | - [Computed properties](guides/computed-properties.md) 15 | - [Multicolumn primary keys](guides/multi-pk.md) 16 | - [Transforming data on network load](guides/network-transform.md) 17 | - [Authentication](guides/auth.md) 18 | - [Cross-orgin requests with JSONP](guides/jsonp.md) 19 | - [Custom networking library](guides/custom-networking.md) 20 | - [Custom cache lifetime](guides/resource-lifetime.md) 21 | - 💨 Performance Optimizations (optional) 22 | - [Nesting related resources (server-side join)](guides/nested-response.md) 23 | - [Cross-resource multi-update RPC (dealing with side-effects)](guides/rpc.md) 24 | - Integrations 25 | - [Redux integration](guides/redux.md) 26 | -------------------------------------------------------------------------------- /packages/normalizr/examples/relationships/schema.js: -------------------------------------------------------------------------------- 1 | import { schema } from '../../src'; 2 | 3 | const userProcessStrategy = (value, parent, key) => { 4 | switch (key) { 5 | case 'author': 6 | return { ...value, posts: [parent.id] }; 7 | case 'commenter': 8 | return { ...value, comments: [parent.id] }; 9 | default: 10 | return { ...value }; 11 | } 12 | }; 13 | 14 | const userMergeStrategy = (entityA, entityB) => { 15 | return { 16 | ...entityA, 17 | ...entityB, 18 | posts: [...(entityA.posts || []), ...(entityB.posts || [])], 19 | comments: [...(entityA.comments || []), ...(entityB.comments || [])] 20 | }; 21 | }; 22 | 23 | const user = new schema.Entity( 24 | 'users', 25 | {}, 26 | { 27 | mergeStrategy: userMergeStrategy, 28 | processStrategy: userProcessStrategy 29 | } 30 | ); 31 | 32 | const comment = new schema.Entity( 33 | 'comments', 34 | { 35 | commenter: user 36 | }, 37 | { 38 | processStrategy: (value, parent, key) => { 39 | return { ...value, post: parent.id }; 40 | } 41 | } 42 | ); 43 | 44 | const post = new schema.Entity('posts', { 45 | author: user, 46 | comments: [comment] 47 | }); 48 | 49 | export default [post]; 50 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/state/__tests__/__snapshots__/pollingSubscription.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`PollingSubscription should call after period 1`] = ` 4 | Array [ 5 | Object { 6 | "meta": Object { 7 | "options": Object { 8 | "dataExpiryLength": 2500, 9 | "errorExpiryLength": 500, 10 | }, 11 | "reject": [Function], 12 | "resolve": [Function], 13 | "responseType": "rest-hooks/receive", 14 | "schema": [Function], 15 | "throttle": true, 16 | "url": "test.com", 17 | }, 18 | "payload": [MockFunction], 19 | "type": "rest-hooks/fetch", 20 | }, 21 | ] 22 | `; 23 | 24 | exports[`PollingSubscription should call after period 2`] = ` 25 | Array [ 26 | Object { 27 | "meta": Object { 28 | "options": Object { 29 | "dataExpiryLength": 2500, 30 | "errorExpiryLength": 500, 31 | }, 32 | "reject": [Function], 33 | "resolve": [Function], 34 | "responseType": "rest-hooks/receive", 35 | "schema": [Function], 36 | "throttle": true, 37 | "url": "test.com", 38 | }, 39 | "payload": [MockFunction], 40 | "type": "rest-hooks/fetch", 41 | }, 42 | ] 43 | `; 44 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/modules/issues.js: -------------------------------------------------------------------------------- 1 | import * as Repo from './repos'; 2 | import { issue } from '../../api/schema'; 3 | import { ADD_ENTITIES, addEntities } from '../actions'; 4 | import { denormalize, normalize } from '../../../../../src'; 5 | 6 | export const STATE_KEY = 'issues'; 7 | 8 | export default function reducer(state = {}, action) { 9 | switch (action.type) { 10 | case ADD_ENTITIES: 11 | return { 12 | ...state, 13 | ...action.payload.issues 14 | }; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export const getIssues = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => { 22 | const state = getState(); 23 | const owner = Repo.selectOwner(state); 24 | const repo = Repo.selectRepo(state); 25 | return api.issues 26 | .getForRepo({ 27 | owner, 28 | repo 29 | }) 30 | .then((response) => { 31 | const data = normalize(response, [schema.issue]); 32 | dispatch(addEntities(data.entities)); 33 | return response; 34 | }) 35 | .catch((error) => { 36 | console.error(error); 37 | }); 38 | }; 39 | 40 | export const selectHydrated = (state, id) => denormalize(id, issue, state); 41 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/modules/labels.js: -------------------------------------------------------------------------------- 1 | import * as Repo from './repos'; 2 | import { label } from '../../api/schema'; 3 | import { ADD_ENTITIES, addEntities } from '../actions'; 4 | import { denormalize, normalize } from '../../../../../src'; 5 | 6 | export const STATE_KEY = 'labels'; 7 | 8 | export default function reducer(state = {}, action) { 9 | switch (action.type) { 10 | case ADD_ENTITIES: 11 | return { 12 | ...state, 13 | ...action.payload.labels 14 | }; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export const getLabels = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => { 22 | const state = getState(); 23 | const owner = Repo.selectOwner(state); 24 | const repo = Repo.selectRepo(state); 25 | return api.issues 26 | .getLabels({ 27 | owner, 28 | repo 29 | }) 30 | .then((response) => { 31 | const data = normalize(response, [schema.label]); 32 | dispatch(addEntities(data.entities)); 33 | return response; 34 | }) 35 | .catch((error) => { 36 | console.error(error); 37 | }); 38 | }; 39 | 40 | export const selectHydrated = (state, id) => denormalize(id, label, state); 41 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/modules/commits.js: -------------------------------------------------------------------------------- 1 | import * as Repo from './repos'; 2 | import { commit } from '../../api/schema'; 3 | import { ADD_ENTITIES, addEntities } from '../actions'; 4 | import { denormalize, normalize } from '../../../../../src'; 5 | 6 | export const STATE_KEY = 'commits'; 7 | 8 | export default function reducer(state = {}, action) { 9 | switch (action.type) { 10 | case ADD_ENTITIES: 11 | return { 12 | ...state, 13 | ...action.payload.commits 14 | }; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export const getCommits = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => { 22 | const state = getState(); 23 | const owner = Repo.selectOwner(state); 24 | const repo = Repo.selectRepo(state); 25 | return api.repos 26 | .getCommits({ 27 | owner, 28 | repo 29 | }) 30 | .then((response) => { 31 | const data = normalize(response, [schema.commit]); 32 | dispatch(addEntities(data.entities)); 33 | return response; 34 | }) 35 | .catch((error) => { 36 | console.error(error); 37 | }); 38 | }; 39 | 40 | export const selectHydrated = (state, id) => denormalize(id, commit, state); 41 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/useRetrieve.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useRetrieve() 3 | id: version-1.6.9-useRetrieve 4 | original_id: useRetrieve 5 | --- 6 | ```typescript 7 | function useRetrieve< 8 | Params extends Readonly, 9 | Body extends Readonly | void, 10 | S extends Schema 11 | >( 12 | selectShape: ReadShape, 13 | params: Params | null, 14 | body?: Body 15 | ): Promise | undefined; 16 | ``` 17 | 18 | Great for retrieving resources optimistically before they are needed. 19 | 20 | Will return a Promise if the resource is not yet in cache, otherwise undefined. 21 | 22 | This can be useful for ensuring resources early in a render tree before they are needed. 23 | 24 | Network errors will result in the promise rejecting. 25 | 26 | ## Example 27 | 28 | Using a type guard to deal with null 29 | 30 | ```tsx 31 | function MasterPost({ id }: { id: number }) { 32 | useRetrieve(PostResource.singleRequest(), { id }); 33 | // ... 34 | } 35 | ``` 36 | 37 | ## Useful `RequestShape`s to send 38 | 39 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 40 | 41 | - singleRequest() 42 | - listRequest() 43 | 44 | Feel free to add your own [RequestShape](./RequestShape.md) as well. 45 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/modules/milestones.js: -------------------------------------------------------------------------------- 1 | import * as Repo from './repos'; 2 | import { milestone } from '../../api/schema'; 3 | import { ADD_ENTITIES, addEntities } from '../actions'; 4 | import { denormalize, normalize } from '../../../../../src'; 5 | 6 | export const STATE_KEY = 'milestones'; 7 | 8 | export default function reducer(state = {}, action) { 9 | switch (action.type) { 10 | case ADD_ENTITIES: 11 | return { 12 | ...state, 13 | ...action.payload.milestones 14 | }; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export const getMilestones = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => { 22 | const state = getState(); 23 | const owner = Repo.selectOwner(state); 24 | const repo = Repo.selectRepo(state); 25 | return api.issues 26 | .getMilestones({ 27 | owner, 28 | repo 29 | }) 30 | .then((response) => { 31 | const data = normalize(response, [schema.milestone]); 32 | dispatch(addEntities(data.entities)); 33 | return response; 34 | }) 35 | .catch((error) => { 36 | console.error(error); 37 | }); 38 | }; 39 | 40 | export const selectHydrated = (state, id) => denormalize(id, milestone, state); 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | flow-typed/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # build directory 62 | **/lib 63 | **/dist 64 | 65 | # build info 66 | **/tsconfig*.tsbuildinfo 67 | -------------------------------------------------------------------------------- /packages/normalizr/examples/redux/src/redux/modules/pull-requests.js: -------------------------------------------------------------------------------- 1 | import * as Repo from './repos'; 2 | import { pullRequest } from '../../api/schema'; 3 | import { ADD_ENTITIES, addEntities } from '../actions'; 4 | import { denormalize, normalize } from '../../../../../src'; 5 | 6 | export const STATE_KEY = 'pullRequests'; 7 | 8 | export default function reducer(state = {}, action) { 9 | switch (action.type) { 10 | case ADD_ENTITIES: 11 | return { 12 | ...state, 13 | ...action.payload.pullRequests 14 | }; 15 | 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export const getPullRequests = ({ page = 0 } = {}) => (dispatch, getState, { api, schema }) => { 22 | const state = getState(); 23 | const owner = Repo.selectOwner(state); 24 | const repo = Repo.selectRepo(state); 25 | return api.pullRequests 26 | .getAll({ 27 | owner, 28 | repo 29 | }) 30 | .then((response) => { 31 | const data = normalize(response, [schema.pullRequest]); 32 | dispatch(addEntities(data.entities)); 33 | return response; 34 | }) 35 | .catch((error) => { 36 | console.error(error); 37 | }); 38 | }; 39 | 40 | export const selectHydrated = (state, id) => denormalize(id, pullRequest, state); 41 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/api/useCache.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useCache() 3 | id: version-2.0-useCache 4 | original_id: useCache 5 | --- 6 | ```typescript 7 | function useCache, S extends Schema>( 8 | { select, getFetchKey }: ReadShape, 9 | params: Params | null 10 | ): SchemaOf | null; 11 | ``` 12 | 13 | Excellent to use data in the normalized cache without fetching. 14 | 15 | * [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 16 | * Returns previously cached if exists 17 | * null otherwise 18 | * While loading: 19 | * Returns previously cached if exists 20 | * null otherwise 21 | 22 | ## Example 23 | 24 | Using a type guard to deal with null 25 | 26 | ```tsx 27 | function Post({ id }: { id: number }) { 28 | const post = useCache(PostResource.detailShape(), { id }); 29 | // post as PostResource | null 30 | if (!post) return null; 31 | // post as PostResource (typeguarded) 32 | // ...render stuff here 33 | } 34 | ``` 35 | 36 | ## Useful `FetchShape`s to send 37 | 38 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 39 | 40 | - detailShape() 41 | - listShape() 42 | 43 | Feel free to add your own [FetchShape](./FetchShape.md) as well. 44 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/resource/types.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from './normal'; 2 | import Entity from './Entity'; 3 | import { FetchShape, DeleteShape } from './shapes'; 4 | 5 | export type SchemaFromShape< 6 | F extends FetchShape 7 | > = F extends FetchShape ? S : never; 8 | 9 | export type BodyFromShape< 10 | F extends FetchShape 11 | > = F extends FetchShape ? B : never; 12 | 13 | export function isDeleteShape( 14 | shape: FetchShape, 15 | ): shape is DeleteShape { 16 | return shape.type === 'delete'; 17 | } 18 | 19 | export type ResultShape = RS extends { schema: infer U } ? U : never; 20 | export type SelectReturn = RS extends { 21 | select: (...args: any[]) => infer U; 22 | } 23 | ? U 24 | : never; 25 | export type AlwaysSelect = NonNullable>; 26 | export type ParamArg = RS extends { 27 | getFetchKey: (params: infer U) => any; 28 | } 29 | ? U 30 | : never; 31 | export type BodyArg = RS extends { 32 | fetch: (url: any, body: infer U) => any; 33 | } 34 | ? U 35 | : never; 36 | 37 | export function isEntity(schema: Schema): schema is typeof Entity { 38 | return schema !== null && (schema as any).getId !== undefined; 39 | } 40 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/useCache.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useCache() 3 | id: version-1.6.9-useCache 4 | original_id: useCache 5 | --- 6 | ```typescript 7 | function useCache, S extends Schema>( 8 | { select, getUrl }: ReadShape, 9 | params: Params | null 10 | ): SchemaOf | null; 11 | ``` 12 | 13 | Excellent to use data in the normalized cache without fetching. 14 | 15 | * [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 16 | * Returns previously cached if exists 17 | * null otherwise 18 | * While loading: 19 | * Returns previously cached if exists 20 | * null otherwise 21 | 22 | ## Example 23 | 24 | Using a type guard to deal with null 25 | 26 | ```tsx 27 | function Post({ id }: { id: number }) { 28 | const post = useCache(PostResource.singleRequest(), { id }); 29 | // post as PostResource | null 30 | if (!post) return null; 31 | // post as PostResource (typeguarded) 32 | // ...render stuff here 33 | } 34 | ``` 35 | 36 | ## Useful `RequestShape`s to send 37 | 38 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 39 | 40 | - singleRequest() 41 | - listRequest() 42 | 43 | Feel free to add your own [RequestShape](./RequestShape.md) as well. 44 | -------------------------------------------------------------------------------- /packages/test/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from 'rollup-plugin-commonjs'; 2 | import resolve from 'rollup-plugin-node-resolve'; 3 | import babel from 'rollup-plugin-babel'; 4 | 5 | import pkg from './package.json'; 6 | 7 | const dependencies = Object.keys(pkg.dependencies) 8 | .concat(Object.keys(pkg.peerDependencies)) 9 | .filter(dep => !['@rest-hooks/normalizr', '@babel/runtime'].includes(dep)); 10 | 11 | const extensions = ['.js', '.ts', '.tsx', '.mjs', '.json', '.node']; 12 | process.env.NODE_ENV = 'production'; 13 | 14 | function isExternal(id) { 15 | const ret = dependencies.includes(id); 16 | if (!ret) { 17 | for (const dep of dependencies) { 18 | if (id.startsWith(dep)) return true; 19 | } 20 | } 21 | return ret; 22 | } 23 | 24 | export default [ 25 | // test utils commonjs build 26 | { 27 | input: 'src/index.ts', 28 | external: id => id === '..' || isExternal(id), 29 | output: [{ file: 'dist/index.cjs.js', format: 'cjs' }], 30 | plugins: [ 31 | babel({ 32 | exclude: ['node_modules/**', '**/__tests__/**', '**/*.d.ts'], 33 | rootMode: "upward", 34 | extensions, 35 | runtimeHelpers: true, 36 | }), 37 | resolve({ extensions }), 38 | commonjs({ extensions }), 39 | ], 40 | }, 41 | ]; 42 | -------------------------------------------------------------------------------- /docs/api/useResetter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useResetter() 3 | --- 4 | 5 | ```typescript 6 | function useResetter(): () => void; 7 | ``` 8 | 9 | Mostly useful for imperatively resetting the cache. 10 | 11 | Does not accept any arguments will always reset when called. 12 | 13 | This is more than just expiring the items. Useful when so much has changed 14 | e.g. impersonating a user that the entire cache set must be thrown away and 15 | retrieved again. 16 | 17 | ## Example 18 | 19 | ```typescript 20 | import { Resource } from 'rest-hooks'; 21 | 22 | // Server returns the logged in user 23 | export default class CurrentUserResource extends Resource { 24 | readonly id: string = null; 25 | readonly name: string = ''; 26 | // ... 27 | } 28 | ``` 29 | 30 | ```tsx 31 | const USER_NUMBER_ONE: string = "1111"; 32 | 33 | function UserName() { 34 | const user = useResource(CurrentUserResource.detailShape(), { }); 35 | const resetCache = useResetter(); 36 | 37 | const becomeAdmin = useCallback(() => { 38 | // Changes the current user 39 | impersonateUser(USER_NUMBER_ONE); 40 | // Empty the cache 41 | resetCache(); 42 | }, []); 43 | return ( 44 |
45 |

{user.name}

46 | 47 |

48 | ); 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/guides/resource-lifetime.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom Resource cache lifetime 3 | sidebar_label: Custom cache lifetime 4 | --- 5 | 6 | By default the NetworkManager specifies the lifetime of data and errors in the cache. 7 | If some resources are longer living, or shorter living than other, the can specify their own expiry length values, 8 | which will be passed on to all [fetch shape](../api/FetchShape.md) creator functions of [Resource](../api/Resource.md). 9 | 10 | ## Examples 11 | 12 | ### Long cache lifetime 13 | 14 | `LongLivingResource.ts` 15 | 16 | ```typescript 17 | // We can now extend LongLivingResource to get a resource that will be cached for one hour 18 | abstract class LongLivingResource extends Resource { 19 | static getFetchOptions() { 20 | return { 21 | ...super.getFetchOptions(), 22 | dataExpiryLength: 60 * 60 * 1000, // one hour 23 | }; 24 | } 25 | } 26 | ``` 27 | 28 | ### Never retry on error 29 | 30 | `NoRetryResource.ts` 31 | 32 | ```typescript 33 | // We can now extend NoRetryResource to get a resource that will never retry on network error 34 | abstract class NoRetryResource extends Resource { 35 | static getFetchOptions() { 36 | return { 37 | ...super.getFetchOptions(), 38 | errorExpiryLength: Infinity, 39 | }; 40 | } 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/guides/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Guides 3 | id: version-1.6.9-README 4 | original_id: README 5 | --- 6 | 7 | - 🔰 Basics 8 | - [Handling loading state](guides/loading-state.md) 9 | - [Dealing with network errors](guides/network-errors.md) 10 | - [Fetching multiple resources at once](guides/fetch-multiple.md) 11 | - [Pagination](guides/pagination.md) 12 | - [Understanding Immutability](guides/immutability.md) 13 | - [Mocking unfinished endpoints](guides/mocking-unfinished.md) 14 | - 🖧 Defining your network interface 15 | - [Defining your Resource types](guides/resource-types.md) 16 | - [Computed properties](guides/computed-properties.md) 17 | - [Multicolumn primary keys](guides/multi-pk.md) 18 | - [Transforming data on network load](guides/network-transform.md) 19 | - [Authentication](guides/auth.md) 20 | - [Cross-orgin requests with JSONP](guides/jsonp.md) 21 | - [Custom networking library](guides/custom-networking.md) 22 | - [Custom cache lifetime](guides/resource-lifetime.md) 23 | - 💨 Performance Optimizations (optional) 24 | - [Nesting related resources (server-side join)](guides/nested-response.md) 25 | - [Cross-resource multi-update RPC (dealing with side-effects)](guides/rpc.md) 26 | - Integrations 27 | - [Redux integration](guides/redux.md) 28 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.1/api/makeExternalCacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: makeExternalCacheProvider() 3 | id: version-2.1-makeExternalCacheProvider 4 | original_id: makeExternalCacheProvider 5 | --- 6 | 7 | ```typescript 8 | declare const makeExternalCacheProvider: ( 9 | managers: Manager[], 10 | initialState?: State, 11 | ) => ({ children }: { children: React.ReactNode }) => JSX.Element; 12 | ``` 13 | 14 | Used to build a [\](./ExternalCacheProvider.md) for [makeRenderRestHook()](./makeRenderRestHook.md) 15 | 16 | Internally constructs a redux store attaching the middlwares. 17 | 18 | ## Arguments 19 | 20 | ### managers 21 | 22 | [Manager](./Manager.md) 23 | 24 | ### initialState 25 | 26 | Can be used to prime the cache if test expects cache values to already be filled. 27 | 28 | ## Returns 29 | 30 | Simple wrapper component that only has child as prop. 31 | 32 | ```tsx 33 | const manager = new MockNetworkManager(); 34 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 35 | const Provider = makeExternalCacheProvider([manager, subscriptionManager]); 36 | 37 | function renderRestHook(callback: () => T) { 38 | return renderHook(callback, { 39 | wrapper: ({ children }) => {children}, 40 | }); 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.0/guides/auth.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Authentication 3 | id: version-4.0-auth 4 | original_id: auth 5 | --- 6 | 7 | All network requests are run through the `static fetchOptionsPlugin` optionally 8 | defined in your `Resource`. 9 | 10 | Here's an example using simple cookie auth: 11 | 12 | 13 | 14 | 15 | ```typescript 16 | class AuthdResource extends Resource { 17 | static fetchOptionsPlugin = (options: RequestInit) => ({ 18 | ...options, 19 | credentials: 'same-origin', 20 | }); 21 | } 22 | ``` 23 | 24 | 25 | 26 | ```typescript 27 | import { Request } from 'rest-hooks'; 28 | 29 | class AuthdResource extends Resource { 30 | static fetchPlugin = (request: Request) => request.withCredentials(); 31 | } 32 | ``` 33 | 34 | 35 | 36 | You can also do more complex flows (like adding arbitrary headers) to 37 | the request. Every `fetchOptionsPlugin` takes in the existing [init options](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) of fetch, and returns new init options to be used. 38 | 39 | ## Code organization 40 | 41 | If much of your `Resources` share a similar auth mechanism, you might 42 | try extending from a base class that defines such common customizations. 43 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/makeRestProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: makeRestProvider() 3 | id: version-1.6.9-makeRestProvider 4 | original_id: makeRestProvider 5 | --- 6 | 7 | ```typescript 8 | declare const makeRestProvider: ( 9 | manager: NetworkManager, 10 | subscriptionManager: SubscriptionManager, 11 | initialState?: State, 12 | ) => ({ children }: { children: React.ReactNode }) => JSX.Element; 13 | ``` 14 | 15 | Used to build a [\](./RestProvider.md) for [makeRenderRestHook()](./makeRenderRestHook.md) 16 | 17 | ## Arguments 18 | 19 | ### manager 20 | 21 | [NetworkManager](./NetworkManager.md) 22 | 23 | ### subscriptionManager 24 | 25 | [SubscriptionManager](./SubscriptionManager.md) 26 | 27 | ### initialState 28 | 29 | Can be used to prime the cache if test expects cache values to already be filled. 30 | 31 | ## Returns 32 | 33 | Simple wrapper component that only has child as prop. 34 | 35 | ```tsx 36 | const manager = new MockNetworkManager(); 37 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 38 | const Provider = makeRestProvider(manager, subscriptionManager); 39 | 40 | function renderRestHook(callback: () => T) { 41 | return renderHook(callback, { 42 | wrapper: ({ children }) => {children}, 43 | }); 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/api/makeCacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: makeCacheProvider() 3 | id: version-2.0-makeCacheProvider 4 | original_id: makeCacheProvider 5 | --- 6 | 7 | ```typescript 8 | declare const makeCacheProvider: ( 9 | manager: NetworkManager, 10 | subscriptionManager: SubscriptionManager, 11 | initialState?: State, 12 | ) => ({ children }: { children: React.ReactNode }) => JSX.Element; 13 | ``` 14 | 15 | Used to build a [\](./CacheProvider.md) for [makeRenderRestHook()](./makeRenderRestHook.md) 16 | 17 | ## Arguments 18 | 19 | ### manager 20 | 21 | [NetworkManager](./NetworkManager.md) 22 | 23 | ### subscriptionManager 24 | 25 | [SubscriptionManager](./SubscriptionManager.md) 26 | 27 | ### initialState 28 | 29 | Can be used to prime the cache if test expects cache values to already be filled. 30 | 31 | ## Returns 32 | 33 | Simple wrapper component that only has child as prop. 34 | 35 | ```tsx 36 | const manager = new MockNetworkManager(); 37 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 38 | const Provider = makeCacheProvider(manager, subscriptionManager); 39 | 40 | function renderRestHook(callback: () => T) { 41 | return renderHook(callback, { 42 | wrapper: ({ children }) => {children}, 43 | }); 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/hooks/useRetrieve.ts: -------------------------------------------------------------------------------- 1 | import { ReadShape, Schema } from 'rest-hooks/resource'; 2 | import { useMemo } from 'react'; 3 | 4 | import useFetcher from './useFetcher'; 5 | import useMeta from './useMeta'; 6 | 7 | /** Returns whether the data at this url is fresh or stale */ 8 | function useExpiresAt, S extends Schema>( 9 | fetchShape: ReadShape, 10 | params: Params | null, 11 | ): number { 12 | const meta = useMeta(fetchShape, params); 13 | if (!meta) { 14 | return 0; 15 | } 16 | return meta.expiresAt; 17 | } 18 | 19 | /** Request a resource if it is not in cache. */ 20 | export default function useRetrieve< 21 | Params extends Readonly, 22 | S extends Schema 23 | >(fetchShape: ReadShape, params: Params | null) { 24 | const fetch = useFetcher(fetchShape, true); 25 | const expiresAt = useExpiresAt(fetchShape, params); 26 | 27 | return useMemo(() => { 28 | if (Date.now() <= expiresAt) return; 29 | // null params mean don't do anything 30 | if (!params) return; 31 | return fetch(params); 32 | // we need to check against serialized params, since params can change frequently 33 | // eslint-disable-next-line react-hooks/exhaustive-deps 34 | }, [expiresAt, fetch, params && fetchShape.getFetchKey(params)]); 35 | } 36 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/NetworkErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface NetworkError extends Error { 4 | status: number | undefined; 5 | response?: { statusText?: string; body?: any }; 6 | } 7 | 8 | function isNetworkError(error: NetworkError | unknown): error is NetworkError { 9 | return Object.prototype.hasOwnProperty.call(error, 'status'); 10 | } 11 | 12 | interface Props { 13 | children: React.ReactNode; 14 | fallbackComponent: React.ComponentType<{ error: E }>; 15 | } 16 | interface State { 17 | error?: E; 18 | } 19 | export default class NetworkErrorBoundary< 20 | E extends NetworkError 21 | > extends React.Component, State> { 22 | static defaultProps = { 23 | fallbackComponent: ({ error }: { error: NetworkError }) => ( 24 |
25 | {error.status} {error.response && error.response.statusText} 26 |
27 | ), 28 | }; 29 | 30 | static getDerivedStateFromError(error: NetworkError | any) { 31 | if (isNetworkError(error)) { 32 | return { error }; 33 | } 34 | } 35 | 36 | state: State = {}; 37 | 38 | render() { 39 | if (!this.state.error) { 40 | return this.props.children; 41 | } 42 | return ; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/normalizr/typescript-tests/relationships.ts: -------------------------------------------------------------------------------- 1 | import { normalize, schema } from '../src' 2 | 3 | const userProcessStrategy = (value: any, parent: any, key: string) => { 4 | switch (key) { 5 | case 'author': 6 | return { ...value, posts: [parent.id] }; 7 | case 'commenter': 8 | return { ...value, comments: [parent.id] }; 9 | default: 10 | return { ...value }; 11 | } 12 | }; 13 | 14 | const userMergeStrategy = (entityA: any, entityB: any) => { 15 | return { 16 | ...entityA, 17 | ...entityB, 18 | posts: [...(entityA.posts || []), ...(entityB.posts || [])], 19 | comments: [...(entityA.comments || []), ...(entityB.comments || [])] 20 | }; 21 | }; 22 | 23 | const user = new schema.Entity( 24 | 'users', 25 | {}, 26 | { 27 | mergeStrategy: userMergeStrategy, 28 | processStrategy: userProcessStrategy 29 | } 30 | ); 31 | 32 | const comment = new schema.Entity( 33 | 'comments', 34 | { 35 | commenter: user 36 | }, 37 | { 38 | processStrategy: (value: any, parent: any, key: string) => { 39 | return { ...value, post: parent.id }; 40 | } 41 | } 42 | ); 43 | 44 | const post = new schema.Entity('posts', { 45 | author: user, 46 | comments: [comment] 47 | }); 48 | 49 | const data = { 50 | /* ...*/ 51 | }; 52 | const normalizedData = normalize(data, post); 53 | console.log(normalizedData); 54 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/api/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Reference 3 | id: version-2.0-README 4 | original_id: README 5 | --- 6 | 7 | ## Interface Definitions 8 | - [Resource](Resource.md) 9 | - [FetchShape](FetchShape.md) 10 | 11 | ## Hooks 12 | - [useResource](useResource.md) 13 | - [useFetcher](useFetcher.md) 14 | - [useCache](useCache.md) 15 | - [useResultCache](useResultCache.md) 16 | - [useSubscription](useSubscription.md) 17 | - [useRetrieve](useRetrieve.md) 18 | - [useInvalidator](useInvalidator.md) 19 | 20 | ## Components 21 | - [CacheProvider](CacheProvider.md) 22 | - [ExternalCacheProvider](ExternalCacheProvider.md) 23 | - [NetworkErrorBoundary](NetworkErrorBoundary.md) 24 | 25 | ## [Manager](Manager.md)s 26 | 27 | Extended the networking/state layer works through [managers](Manager.md). 28 | 29 | - [NetworkManager](NetworkManager.md) 30 | - [SubscriptionManager](SubscriptionManager.md) 31 | - [PollingSubscription](PollingSubscription.md) 32 | 33 | ## Testing 34 | 35 | Testing utilities are imported from `rest-hooks/testing`. These are useful 36 | helpers to ensure your code works as intended and are not meant to be shipped 37 | to production themselves. 38 | 39 | - [\](MockProvider) 40 | - [makeRenderRestHook()](makeRenderRestHook) 41 | - [makeCacheProvider()](makeCacheProvider) 42 | - [makeExternalCacheProvider()](makeExternalCacheProvider) 43 | 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Reference 3 | id: version-1.6.9-README 4 | original_id: README 5 | --- 6 | 7 | ## Interface Definitions 8 | - [Resource](Resource.md) 9 | - [RequestShape](RequestShape.md) 10 | 11 | ## Hooks 12 | - [useResource](useResource.md) 13 | - [useFetcher](useFetcher.md) 14 | - [useCache](useCache.md) 15 | - [useResultCache](useResultCache.md) 16 | - [useSubscription](useSubscription.md) 17 | - [useRetrieve](useRetrieve.md) 18 | - [useInvalidator](useInvalidator.md) 19 | 20 | ## Components 21 | - [RestProvider](RestProvider.md) 22 | - [ExternalCacheProvider](ExternalCacheProvider.md) 23 | - [NetworkErrorBoundary](NetworkErrorBoundary.md) 24 | 25 | ## [Manager](Manager.md)s 26 | 27 | Extended the networking/state layer works through [managers](Manager.md). 28 | 29 | - [NetworkManager](NetworkManager.md) 30 | - [SubscriptionManager](SubscriptionManager.md) 31 | - [PollingSubscription](PollingSubscription.md) 32 | 33 | ## Testing 34 | 35 | Testing utilities are imported from `rest-hooks/testing`. These are useful 36 | helpers to ensure your code works as intended and are not meant to be shipped 37 | to production themselves. 38 | 39 | - [\](MockProvider) 40 | - [makeRenderRestHook()](makeRenderRestHook) 41 | - [makeRestProvider()](makeRestProvider) 42 | - [makeExternalCacheProvider()](makeExternalCacheProvider) 43 | 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-3.0/api/useResetter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useResetter() 3 | id: version-3.0-useResetter 4 | original_id: useResetter 5 | --- 6 | 7 | ```typescript 8 | function useResetter(): () => void; 9 | ``` 10 | 11 | Mostly useful for imperatively resetting the cache. 12 | 13 | Does not accept any arguments will always reset when called. 14 | 15 | This is more than just expiring the items. Useful when so much has changed 16 | e.g. impersonating a user that the entire cache set must be thrown away and 17 | retrieved again. 18 | 19 | ## Example 20 | 21 | ```typescript 22 | import { Resource } from 'rest-hooks'; 23 | 24 | // Server returns the logged in user 25 | export default class CurrentUserResource extends Resource { 26 | readonly id: string = null; 27 | readonly name: string = ''; 28 | // ... 29 | } 30 | ``` 31 | 32 | ```tsx 33 | const USER_NUMBER_ONE: string = "1111"; 34 | 35 | function UserName() { 36 | const user = useResource(CurrentUserResource.detailShape(), { }); 37 | const resetCache = useResetter(); 38 | 39 | const becomeAdmin = useCallback(() => { 40 | // Changes the current user 41 | impersonateUser(USER_NUMBER_ONE); 42 | // Empty the cache 43 | resetCache(); 44 | }, []); 45 | return ( 46 |
47 |

{user.name}

48 | 49 |

50 | ); 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /website/blog/2020-01-06-Rest-Hooks-4.1-Released.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: Nathaniel Tucker 3 | authorURL: https://twitter.com/npinp 4 | authorFBID: 7941978 5 | title: Rest Hooks 4.1 Released 6 | --- 7 | 8 | 4.1 comes with a more granular data definition hierarchy that will make it easier to 9 | write more API definitions. This marked by the introduction of a new member known 10 | as [Entity](/docs/api/Entity). `Entity` only needs a `pk()` and `get key()`, as well 11 | as member declarations to integrate fully. 12 | 13 | ### Entity 14 | 15 | - Useful for nested entities that don't have endpoints like LatestPrice. 16 | - Useful for non-REST style APIs like GraphQL. 17 | - Simplifies defining nested entities. 18 | 19 | 20 | 21 | ### New hierarchy: 22 | 23 | ``` 24 | SimpleRecord 25 | | 26 | Entity 27 | | 28 | SimpleResource 29 | | 30 | Resource 31 | ``` 32 | 33 | ### Deprecations: 34 | 35 | - Resource.getKey() -> Resource.key 36 | - Resource.getEntitySchema() -> Resource.asSchema() 37 | - Entity.define() -> override Entity.schema 38 | 39 | ### Changes: 40 | 41 | - Normalizr: top level key sent to getId is undefined not null 42 | - pk() now takes additional parent, and key optional args 43 | - pk() no longer accepts number return value (run .toString()) 44 | 45 | [Full set of 4.1 release commits](https://github.com/coinbase/rest-hooks/releases/tag/rest-hooks%404.1.0) 46 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/guides/resource-lifetime.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom Resource cache lifetime 3 | sidebar_label: Custom cache lifetime 4 | id: version-2.0-resource-lifetime 5 | original_id: resource-lifetime 6 | --- 7 | By default the NetworkManager specifies the lifetime of data and errors in the cache. 8 | If some resources are longer living, or shorter living than other, the can specify their own expiry length values, 9 | which will be passed on to all [fetch shape](../api/FetchShape.md) creator functions of [Resource](../api/Resource.md). 10 | 11 | ## Examples 12 | 13 | ### Long cache lifetime 14 | 15 | `LongLivingResource.ts` 16 | 17 | ```typescript 18 | // We can now extend LongLivingResource to get a resource that will be cached for one hour 19 | abstract class LongLivingResource extends Resource { 20 | static getRequestOptions() { 21 | return { 22 | ...super.getRequestOptions(), 23 | dataExpiryLength: 60 * 60 * 1000, // one hour 24 | }; 25 | } 26 | } 27 | ``` 28 | 29 | ### Never retry on error 30 | 31 | `NoRetryResource.ts` 32 | 33 | ```typescript 34 | // We can now extend NoRetryResource to get a resource that will never retry on network error 35 | abstract class NoRetryResource extends Resource { 36 | static getRequestOptions() { 37 | return { 38 | ...super.getRequestOptions(), 39 | errorExpiryLength: Infinity, 40 | }; 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.2/api/useResetter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useResetter() 3 | id: version-2.2-useResetter 4 | original_id: useResetter 5 | --- 6 | 7 | ```typescript 8 | function useResetter(): () => void; 9 | ``` 10 | 11 | Mostly useful for imperatively resetting the cache. 12 | 13 | Does not accept any arguments will always reset when called. 14 | 15 | This is more than just expiring the items. Useful when so much has changed 16 | e.g. impersonating a user that the entire cache set must be thrown away and 17 | retrieved again. 18 | 19 | ## Example 20 | 21 | ```typescript 22 | import { Resource, RequestOptions } from 'rest-hooks'; 23 | 24 | // Server returns the logged in user 25 | export default class CurrentUserResource extends Resource { 26 | readonly id: string = null; 27 | readonly name: string = ''; 28 | // ... 29 | } 30 | ``` 31 | 32 | ```tsx 33 | const USER_NUMBER_ONE: string = "1111"; 34 | 35 | function UserName() { 36 | const user = useResource(CurrentUserResource.detailShape(), { }); 37 | const resetCache = useResetter(); 38 | 39 | const becomeAdmin = useCallback(() => { 40 | // Changes the current user 41 | impersonateUser(USER_NUMBER_ONE); 42 | // Empty the cache 43 | resetCache(); 44 | }, []); 45 | return ( 46 |
47 |

{user.name}

48 | 49 |

50 | ); 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /website/versioned_docs/version-3.0/guides/resource-lifetime.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom Resource cache lifetime 3 | sidebar_label: Custom cache lifetime 4 | id: version-3.0-resource-lifetime 5 | original_id: resource-lifetime 6 | --- 7 | 8 | By default the NetworkManager specifies the lifetime of data and errors in the cache. 9 | If some resources are longer living, or shorter living than other, the can specify their own expiry length values, 10 | which will be passed on to all [fetch shape](../api/FetchShape.md) creator functions of [Resource](../api/Resource.md). 11 | 12 | ## Examples 13 | 14 | ### Long cache lifetime 15 | 16 | `LongLivingResource.ts` 17 | 18 | ```typescript 19 | // We can now extend LongLivingResource to get a resource that will be cached for one hour 20 | abstract class LongLivingResource extends Resource { 21 | static getFetchOptions() { 22 | return { 23 | ...super.getFetchOptions(), 24 | dataExpiryLength: 60 * 60 * 1000, // one hour 25 | }; 26 | } 27 | } 28 | ``` 29 | 30 | ### Never retry on error 31 | 32 | `NoRetryResource.ts` 33 | 34 | ```typescript 35 | // We can now extend NoRetryResource to get a resource that will never retry on network error 36 | abstract class NoRetryResource extends Resource { 37 | static getFetchOptions() { 38 | return { 39 | ...super.getFetchOptions(), 40 | errorExpiryLength: Infinity, 41 | }; 42 | } 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.0/api/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Reference 3 | id: version-4.0-README 4 | original_id: README 5 | --- 6 | 7 | ## Interface Definitions 8 | - [Resource](Resource.md) 9 | - [SimpleRecord](SimpleRecord.md) 10 | - [FetchShape](FetchShape.md) 11 | 12 | ## Hooks 13 | - [useResource](useResource.md) 14 | - [useFetcher](useFetcher.md) 15 | - [useCache](useCache.md) 16 | - [useSubscription](useSubscription.md) 17 | - [useRetrieve](useRetrieve.md) 18 | - [useInvalidator](useInvalidator.md) 19 | - [useResetter](useResetter.md) 20 | 21 | ## Components 22 | - [CacheProvider](CacheProvider.md) 23 | - [ExternalCacheProvider](ExternalCacheProvider.md) 24 | - [NetworkErrorBoundary](NetworkErrorBoundary.md) 25 | 26 | ## [Manager](Manager.md)s 27 | 28 | Extended the networking/state layer works through [managers](Manager.md). 29 | 30 | - [NetworkManager](NetworkManager.md) 31 | - [SubscriptionManager](SubscriptionManager.md) 32 | - [PollingSubscription](PollingSubscription.md) 33 | 34 | ## Testing 35 | 36 | Testing utilities are imported from `@rest-hooks/test`. These are useful 37 | helpers to ensure your code works as intended and are not meant to be shipped 38 | to production themselves. 39 | 40 | - [\](MockProvider) 41 | - [makeRenderRestHook()](makeRenderRestHook) 42 | - [makeCacheProvider()](makeCacheProvider) 43 | - [makeExternalCacheProvider()](makeExternalCacheProvider) 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/guides/resource-lifetime.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom Resource cache lifetime 3 | sidebar_label: Custom cache lifetime 4 | id: version-1.6.9-resource-lifetime 5 | original_id: resource-lifetime 6 | --- 7 | By default the NetworkManager specifies the lifetime of data and errors in the cache. 8 | If some resources are longer living, or shorter living than other, the can specify their own expiry length values, 9 | which will be passed on to all [request shape](../api/RequestShape.md) creator functions of [Resource](../api/Resource.md). 10 | 11 | ## Examples 12 | 13 | ### Long cache lifetime 14 | 15 | `LongLivingResource.ts` 16 | 17 | ```typescript 18 | // We can now extend LongLivingResource to get a resource that will be cached for one hour 19 | abstract class LongLivingResource extends Resource { 20 | static getRequestOptions() { 21 | return { 22 | ...super.getRequestOptions(), 23 | dataExpiryLength: 60 * 60 * 1000, // one hour 24 | }; 25 | } 26 | } 27 | ``` 28 | 29 | ### Never retry on error 30 | 31 | `NoRetryResource.ts` 32 | 33 | ```typescript 34 | // We can now extend NoRetryResource to get a resource that will never retry on network error 35 | abstract class NoRetryResource extends Resource { 36 | static getRequestOptions() { 37 | return { 38 | ...super.getRequestOptions(), 39 | errorExpiryLength: Infinity, 40 | }; 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/api/ExternalCacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | --- 4 | Integrates external stores with `rest-hooks`. Should be placed as high as possible 5 | in application tree as any usage of the hooks is only possible for components below the provider 6 | in the React tree. 7 | 8 | **Is a replacement for \ - do _NOT_ use both at once** 9 | 10 | `index.tsx` 11 | 12 | ```tsx 13 | import { ExternalCacheProvider } from 'rest-hooks'; 14 | import ReactDOM from 'react-dom'; 15 | 16 | import { store, selector } from './store'; 17 | 18 | ReactDOM.render( 19 | 20 | 21 | , 22 | document.body, 23 | ); 24 | ``` 25 | 26 | See [redux example](../guides/redux.md) for a more complete example. 27 | 28 | ## store 29 | 30 | ```typescript 31 | interface Store { 32 | subscribe(listener: () => void): () => void; 33 | dispatch: React.Dispatch; 34 | getState(): S; 35 | } 36 | ``` 37 | 38 | Store simply needs to conform to this interface. A common implementation is a [redux store](https://redux.js.org/api/store), 39 | but theoretically any external store could be used. 40 | 41 | [Read more about integrating redux.](../guides/redux.md) 42 | 43 | ## selector 44 | 45 | ```typescript 46 | (state: S) => State 47 | ``` 48 | 49 | This function is used to retrieve the `rest-hooks` specific part of the store's state tree. 50 | -------------------------------------------------------------------------------- /website/versioned_docs/version-3.0/api/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Reference 3 | id: version-3.0-README 4 | original_id: README 5 | --- 6 | 7 | ## Interface Definitions 8 | - [Resource](Resource.md) 9 | - [FetchShape](FetchShape.md) 10 | 11 | ## Hooks 12 | - [useResource](useResource.md) 13 | - [useResourceLegacy](useResourceLegacy.md) 14 | - [useFetcher](useFetcher.md) 15 | - [useCache](useCache.md) 16 | - [useCacheLegacy](useCacheLegacy.md) 17 | - [useSubscription](useSubscription.md) 18 | - [useRetrieve](useRetrieve.md) 19 | - [useInvalidator](useInvalidator.md) 20 | - [useResetter](useResetter.md) 21 | 22 | ## Components 23 | - [CacheProvider](CacheProvider.md) 24 | - [ExternalCacheProvider](ExternalCacheProvider.md) 25 | - [NetworkErrorBoundary](NetworkErrorBoundary.md) 26 | 27 | ## [Manager](Manager.md)s 28 | 29 | Extended the networking/state layer works through [managers](Manager.md). 30 | 31 | - [NetworkManager](NetworkManager.md) 32 | - [SubscriptionManager](SubscriptionManager.md) 33 | - [PollingSubscription](PollingSubscription.md) 34 | 35 | ## Testing 36 | 37 | Testing utilities are imported from `@rest-hooks/test`. These are useful 38 | helpers to ensure your code works as intended and are not meant to be shipped 39 | to production themselves. 40 | 41 | - [\](MockProvider) 42 | - [makeRenderRestHook()](makeRenderRestHook) 43 | - [makeCacheProvider()](makeCacheProvider) 44 | - [makeExternalCacheProvider()](makeExternalCacheProvider) 45 | -------------------------------------------------------------------------------- /website/static/img/rest_hooks_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 15 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/makeExternalCacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: makeExternalCacheProvider() 3 | id: version-1.6.9-makeExternalCacheProvider 4 | original_id: makeExternalCacheProvider 5 | --- 6 | 7 | ```typescript 8 | declare const makeExternalCacheProvider: ( 9 | manager: NetworkManager, 10 | subscriptionManager: SubscriptionManager, 11 | initialState?: State, 12 | ) => ({ children }: { children: React.ReactNode }) => JSX.Element; 13 | ``` 14 | 15 | Used to build a [\](./ExternalCacheProvider.md) for [makeRenderRestHook()](./makeRenderRestHook.md) 16 | 17 | Internally constructs a redux store attaching the middlwares. 18 | 19 | ## Arguments 20 | 21 | ### manager 22 | 23 | [NetworkManager](./NetworkManager.md) 24 | 25 | ### subscriptionManager 26 | 27 | [SubscriptionManager](./SubscriptionManager.md) 28 | 29 | ### initialState 30 | 31 | Can be used to prime the cache if test expects cache values to already be filled. 32 | 33 | ## Returns 34 | 35 | Simple wrapper component that only has child as prop. 36 | 37 | ```tsx 38 | const manager = new MockNetworkManager(); 39 | const subscriptionManager = new SubscriptionManager(PollingSubscription); 40 | const Provider = makeExternalCacheProvider(manager, subscriptionManager); 41 | 42 | function renderRestHook(callback: () => T) { 43 | return renderHook(callback, { 44 | wrapper: ({ children }) => {children}, 45 | }); 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/api/NetworkErrorBoundary.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | --- 4 | 5 | Displays a fallback component when a network error happens in its subtree. 6 | 7 | ```tsx 8 | interface Props { 9 | children: React.ReactNode; 10 | fallbackComponent: React.ComponentType<{ 11 | error: NetworkError; 12 | }>; 13 | } 14 | export default class NetworkErrorBoundary extends React.Component { 15 | static defaultProps: { 16 | fallbackComponent: ({ error }: { error: NetworkError }) => JSX.Element; 17 | }; 18 | } 19 | ``` 20 | 21 | Custom fallback usage example: 22 | 23 | ```tsx 24 | import React from 'react'; 25 | import { CacheProvider, NetworkErrorBoundary, NetworkError } from 'rest-hooks'; 26 | 27 | function ErrorPage({ error }: { error: NetworkError }) { 28 | return ( 29 |
30 | {error.status} {error.response && error.response.statusText} 31 |
32 | ); 33 | } 34 | 35 | export default function App(): React.ReactElement { 36 | return ( 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | ``` 45 | 46 | Note: Once `` catches an error it will only render the fallback 47 | until it is remounted. To get around this you'll likely want to place the boundary at 48 | locations that will cause remounts when the error should be cleared. This is usually 49 | below the route itself. 50 | 51 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/api/useResultCache.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useResultCache() 3 | id: version-2.0-useResultCache 4 | original_id: useResultCache 5 | --- 6 | ```typescript 7 | function useResultCache, D extends object>( 8 | { getFetchKey, fetch }: ReadShape, 9 | params: Params | null, 10 | defaults?: D 11 | ): D extends undefined 12 | ? Resolved> | null 13 | : Readonly; 14 | ``` 15 | 16 | Excellent to use with [pagination](../guides/pagination.md) or any other extra (non-entity) data in results. 17 | 18 | * [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 19 | * Returns previously cached if exists 20 | * `defaults` if provided 21 | * null otherwise 22 | * While loading: 23 | * Returns previously cached if exists 24 | * `defaults` if provided 25 | * null otherwise 26 | 27 | ## Example 28 | 29 | By sending defaults we can destructure the values even if the results don't exist. 30 | 31 | ```tsx 32 | function PostList() { 33 | const { prevPage, nextPage } = useResultCache( 34 | PaginatedResource.listShape(), 35 | {}, 36 | { prevPage: '', nextPage: '' } 37 | ); 38 | // ...render stuff here 39 | } 40 | ``` 41 | 42 | ## Useful `FetchShape`s to send 43 | 44 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 45 | 46 | - listShape() 47 | 48 | Feel free to add your own [FetchShape](./FetchShape.md) as well. 49 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/useResultCache.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useResultCache() 3 | id: version-1.6.9-useResultCache 4 | original_id: useResultCache 5 | --- 6 | ```typescript 7 | function useResultCache, D extends object>( 8 | { getUrl, fetch }: ReadShape, 9 | params: Params | null, 10 | defaults?: D 11 | ): D extends undefined 12 | ? Resolved> | null 13 | : Readonly; 14 | ``` 15 | 16 | Excellent to use with [pagination](../guides/pagination.md) or any other extra (non-entity) data in results. 17 | 18 | * [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 19 | * Returns previously cached if exists 20 | * `defaults` if provided 21 | * null otherwise 22 | * While loading: 23 | * Returns previously cached if exists 24 | * `defaults` if provided 25 | * null otherwise 26 | 27 | ## Example 28 | 29 | By sending defaults we can destructure the values even if the results don't exist. 30 | 31 | ```tsx 32 | function PostList() { 33 | const { prevPage, nextPage } = useResultCache( 34 | PaginatedResource.listRequest(), 35 | {}, 36 | { prevPage: '', nextPage: '' } 37 | ); 38 | // ...render stuff here 39 | } 40 | ``` 41 | 42 | ## Useful `RequestShape`s to send 43 | 44 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 45 | 46 | - listRequest() 47 | 48 | Feel free to add your own [RequestShape](./RequestShape.md) as well. 49 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.2/api/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Reference 3 | id: version-2.2-README 4 | original_id: README 5 | --- 6 | 7 | ## Interface Definitions 8 | - [Resource](Resource.md) 9 | - [FetchShape](FetchShape.md) 10 | 11 | ## Hooks 12 | - [useResource](useResource.md) 13 | - [useResourceNew](useResourceNew.md) 14 | - [useFetcher](useFetcher.md) 15 | - [useCache](useCache.md) 16 | - [useCacheNew](useCacheNew.md) 17 | - [useResultCache](useResultCache.md) 18 | - [useSubscription](useSubscription.md) 19 | - [useRetrieve](useRetrieve.md) 20 | - [useInvalidator](useInvalidator.md) 21 | - [useResetter](useResetter.md) 22 | 23 | ## Components 24 | - [CacheProvider](CacheProvider.md) 25 | - [ExternalCacheProvider](ExternalCacheProvider.md) 26 | - [NetworkErrorBoundary](NetworkErrorBoundary.md) 27 | 28 | ## [Manager](Manager.md)s 29 | 30 | Extended the networking/state layer works through [managers](Manager.md). 31 | 32 | - [NetworkManager](NetworkManager.md) 33 | - [SubscriptionManager](SubscriptionManager.md) 34 | - [PollingSubscription](PollingSubscription.md) 35 | 36 | ## Testing 37 | 38 | Testing utilities are imported from `rest-hooks/testing`. These are useful 39 | helpers to ensure your code works as intended and are not meant to be shipped 40 | to production themselves. 41 | 42 | - [\](MockProvider) 43 | - [makeRenderRestHook()](makeRenderRestHook) 44 | - [makeCacheProvider()](makeCacheProvider) 45 | - [makeExternalCacheProvider()](makeExternalCacheProvider) 46 | -------------------------------------------------------------------------------- /website/static/img/github-brands.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Reference 3 | --- 4 | 5 | ## Interface Definitions 6 | - [Resource](Resource.md) 7 | - [Entity](Entity.md) 8 | - [SimpleRecord](SimpleRecord.md) 9 | - [FetchShape](FetchShape.md) 10 | 11 | ### Hierarchy 12 | 13 | ``` 14 | SimpleRecord 15 | | 16 | Entity 17 | | 18 | SimpleResource 19 | | 20 | Resource 21 | ``` 22 | 23 | ## Hooks 24 | - [useResource](useResource.md) 25 | - [useFetcher](useFetcher.md) 26 | - [useCache](useCache.md) 27 | - [useSubscription](useSubscription.md) 28 | - [useRetrieve](useRetrieve.md) 29 | - [useInvalidator](useInvalidator.md) 30 | - [useResetter](useResetter.md) 31 | 32 | ## Components 33 | - [CacheProvider](CacheProvider.md) 34 | - [ExternalCacheProvider](ExternalCacheProvider.md) 35 | - [NetworkErrorBoundary](NetworkErrorBoundary.md) 36 | 37 | ## [Manager](Manager.md)s 38 | 39 | Extended the networking/state layer works through [managers](Manager.md). 40 | 41 | - [NetworkManager](NetworkManager.md) 42 | - [SubscriptionManager](SubscriptionManager.md) 43 | - [PollingSubscription](PollingSubscription.md) 44 | 45 | ## Testing 46 | 47 | Testing utilities are imported from `@rest-hooks/test`. These are useful 48 | helpers to ensure your code works as intended and are not meant to be shipped 49 | to production themselves. 50 | 51 | - [\](MockProvider) 52 | - [makeRenderRestHook()](makeRenderRestHook) 53 | - [makeCacheProvider()](makeCacheProvider) 54 | - [makeExternalCacheProvider()](makeExternalCacheProvider) 55 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/__tests__/__snapshots__/useResource.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`useResource() should dispatch an action that fetches 1`] = ` 4 | Object { 5 | "meta": Object { 6 | "options": undefined, 7 | "reject": [Function], 8 | "resolve": [Function], 9 | "responseType": "rest-hooks/receive", 10 | "schema": [Function], 11 | "throttle": true, 12 | "url": "GET http://test.com/article-cooler/5", 13 | }, 14 | "payload": [Function], 15 | "type": "rest-hooks/fetch", 16 | } 17 | `; 18 | 19 | exports[`useResource() should dispatch fetch when sent multiple arguments 1`] = ` 20 | Object { 21 | "meta": Object { 22 | "options": undefined, 23 | "reject": [Function], 24 | "resolve": [Function], 25 | "responseType": "rest-hooks/receive", 26 | "schema": [Function], 27 | "throttle": true, 28 | "url": "GET http://test.com/article-cooler/5", 29 | }, 30 | "payload": [Function], 31 | "type": "rest-hooks/fetch", 32 | } 33 | `; 34 | 35 | exports[`useResource() should dispatch fetch when sent multiple arguments 2`] = ` 36 | Object { 37 | "meta": Object { 38 | "options": undefined, 39 | "reject": [Function], 40 | "resolve": [Function], 41 | "responseType": "rest-hooks/receive", 42 | "schema": Array [ 43 | [Function], 44 | ], 45 | "throttle": true, 46 | "url": "GET http://test.com/user/", 47 | }, 48 | "payload": [Function], 49 | "type": "rest-hooks/fetch", 50 | } 51 | `; 52 | -------------------------------------------------------------------------------- /packages/normalizr/examples/relationships/input.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "title": "My first post!", 5 | "author": { 6 | "id": "123", 7 | "name": "Paul" 8 | }, 9 | "comments": [ 10 | { 11 | "id": "249", 12 | "content": "Nice post!", 13 | "commenter": { 14 | "id": "245", 15 | "name": "Jane" 16 | } 17 | }, 18 | { 19 | "id": "250", 20 | "content": "Thanks!", 21 | "commenter": { 22 | "id": "123", 23 | "name": "Paul" 24 | } 25 | } 26 | ] 27 | }, 28 | { 29 | "id": "2", 30 | "title": "This other post", 31 | "author": { 32 | "id": "123", 33 | "name": "Paul" 34 | }, 35 | "comments": [ 36 | { 37 | "id": "251", 38 | "content": "Your other post was nicer", 39 | "commenter": { 40 | "id": "245", 41 | "name": "Jane" 42 | } 43 | }, 44 | { 45 | "id": "252", 46 | "content": "I am a spammer!", 47 | "commenter": { 48 | "id": "246", 49 | "name": "Spambot5000" 50 | } 51 | } 52 | ] 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /docs/api/CacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | --- 4 | 5 | ```typescript 6 | interface ProviderProps { 7 | children: ReactNode; 8 | managers: Manager[]; 9 | initialState: State; 10 | } 11 | ``` 12 | 13 | Manages state, providing all context needed to use the hooks. Should be placed as high as possible 14 | in application tree as any usage of the hooks is only possible for components below the provider 15 | in the React tree. 16 | 17 | `index.tsx` 18 | 19 | ```tsx 20 | import { CacheProvider } from 'rest-hooks'; 21 | import ReactDOM from 'react-dom'; 22 | 23 | ReactDOM.render( 24 | 25 | 26 | , 27 | document.body 28 | ); 29 | ``` 30 | 31 | ## initialState: State 32 | 33 | ```typescript 34 | type State = Readonly<{ 35 | entities: Readonly<{ [fetchKey: string]: { [pk: string]: T } | undefined }>; 36 | results: Readonly<{ [url: string]: unknown | PK[] | PK | undefined }>; 37 | meta: Readonly<{ 38 | [url: string]: { date: number; error?: Error; expiresAt: number }; 39 | }>; 40 | }>; 41 | ``` 42 | 43 | Instead of starting with an empty cache, you can provide your own initial state. This can 44 | be useful for testing, or rehydrating the cache state when using server side rendering. 45 | 46 | ## managers: Manager[] 47 | 48 | Default: 49 | 50 | ```typescript 51 | [new NetworkManager(), new SubscriptionManager(PollingSubscription)] 52 | ``` 53 | 54 | List of [Manager](./Manager)s use. This is the main extensibility point of the provider. 55 | -------------------------------------------------------------------------------- /docs/guides/fetch-multiple.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fetching multiple resources at once 3 | --- 4 | ## Parallel 5 | 6 | If you have the parameters you needs to fetch, they will all happen in parallel! 7 | 8 | ```tsx 9 | import React from "react"; 10 | import { useResource } from "rest-hooks"; 11 | import { PostResource, TaskResource } from "./resources"; 12 | 13 | export default function Post({ name }: { name: string }) { 14 | const [post, tasks] = useResource( 15 | [PostResource.detailShape(), { name }], 16 | [TaskResource.detailShape(), { name }], 17 | ); 18 | return ( 19 |
20 | 21 | 22 |
23 | ); 24 | } 25 | ``` 26 | 27 | ## Sequential 28 | 29 | Each [useResource()](../api/useResource.md) call ensures the resource returned is available. That means 30 | that until that point it will yield running the rest of the component function 31 | when it is loading or errored. 32 | 33 | ```tsx 34 | import React from "react"; 35 | import { useResource } from "rest-hooks"; 36 | import { PostResource, UserResource } from "./resources"; 37 | 38 | export default function Post({ id }: { id: number }) { 39 | const post = useResource(PostResource.detailShape(), { id }); 40 | const author = useResource( 41 | UserResource.detailShape(), 42 | { 43 | id: post.userId 44 | } 45 | ); 46 | return ( 47 |
48 |

49 | {post.title} by {author && author.name} 50 |

51 |

{post.body}

52 |
53 | ); 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /website/pages/en/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require('react'); 9 | 10 | const CompLibrary = require('../../core/CompLibrary.js'); 11 | 12 | const Container = CompLibrary.Container; 13 | 14 | class Users extends React.Component { 15 | render() { 16 | const {config: siteConfig} = this.props; 17 | if ((siteConfig.users || []).length === 0) { 18 | return null; 19 | } 20 | 21 | const editUrl = `${siteConfig.repoUrl}/edit/master/website/siteConfig.js`; 22 | const showcase = siteConfig.users.map(user => ( 23 | 24 | {user.caption} 25 | 26 | )); 27 | 28 | return ( 29 |
30 | 31 |
32 |
33 |

Who is Using This?

34 |

This project is used by many folks

35 |
36 |
{showcase}
37 |

Are you using this project?

38 | 39 | Add your company 40 | 41 |
42 |
43 |
44 | ); 45 | } 46 | } 47 | 48 | module.exports = Users; 49 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/ExternalCacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | id: version-1.6.9-ExternalCacheProvider 4 | original_id: ExternalCacheProvider 5 | --- 6 | Integrates external stores with `rest-hooks`. Should be placed as high as possible 7 | in application tree as any usage of the hooks is only possible for components below the provider 8 | in the React tree. 9 | 10 | **Is a replacement for \ - do _NOT_ use both at once** 11 | 12 | `index.tsx` 13 | 14 | ```tsx 15 | import { ExternalCacheProvider } from 'rest-hooks'; 16 | import ReactDOM from 'react-dom'; 17 | 18 | import { store, selector } from './store'; 19 | 20 | ReactDOM.render( 21 | 22 | 23 | , 24 | document.body, 25 | ); 26 | ``` 27 | 28 | See [redux example](../guides/redux.md) for a more complete example. 29 | 30 | ## store 31 | 32 | ```typescript 33 | interface Store { 34 | subscribe(listener: () => void): () => void; 35 | dispatch: React.Dispatch; 36 | getState(): S; 37 | } 38 | ``` 39 | 40 | Store simply needs to conform to this interface. A common implementation is a [redux store](https://redux.js.org/api/store), 41 | but theoretically any external store could be used. 42 | 43 | [Read more about integrating redux.](../guides/redux.md) 44 | 45 | ## selector 46 | 47 | ```typescript 48 | (state: S) => State 49 | ``` 50 | 51 | This function is used to retrieve the `rest-hooks` specific part of the store's state tree. 52 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/api/ExternalCacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | id: version-2.0-ExternalCacheProvider 4 | original_id: ExternalCacheProvider 5 | --- 6 | Integrates external stores with `rest-hooks`. Should be placed as high as possible 7 | in application tree as any usage of the hooks is only possible for components below the provider 8 | in the React tree. 9 | 10 | **Is a replacement for \ - do _NOT_ use both at once** 11 | 12 | `index.tsx` 13 | 14 | ```tsx 15 | import { ExternalCacheProvider } from 'rest-hooks'; 16 | import ReactDOM from 'react-dom'; 17 | 18 | import { store, selector } from './store'; 19 | 20 | ReactDOM.render( 21 | 22 | 23 | , 24 | document.body, 25 | ); 26 | ``` 27 | 28 | See [redux example](../guides/redux.md) for a more complete example. 29 | 30 | ## store 31 | 32 | ```typescript 33 | interface Store { 34 | subscribe(listener: () => void): () => void; 35 | dispatch: React.Dispatch; 36 | getState(): S; 37 | } 38 | ``` 39 | 40 | Store simply needs to conform to this interface. A common implementation is a [redux store](https://redux.js.org/api/store), 41 | but theoretically any external store could be used. 42 | 43 | [Read more about integrating redux.](../guides/redux.md) 44 | 45 | ## selector 46 | 47 | ```typescript 48 | (state: S) => State 49 | ``` 50 | 51 | This function is used to retrieve the `rest-hooks` specific part of the store's state tree. 52 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.1/api/useCache.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useCache() 3 | id: version-2.1-useCache 4 | original_id: useCache 5 | --- 6 | 7 | 8 | 9 | 10 | ```typescript 11 | function useCache( 12 | fetchShape: ReadShape, 13 | params: object | null 14 | ): SchemaOf | null; 15 | ``` 16 | 17 | 18 | 19 | ```typescript 20 | function useCache, S extends Schema>( 21 | { schema, getFetchKey }: ReadShape, 22 | params: Params | null 23 | ): SchemaOf | null; 24 | ``` 25 | 26 | 27 | 28 | Excellent to use data in the normalized cache without fetching. 29 | 30 | - [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 31 | - Returns previously cached if exists 32 | - null otherwise 33 | - While loading: 34 | - Returns previously cached if exists 35 | - null otherwise 36 | 37 | ## Example 38 | 39 | Using a type guard to deal with null 40 | 41 | ```tsx 42 | function Post({ id }: { id: number }) { 43 | const post = useCache(PostResource.detailShape(), { id }); 44 | // post as PostResource | null 45 | if (!post) return null; 46 | // post as PostResource (typeguarded) 47 | // ...render stuff here 48 | } 49 | ``` 50 | 51 | ## Useful `FetchShape`s to send 52 | 53 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 54 | 55 | - detailShape() 56 | - listShape() 57 | 58 | Feel free to add your own [FetchShape](./FetchShape.md) as well. 59 | -------------------------------------------------------------------------------- /docs/guides/binary-fetches.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fetching Media 3 | --- 4 | 5 | After setting up Rest Hooks for structured data fetching, you might want to incorporate 6 | some media fetches as well to take advantage of suspense and concurrent mode support. 7 | 8 | [Resource](../api/Resource) and [Entity](../api/Entity) should not be used in this case, since they both represent 9 | string -> value map structures. Instead, we'll define our own simple [FetchShape](../api/FetchShape) 10 | with a schema set to null, but with a type including what we expect in the response. 11 | 12 | Schemas with literal types like null simply pass through the response, but their value is 13 | used to construct responses when the data does not exist yet (like in [useCache](../api/useCache)) 14 | 15 | 16 | ```typescript 17 | export const photoShape = { 18 | type: 'read' as const, 19 | schema: null as ArrayBuffer | null, 20 | getFetchKey({ userId }: { userId: string }) { 21 | return `/users/${userId}/photo`; 22 | }, 23 | fetch: async ({ userId }: { userId: string }) => { 24 | const response = await fetch(`/users/${userId}/photo`); 25 | const photoArrayBuffer = await response.arrayBuffer(); 26 | 27 | return photoArrayBuffer; 28 | }, 29 | }; 30 | ``` 31 | 32 | ```tsx 33 | // photo is typed as null | ArrayBuffer, but should be an ArrayBuffer 34 | const photo = useResource(photoShape, { userId }); 35 | ``` 36 | 37 | ```tsx 38 | // photo will be null if the fetch hasn't completed 39 | // photo will be ArrayBuffer if the fetch has completed 40 | const photo = useCache(photoShape, { userId }); 41 | ``` 42 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/api/NetworkErrorBoundary.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | id: version-2.0-NetworkErrorBoundary 4 | original_id: NetworkErrorBoundary 5 | --- 6 | 7 | Displays a fallback component when a network error happens in its subtree. 8 | 9 | ```tsx 10 | interface Props { 11 | children: React.ReactNode; 12 | fallbackComponent: React.ComponentType<{ 13 | error: NetworkError; 14 | }>; 15 | } 16 | export default class NetworkErrorBoundary extends React.Component { 17 | static defaultProps: { 18 | fallbackComponent: ({ error }: { error: NetworkError }) => JSX.Element; 19 | }; 20 | } 21 | ``` 22 | 23 | Custom fallback usage example: 24 | 25 | ```tsx 26 | import React from 'react'; 27 | import { CacheProvider, NetworkErrorBoundary, NetworkError } from 'rest-hooks'; 28 | 29 | function ErrorPage({ error }: { error: NetworkError }) { 30 | return ( 31 |
32 | {error.status} {error.response && error.response.statusText} 33 |
34 | ); 35 | } 36 | 37 | export default function App(): React.ReactElement { 38 | return ( 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | } 46 | ``` 47 | 48 | Note: Once `` catches an error it will only render the fallback 49 | until it is remounted. To get around this you'll likely want to place the boundary at 50 | locations that will cause remounts when the error should be cleared. This is usually 51 | below the route itself. 52 | 53 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.1/api/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Reference 3 | id: version-4.1-README 4 | original_id: README 5 | --- 6 | 7 | ## Interface Definitions 8 | - [Resource](Resource.md) 9 | - [Entity](Entity.md) 10 | - [SimpleRecord](SimpleRecord.md) 11 | - [FetchShape](FetchShape.md) 12 | 13 | ### Hierarchy 14 | 15 | ``` 16 | SimpleRecord 17 | | 18 | Entity 19 | | 20 | SimpleResource 21 | | 22 | Resource 23 | ``` 24 | 25 | ## Hooks 26 | - [useResource](useResource.md) 27 | - [useFetcher](useFetcher.md) 28 | - [useCache](useCache.md) 29 | - [useSubscription](useSubscription.md) 30 | - [useRetrieve](useRetrieve.md) 31 | - [useInvalidator](useInvalidator.md) 32 | - [useResetter](useResetter.md) 33 | 34 | ## Components 35 | - [CacheProvider](CacheProvider.md) 36 | - [ExternalCacheProvider](ExternalCacheProvider.md) 37 | - [NetworkErrorBoundary](NetworkErrorBoundary.md) 38 | 39 | ## [Manager](Manager.md)s 40 | 41 | Extended the networking/state layer works through [managers](Manager.md). 42 | 43 | - [NetworkManager](NetworkManager.md) 44 | - [SubscriptionManager](SubscriptionManager.md) 45 | - [PollingSubscription](PollingSubscription.md) 46 | 47 | ## Testing 48 | 49 | Testing utilities are imported from `@rest-hooks/test`. These are useful 50 | helpers to ensure your code works as intended and are not meant to be shipped 51 | to production themselves. 52 | 53 | - [\](MockProvider) 54 | - [makeRenderRestHook()](makeRenderRestHook) 55 | - [makeCacheProvider()](makeCacheProvider) 56 | - [makeExternalCacheProvider()](makeExternalCacheProvider) 57 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/api/NetworkErrorBoundary.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | id: version-1.6.9-NetworkErrorBoundary 4 | original_id: NetworkErrorBoundary 5 | --- 6 | 7 | Displays a fallback component when a network error happens in its subtree. 8 | 9 | ```tsx 10 | interface Props { 11 | children: React.ReactNode; 12 | fallbackComponent: React.ComponentType<{ 13 | error: NetworkError; 14 | }>; 15 | } 16 | export default class NetworkErrorBoundary extends React.Component { 17 | static defaultProps: { 18 | fallbackComponent: ({ error }: { error: NetworkError }) => JSX.Element; 19 | }; 20 | } 21 | ``` 22 | 23 | Custom fallback usage example: 24 | 25 | ```tsx 26 | import React from 'react'; 27 | import { RestProvider, NetworkErrorBoundary, NetworkError } from 'rest-hooks'; 28 | 29 | function ErrorPage({ error }: { error: NetworkError }) { 30 | return ( 31 |
32 | {error.status} {error.response && error.response.statusText} 33 |
34 | ); 35 | } 36 | 37 | export default function App(): React.ReactElement { 38 | return ( 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | } 46 | ``` 47 | 48 | Note: Once `` catches an error it will only render the fallback 49 | until it is remounted. To get around this you'll likely want to place the boundary at 50 | locations that will cause remounts when the error should be cleared. This is usually 51 | below the route itself. 52 | 53 | -------------------------------------------------------------------------------- /packages/use-enhanced-reducer/src/usePromisifiedDispatch.ts: -------------------------------------------------------------------------------- 1 | import React, { useRef, useCallback, useEffect } from 'react'; 2 | 3 | type PromiseHolder = { promise: Promise; resolve: () => void }; 4 | 5 | /** Turns a dispatch function into one that resolves once its been commited */ 6 | export default function usePromisifiedDispatch< 7 | R extends React.Reducer 8 | >( 9 | dispatch: React.Dispatch>, 10 | state: React.ReducerState, 11 | ) { 12 | const dispatchPromiseRef = useRef(null); 13 | useEffect(() => { 14 | if (dispatchPromiseRef.current) { 15 | dispatchPromiseRef.current.resolve(); 16 | dispatchPromiseRef.current = null; 17 | } 18 | }, [state]); 19 | 20 | return useCallback( 21 | (action: React.ReducerAction) => { 22 | if (!dispatchPromiseRef.current) { 23 | dispatchPromiseRef.current = NewPromiseHolder(); 24 | } 25 | // we use the promise before dispatch so we know it will be resolved 26 | // however that can also make the ref clear, so we need to make sure we have to promise before 27 | // dispatching so we can return it even if the ref changes. 28 | const promise = dispatchPromiseRef.current.promise; 29 | dispatch(action); 30 | return promise; 31 | }, 32 | [dispatch], 33 | ); 34 | } 35 | 36 | function NewPromiseHolder(): PromiseHolder { 37 | // any so we can build it 38 | const promiseHolder: any = {}; 39 | promiseHolder.promise = new Promise(resolve => { 40 | promiseHolder.resolve = resolve; 41 | }); 42 | return promiseHolder; 43 | } 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/api/CacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | id: version-2.0-CacheProvider 4 | original_id: CacheProvider 5 | --- 6 | 7 | ```typescript 8 | interface ProviderProps { 9 | children: ReactNode; 10 | managers: Manager[]; 11 | initialState: State; 12 | } 13 | ``` 14 | 15 | Manages state, providing all context needed to use the hooks. Should be placed as high as possible 16 | in application tree as any usage of the hooks is only possible for components below the provider 17 | in the React tree. 18 | 19 | `index.tsx` 20 | 21 | ```tsx 22 | import { CacheProvider } from 'rest-hooks'; 23 | import ReactDOM from 'react-dom'; 24 | 25 | ReactDOM.render( 26 | 27 | 28 | , 29 | document.body 30 | ); 31 | ``` 32 | 33 | ## initialState: State 34 | 35 | ```typescript 36 | type State = Readonly<{ 37 | entities: Readonly<{ [k: string]: { [id: string]: T } | undefined }>; 38 | results: Readonly<{ [url: string]: unknown | PK[] | PK | undefined }>; 39 | meta: Readonly<{ 40 | [url: string]: { date: number; error?: Error; expiresAt: number }; 41 | }>; 42 | }>; 43 | ``` 44 | 45 | Instead of starting with an empty cache, you can provide your own initial state. This can 46 | be useful for testing, or rehydrating the cache state when using server side rendering. 47 | 48 | ## managers: Manager[] 49 | 50 | Default: 51 | 52 | ```typescript 53 | [new NetworkManager(), new SubscriptionManager(PollingSubscription)] 54 | ``` 55 | 56 | List of [Manager](./Manager)s use. This is the main extensibility point of the provider. 57 | -------------------------------------------------------------------------------- /website/versioned_docs/version-3.0/api/CacheProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | id: version-3.0-CacheProvider 4 | original_id: CacheProvider 5 | --- 6 | 7 | ```typescript 8 | interface ProviderProps { 9 | children: ReactNode; 10 | managers: Manager[]; 11 | initialState: State; 12 | } 13 | ``` 14 | 15 | Manages state, providing all context needed to use the hooks. Should be placed as high as possible 16 | in application tree as any usage of the hooks is only possible for components below the provider 17 | in the React tree. 18 | 19 | `index.tsx` 20 | 21 | ```tsx 22 | import { CacheProvider } from 'rest-hooks'; 23 | import ReactDOM from 'react-dom'; 24 | 25 | ReactDOM.render( 26 | 27 | 28 | , 29 | document.body 30 | ); 31 | ``` 32 | 33 | ## initialState: State 34 | 35 | ```typescript 36 | type State = Readonly<{ 37 | entities: Readonly<{ [fetchKey: string]: { [pk: string]: T } | undefined }>; 38 | results: Readonly<{ [url: string]: unknown | PK[] | PK | undefined }>; 39 | meta: Readonly<{ 40 | [url: string]: { date: number; error?: Error; expiresAt: number }; 41 | }>; 42 | }>; 43 | ``` 44 | 45 | Instead of starting with an empty cache, you can provide your own initial state. This can 46 | be useful for testing, or rehydrating the cache state when using server side rendering. 47 | 48 | ## managers: Manager[] 49 | 50 | Default: 51 | 52 | ```typescript 53 | [new NetworkManager(), new SubscriptionManager(PollingSubscription)] 54 | ``` 55 | 56 | List of [Manager](./Manager)s use. This is the main extensibility point of the provider. 57 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/guides/fetch-multiple.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fetching multiple resources at once 3 | id: version-2.0-fetch-multiple 4 | original_id: fetch-multiple 5 | --- 6 | ## Parallel 7 | 8 | If you have the parameters you needs to fetch, they will all happen in parallel! 9 | 10 | ```tsx 11 | import React from "react"; 12 | import { useResource } from "rest-hooks"; 13 | import { PostResource, TaskResource } from "./resources"; 14 | 15 | export default function Post({ name }: { name: string }) { 16 | const [post, tasks] = useResource( 17 | [PostResource.detailShape(), { name }], 18 | [TaskResource.detailShape(), { name }], 19 | ); 20 | return ( 21 |
22 | 23 | 24 |
25 | ); 26 | } 27 | ``` 28 | 29 | ## Sequential 30 | 31 | Each [useResource()](../api/useResource.md) call ensures the resource returned is available. That means 32 | that until that point it will yield running the rest of the component function 33 | when it is loading or errored. 34 | 35 | ```tsx 36 | import React from "react"; 37 | import { useResource } from "rest-hooks"; 38 | import { PostResource, UserResource } from "./resources"; 39 | 40 | export default function Post({ id }: { id: number }) { 41 | const post = useResource(PostResource.detailShape(), { id }); 42 | const author = useResource( 43 | UserResource.detailShape(), 44 | { 45 | id: post.userId 46 | } 47 | ); 48 | return ( 49 |
50 |

51 | {post.title} by {author && author.name} 52 |

53 |

{post.body}

54 |
55 | ); 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /website/versioned_docs/version-1.6.9/guides/fetch-multiple.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fetching multiple resources at once 3 | id: version-1.6.9-fetch-multiple 4 | original_id: fetch-multiple 5 | --- 6 | ## Parallel 7 | 8 | If you have the parameters you needs to fetch, they will all happen in parallel! 9 | 10 | ```tsx 11 | import React from "react"; 12 | import { useResource } from "rest-hooks"; 13 | import { PostResource, TaskResource } from "./resources"; 14 | 15 | export default function Post({ name }: { name: string }) { 16 | const [post, tasks] = useResource( 17 | [PostResource.singleRequest(), { name }], 18 | [TaskResource.singleRequest(), { name }], 19 | ); 20 | return ( 21 |
22 | 23 | 24 |
25 | ); 26 | } 27 | ``` 28 | 29 | ## Sequential 30 | 31 | Each [useResource()](../api/useResource.md) call ensures the resource returned is available. That means 32 | that until that point it will yield running the rest of the component function 33 | when it is loading or errored. 34 | 35 | ```tsx 36 | import React from "react"; 37 | import { useResource } from "rest-hooks"; 38 | import { PostResource, UserResource } from "./resources"; 39 | 40 | export default function Post({ id }: { id: number }) { 41 | const post = useResource(PostResource.singleRequest(), { id }); 42 | const author = useResource( 43 | UserResource.singleRequest(), 44 | { 45 | id: post.userId 46 | } 47 | ); 48 | return ( 49 |
50 |

51 | {post.title} by {author && author.name} 52 |

53 |

{post.body}

54 |
55 | ); 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /packages/normalizr/docs/quickstart.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | Consider a typical blog post. The API response for a single post might look something like this: 4 | 5 | ```json 6 | { 7 | "id": "123", 8 | "author": { 9 | "id": "1", 10 | "name": "Paul" 11 | }, 12 | "title": "My awesome blog post", 13 | "comments": [ 14 | { 15 | "id": "324", 16 | "commenter": { 17 | "id": "2", 18 | "name": "Nicole" 19 | } 20 | } 21 | ] 22 | } 23 | ``` 24 | 25 | We have two nested entity types within our `article`: `users` and `comments`. Using various `schema`, we can normalize all three entity types down: 26 | 27 | ```js 28 | import { normalize, schema } from '@rest-hooks/normalizr'; 29 | 30 | // Define a users schema 31 | const user = new schema.Entity('users'); 32 | 33 | // Define your comments schema 34 | const comment = new schema.Entity('comments', { 35 | commenter: user 36 | }); 37 | 38 | // Define your article 39 | const article = new schema.Entity('articles', { 40 | author: user, 41 | comments: [comment] 42 | }); 43 | 44 | const normalizedData = normalize(originalData, article); 45 | ``` 46 | 47 | Now, `normalizedData` will be: 48 | 49 | ```js 50 | { 51 | result: "123", 52 | entities: { 53 | "articles": { 54 | "123": { 55 | id: "123", 56 | author: "1", 57 | title: "My awesome blog post", 58 | comments: [ "324" ] 59 | } 60 | }, 61 | "users": { 62 | "1": { "id": "1", "name": "Paul" }, 63 | "2": { "id": "2", "name": "Nicole" } 64 | }, 65 | "comments": { 66 | "324": { id: "324", "commenter": "2" } 67 | } 68 | } 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /packages/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rest-hooks/test", 3 | "version": "1.0.15", 4 | "description": "Testing utilities for Rest Hooks", 5 | "sideEffects": false, 6 | "main": "dist/index.cjs.js", 7 | "types": "dist/index.d.ts", 8 | "files": [ 9 | "src", 10 | "dist", 11 | "LICENSE", 12 | "README.md" 13 | ], 14 | "scripts": { 15 | "build:bundle": "rollup -c", 16 | "build:clean": "rimraf dist *.tsbuildinfo", 17 | "build": "yarn run build:bundle", 18 | "prepublishOnly": "yarn run build" 19 | }, 20 | "keywords": [ 21 | "test", 22 | "storybook", 23 | "rest hooks", 24 | "react", 25 | "networking", 26 | "suspense", 27 | "concurrent mode", 28 | "fetch", 29 | "hook", 30 | "typescript", 31 | "redux", 32 | "data fetching", 33 | "data cache", 34 | "normalized cache", 35 | "swr" 36 | ], 37 | "author": "Nathaniel Tucker (https://github.com/ntucker)", 38 | "license": "Apache-2.0", 39 | "homepage": "https://resthooks.io/docs/guides/storybook", 40 | "repository": { 41 | "type": "git", 42 | "url": "git+ssh://git@github.com:coinbase/rest-hooks.git", 43 | "directory": "packages/test" 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/coinbase/rest-hooks/issues" 47 | }, 48 | "dependencies": { 49 | "@testing-library/react-hooks": "^3.2.1" 50 | }, 51 | "peerDependencies": { 52 | "@types/react": "^16.8.2", 53 | "react": "^16.8.2", 54 | "redux": "^4.0.0", 55 | "rest-hooks": "^3.0.2 || ^4.0.0-beta.1" 56 | }, 57 | "peerDependenciesMeta": { 58 | "redux": { 59 | "optional": true 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /website/versioned_docs/version-4.5/guides/binary-fetches.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fetching Media 3 | id: version-4.5-binary-fetches 4 | original_id: binary-fetches 5 | --- 6 | 7 | After setting up Rest Hooks for structured data fetching, you might want to incorporate 8 | some media fetches as well to take advantage of suspense and concurrent mode support. 9 | 10 | [Resource](../api/Resource) and [Entity](../api/Entity) should not be used in this case, since they both represent 11 | string -> value map structures. Instead, we'll define our own simple [FetchShape](../api/FetchShape) 12 | with a schema set to null, but with a type including what we expect in the response. 13 | 14 | Schemas with literal types like null simply pass through the response, but their value is 15 | used to construct responses when the data does not exist yet (like in [useCache](../api/useCache)) 16 | 17 | 18 | ```typescript 19 | export const photoShape = { 20 | type: 'read' as const, 21 | schema: null as ArrayBuffer | null, 22 | getFetchKey({ userId }: { userId: string }) { 23 | return `/users/${userId}/photo`; 24 | }, 25 | fetch: async ({ userId }: { userId: string }) => { 26 | const response = await fetch(`/users/${userId}/photo`); 27 | const photoArrayBuffer = await response.arrayBuffer(); 28 | 29 | return photoArrayBuffer; 30 | }, 31 | }; 32 | ``` 33 | 34 | ```tsx 35 | // photo is typed as null | ArrayBuffer, but should be an ArrayBuffer 36 | const photo = useResource(photoShape, { userId }); 37 | ``` 38 | 39 | ```tsx 40 | // photo will be null if the fetch hasn't completed 41 | // photo will be ArrayBuffer if the fetch has completed 42 | const photo = useCache(photoShape, { userId }); 43 | ``` 44 | -------------------------------------------------------------------------------- /packages/normalizr/src/schemas/ImmutableUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helpers to enable Immutable compatibility *without* bringing in 3 | * the 'immutable' package as a dependency. 4 | */ 5 | 6 | /** 7 | * Check if an object is immutable by checking if it has a key specific 8 | * to the immutable library. 9 | * 10 | * @param {any} object 11 | * @return {bool} 12 | */ 13 | export function isImmutable(object) { 14 | return !!( 15 | object && 16 | typeof object.hasOwnProperty === 'function' && 17 | (Object.hasOwnProperty.call(object, '__ownerID') || // Immutable.Map 18 | (object._map && Object.hasOwnProperty.call(object._map, '__ownerID'))) 19 | ); // Immutable.Record 20 | } 21 | 22 | /** 23 | * Denormalize an immutable entity. 24 | * 25 | * @param {Schema} schema 26 | * @param {Immutable.Map|Immutable.Record} input 27 | * @param {function} unvisit 28 | * @param {function} getDenormalizedEntity 29 | * @return {Immutable.Map|Immutable.Record} 30 | */ 31 | export function denormalizeImmutable(schema, input, unvisit) { 32 | let found = true; 33 | return [ 34 | Object.keys(schema).reduce((object, key) => { 35 | // Immutable maps cast keys to strings on write so we need to ensure 36 | // we're accessing them using string keys. 37 | const stringKey = `${key}`; 38 | 39 | const [item, foundItem] = unvisit( 40 | object.get(stringKey), 41 | schema[stringKey], 42 | ); 43 | if (!foundItem) { 44 | found = false; 45 | } 46 | if (object.has(stringKey)) { 47 | return object.set(stringKey, item); 48 | } else { 49 | return object; 50 | } 51 | }, input), 52 | found, 53 | ]; 54 | } 55 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.0/api/useRetrieve.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useRetrieve() 3 | id: version-2.0-useRetrieve 4 | original_id: useRetrieve 5 | --- 6 | ```typescript 7 | function useRetrieve< 8 | Params extends Readonly, 9 | Body extends Readonly | void, 10 | S extends Schema 11 | >( 12 | fetchShape: ReadShape, 13 | params: Params | null, 14 | body?: Body, 15 | ): Promise | undefined; 16 | ``` 17 | 18 | Great for retrieving resources optimistically before they are needed. 19 | 20 | This can be useful for ensuring resources early in a render tree before they are needed. 21 | 22 | - Triggers fetch: 23 | - On first-render and when parameters change 24 | - and When not in cache or result is considered stale 25 | - and When no identical requests are in flight 26 | - and when params are not null 27 | - [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 28 | - Returned promise will reject 29 | - On fetch returns a promise else undefined. 30 | 31 | ## Example 32 | 33 | ### Simple 34 | 35 | ```tsx 36 | function MasterPost({ id }: { id: number }) { 37 | useRetrieve(PostResource.detailShape(), { id }); 38 | // ... 39 | } 40 | ``` 41 | 42 | ### Conditional 43 | 44 | ```tsx 45 | function MasterPost({ id, doNotFetch }: { id: number, doNotFetch: boolean }) { 46 | useRetrieve(PostResource.detailShape(), doNotFetch ? null : { id }); 47 | // ... 48 | } 49 | ``` 50 | 51 | ## Useful `FetchShape`s to send 52 | 53 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 54 | 55 | - detailShape() 56 | - listShape() 57 | 58 | Feel free to add your own [FetchShape](./FetchShape.md) as well. 59 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/react-integration/provider/ExternalCacheProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | StateContext, 3 | DispatchContext, 4 | } from 'rest-hooks/react-integration/context'; 5 | import masterReducer from 'rest-hooks/state/reducer'; 6 | import { State, ActionTypes } from 'rest-hooks/types'; 7 | import { usePromisifiedDispatch } from '@rest-hooks/use-enhanced-reducer'; 8 | import React, { ReactNode, useEffect, useState, useMemo } from 'react'; 9 | 10 | interface Store { 11 | subscribe(listener: () => void): () => void; 12 | dispatch: React.Dispatch; 13 | getState(): S; 14 | } 15 | interface Props { 16 | children: ReactNode; 17 | store: Store; 18 | selector: (state: S) => State; 19 | } 20 | 21 | export default function ExternalCacheProvider({ 22 | children, 23 | store, 24 | selector, 25 | }: Props) { 26 | const [state, setState] = useState(() => selector(store.getState())); 27 | 28 | const optimisticState = useMemo( 29 | () => state.optimistic.reduce(masterReducer, state), 30 | [state], 31 | ); 32 | 33 | useEffect(() => { 34 | const unsubscribe = store.subscribe(() => { 35 | setState(selector(store.getState())); 36 | }); 37 | return unsubscribe; 38 | // we don't care to recompute if they change selector - only when store updates 39 | // eslint-disable-next-line react-hooks/exhaustive-deps 40 | }, [store]); 41 | 42 | const dispatch = usePromisifiedDispatch(store.dispatch, state); 43 | 44 | return ( 45 | 46 | 47 | {children} 48 | 49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/resource/normal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | schema as schemas, 3 | Schema, 4 | Denormalize as DenormalizeCore, 5 | DenormalizeNullable as DenormalizeNullableCore, 6 | Normalize as NormalizeCore, 7 | NormalizeNullable as NormalizeNullableCore, 8 | } from '@rest-hooks/normalizr'; 9 | export * from '@rest-hooks/normalizr'; 10 | export { schemas }; 11 | 12 | export type SchemaDetail = 13 | | schemas.Entity 14 | | { [K: string]: any } 15 | | schemas.SchemaClass; 16 | 17 | export type SchemaList = 18 | | schemas.Entity[] 19 | | { [K: string]: any } 20 | | Schema[] 21 | | schemas.SchemaClass; 22 | 23 | export type Denormalize = Extract extends never 24 | ? Extract extends never 25 | ? DenormalizeCore 26 | : DenormalizeCore> 27 | : DenormalizeCore>; 28 | 29 | export type DenormalizeNullable = Extract extends never 30 | ? Extract extends never 31 | ? DenormalizeNullableCore 32 | : DenormalizeNullableCore> 33 | : DenormalizeNullableCore>; 34 | 35 | export type Normalize = Extract extends never 36 | ? Extract extends never 37 | ? NormalizeCore 38 | : NormalizeCore> 39 | : NormalizeCore>; 40 | 41 | export type NormalizeNullable = Extract extends never 42 | ? Extract extends never 43 | ? NormalizeNullableCore 44 | : NormalizeNullableCore> 45 | : NormalizeNullableCore>; 46 | -------------------------------------------------------------------------------- /packages/rest-hooks/src/resource/shapes.ts: -------------------------------------------------------------------------------- 1 | import { FetchOptions } from 'rest-hooks/types'; 2 | 3 | import { schemas, Schema } from './normal'; 4 | 5 | /** Defines the shape of a network request */ 6 | export interface FetchShape< 7 | S extends Schema, 8 | Params extends Readonly = Readonly, 9 | Body extends Readonly | void = 10 | | Readonly 11 | | undefined 12 | > { 13 | readonly type: 'read' | 'mutate' | 'delete'; 14 | fetch(params: Params, body: Body): Promise; 15 | getFetchKey(params: Params): string; 16 | readonly schema: S; 17 | readonly options?: FetchOptions; 18 | } 19 | 20 | /** Purges a value from the server */ 21 | export interface DeleteShape< 22 | S extends schemas.Entity, 23 | Params extends Readonly = Readonly, 24 | Body extends Readonly | void = undefined 25 | > extends FetchShape { 26 | readonly type: 'delete'; 27 | } 28 | 29 | /** To change values on the server */ 30 | export interface MutateShape< 31 | S extends Schema, 32 | Params extends Readonly = Readonly, 33 | Body extends Readonly | void = 34 | | Readonly 35 | | undefined 36 | > extends FetchShape { 37 | readonly type: 'mutate'; 38 | fetch( 39 | params: Params, 40 | body: Body, 41 | ): Promise; 42 | } 43 | 44 | /** For retrieval requests */ 45 | export interface ReadShape< 46 | S extends Schema, 47 | Params extends Readonly = Readonly 48 | > extends FetchShape { 49 | readonly type: 'read'; 50 | fetch(params: Params): Promise; 51 | } 52 | -------------------------------------------------------------------------------- /packages/test/src/makeRenderRestHook.tsx: -------------------------------------------------------------------------------- 1 | import { State, SubscriptionManager, Manager } from 'rest-hooks'; 2 | 3 | import React from 'react'; 4 | import { renderHook, RenderHookOptions } from '@testing-library/react-hooks'; 5 | 6 | import { MockNetworkManager, MockPollingSubscription } from './managers'; 7 | import mockInitialState, { Fixture } from './mockState'; 8 | 9 | export default function makeRenderRestHook( 10 | makeProvider: ( 11 | managers: Manager[], 12 | initialState?: State, 13 | ) => React.ComponentType<{ children: React.ReactChild }>, 14 | ) { 15 | const manager = new MockNetworkManager(); 16 | const subManager = new SubscriptionManager(MockPollingSubscription); 17 | function renderRestHook( 18 | callback: (props: P) => R, 19 | options?: { 20 | initialProps?: P; 21 | results?: Fixture[]; 22 | } & RenderHookOptions

, 23 | ) { 24 | const initialState = 25 | options && options.results && mockInitialState(options.results); 26 | const Provider: React.ComponentType = makeProvider( 27 | [manager, subManager], 28 | initialState, 29 | ); 30 | const Wrapper = options && options.wrapper; 31 | const wrapper: React.ComponentType = Wrapper 32 | ? function ProviderWrapped({ children }: { children: React.ReactChild }) { 33 | return ( 34 | 35 | {children} 36 | 37 | ); 38 | } 39 | : Provider; 40 | return renderHook(callback, { 41 | ...options, 42 | wrapper, 43 | }); 44 | } 45 | renderRestHook.cleanup = () => { 46 | manager.cleanup(); 47 | subManager.cleanup(); 48 | }; 49 | return renderRestHook; 50 | } 51 | -------------------------------------------------------------------------------- /website/pages/en/help.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require('react'); 9 | 10 | const CompLibrary = require('../../core/CompLibrary.js'); 11 | 12 | const Container = CompLibrary.Container; 13 | const GridBlock = CompLibrary.GridBlock; 14 | 15 | function Help(props) { 16 | const {config: siteConfig, language = ''} = props; 17 | const {baseUrl, docsUrl} = siteConfig; 18 | const docsPart = `${docsUrl ? `${docsUrl}/` : ''}`; 19 | const langPart = `${language ? `${language}/` : ''}`; 20 | const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`; 21 | 22 | const supportLinks = [ 23 | { 24 | content: `Learn more using the [documentation on this site.](${docUrl( 25 | 'doc1.html', 26 | )})`, 27 | title: 'Browse Docs', 28 | }, 29 | { 30 | content: 'Ask questions about the documentation and project', 31 | title: 'Join the community', 32 | }, 33 | { 34 | content: "Find out what's new with this project", 35 | title: 'Stay up to date', 36 | }, 37 | ]; 38 | 39 | return ( 40 |

41 | 42 |
43 |
44 |

Need help?

45 |
46 |

This project is maintained by a dedicated group of people.

47 | 48 |
49 |
50 |
51 | ); 52 | } 53 | 54 | module.exports = Help; 55 | -------------------------------------------------------------------------------- /website/versioned_docs/version-3.0/api/useCacheLegacy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useCacheLegacy() 3 | id: version-3.0-useCacheLegacy 4 | original_id: useCacheLegacy 5 | --- 6 | 7 | 8 | 9 | 10 | ```typescript 11 | function useCacheLegacy( 12 | fetchShape: ReadShape, 13 | params: object | null 14 | ): SchemaOf | null; 15 | ``` 16 | 17 | 18 | 19 | ```typescript 20 | function useCacheLegacy, S extends Schema>( 21 | { schema, getFetchKey }: ReadShape, 22 | params: Params | null 23 | ): SchemaOf | null; 24 | ``` 25 | 26 | 27 | 28 | > ### Rest Hooks 3.1 - Removal 29 | > 30 | > This hook is deprecated in favor of [useCache()](./useCache) 31 | > 32 | > - 3.1 will remove `useCacheLegacy()` 33 | 34 | Excellent to use data in the normalized cache without fetching. 35 | 36 | - [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 37 | - Returns previously cached if exists 38 | - null otherwise 39 | - While loading: 40 | - Returns previously cached if exists 41 | - null otherwise 42 | 43 | ## Example 44 | 45 | Using a type guard to deal with null 46 | 47 | ```tsx 48 | function Post({ id }: { id: number }) { 49 | const post = useCacheLegacy(PostResource.detailShape(), { id }); 50 | // post as PostResource | null 51 | if (!post) return null; 52 | // post as PostResource (typeguarded) 53 | // ...render stuff here 54 | } 55 | ``` 56 | 57 | ## Useful `FetchShape`s to send 58 | 59 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 60 | 61 | - detailShape() 62 | - listShape() 63 | 64 | Feel free to add your own [FetchShape](./FetchShape.md) as well. 65 | -------------------------------------------------------------------------------- /docs/api/MockProvider.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | --- 4 | 5 | ```typescript 6 | function MockProvider({ 7 | children, 8 | results, 9 | }: { 10 | children: React.ReactChild; 11 | results: Fixture[]; 12 | }): JSX.Element; 13 | ``` 14 | 15 | \ is a simple substitute provider to prefill the cache with fixtures so the 'happy path' 16 | can be tested. This is useful for [storybook](../guides/storybook.md) as well as component testing. 17 | 18 | ## Arguments 19 | 20 | ### results 21 | 22 | ```typescript 23 | interface Fixture { 24 | request: ReadShape; 25 | params: object; 26 | result: object | string | number; 27 | } 28 | ``` 29 | 30 | This prop specifies the fixtures to use data from. Each item represents a fetch defined by the 31 | [FetchShape](./FetchShape.md) and params. `Result` contains the JSON response expected from said fetch. 32 | 33 | ## Returns 34 | 35 | ```typescript 36 | JSX.Element 37 | ``` 38 | 39 | Renders the children prop. 40 | 41 | ## Example 42 | 43 | ```typescript 44 | import { MockProvider } from '@rest-hooks/test'; 45 | 46 | import ArticleResource from 'resources/ArticleResource'; 47 | import MyComponentToTest from 'components/MyComponentToTest'; 48 | 49 | const results = [ 50 | { 51 | request: ArticleResource.listShape(), 52 | params: { maxResults: 10 }, 53 | result: [ 54 | { 55 | id: 5, 56 | content: 'have a merry christmas', 57 | author: 2, 58 | contributors: [], 59 | }, 60 | { 61 | id: 532, 62 | content: 'never again', 63 | author: 23, 64 | contributors: [5], 65 | }, 66 | ], 67 | }, 68 | ]; 69 | 70 | 71 | 72 | 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/guides/network-errors.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dealing with network errors 3 | --- 4 | 5 | When you use the `useResource()` hook, React will suspend rendering while the network 6 | request takes place. But what happens if there is a network failure? It will 7 | throw the network error. When this happens you'll want to have an 8 | [error boundary](https://reactjs.org/docs/error-boundaries.html) set up to handle it. 9 | Most likely you'll want to place one specficially for network errors at the same place 10 | you put your ``. What you do with the error once you catch it, is of course 11 | up to you. 12 | 13 | ## Error Boundary 14 | 15 | This library provides `NetworkErrorBoundary` component that only catches network 16 | errors and sends them to a fallback component you provide. Other errors will rethrow. 17 | 18 | ```tsx 19 | import { Suspense } from 'react'; 20 | import { NetworkErrorBoundary } from 'rest-hooks'; 21 | import { RouteChildrenProps } from 'react-router'; 22 | 23 | const App = ({ location }: RouteChildrenProps) => ( 24 | }> 25 | 26 | 27 | 28 | 29 | ) 30 | ``` 31 | 32 | Alternatively you could create your own error boundary where you might 33 | try dispatching the errors to another provider to use in a transient 34 | popup. 35 | 36 | Additionally you could also use one error boundary for any error 37 | type and handle network errors there. 38 | 39 | ## Without Error Boundary 40 | 41 | Error boundaries provide elegant ways to reduce complexity by handling many 42 | errors in the react tree in one location. However, if you find yourself wanting to handle 43 | error state manually you can adapt the [useStatefulResource()](./no-suspense.md) hook. 44 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.1/api/useResultCache.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useResultCache() 3 | id: version-2.1-useResultCache 4 | original_id: useResultCache 5 | --- 6 | 7 | 8 | 9 | 10 | ```typescript 11 | function useResultCache( 12 | fetchShape: ReadShape, 13 | params: object | null, 14 | defaults?: object, 15 | ): typeof defaults; 16 | ``` 17 | 18 | 19 | 20 | ```typescript 21 | function useResultCache, D extends object>( 22 | { getFetchKey, fetch }: ReadShape, 23 | params: Params | null, 24 | defaults?: D, 25 | ): D extends undefined 26 | ? Resolved> | null 27 | : Readonly; 28 | ``` 29 | 30 | 31 | 32 | Excellent to use with [pagination](../guides/pagination.md) or any other extra (non-entity) data in results. 33 | 34 | - [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 35 | - Returns previously cached if exists 36 | - `defaults` if provided 37 | - null otherwise 38 | - While loading: 39 | - Returns previously cached if exists 40 | - `defaults` if provided 41 | - null otherwise 42 | 43 | ## Example 44 | 45 | By sending defaults we can destructure the values even if the results don't exist. 46 | 47 | ```tsx 48 | function PostList() { 49 | const { prevPage, nextPage } = useResultCache( 50 | PaginatedResource.listShape(), 51 | {}, 52 | { prevPage: '', nextPage: '' }, 53 | ); 54 | // ...render stuff here 55 | } 56 | ``` 57 | 58 | ## Useful `FetchShape`s to send 59 | 60 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 61 | 62 | - listShape() 63 | 64 | Feel free to add your own [FetchShape](./FetchShape.md) as well. 65 | -------------------------------------------------------------------------------- /website/versioned_docs/version-2.2/api/useCache.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useCache() 3 | id: version-2.2-useCache 4 | original_id: useCache 5 | --- 6 | 7 | 8 | 9 | 10 | ```typescript 11 | function useCache( 12 | fetchShape: ReadShape, 13 | params: object | null 14 | ): SchemaOf | null; 15 | ``` 16 | 17 | 18 | 19 | ```typescript 20 | function useCache, S extends Schema>( 21 | { schema, getFetchKey }: ReadShape, 22 | params: Params | null 23 | ): SchemaOf | null; 24 | ``` 25 | 26 | 27 | 28 | > ### Rest Hooks 3.0 - Deprecation 29 | > 30 | > This hook is being deprecated in favor of [useCacheNew()](./useCacheNew) 31 | > 32 | > - 3.0 this will be renamed to `useCacheLegacy()` 33 | > - 3.1 will remove `useCacheLegacy()` 34 | 35 | Excellent to use data in the normalized cache without fetching. 36 | 37 | - [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 38 | - Returns previously cached if exists 39 | - null otherwise 40 | - While loading: 41 | - Returns previously cached if exists 42 | - null otherwise 43 | 44 | ## Example 45 | 46 | Using a type guard to deal with null 47 | 48 | ```tsx 49 | function Post({ id }: { id: number }) { 50 | const post = useCache(PostResource.detailShape(), { id }); 51 | // post as PostResource | null 52 | if (!post) return null; 53 | // post as PostResource (typeguarded) 54 | // ...render stuff here 55 | } 56 | ``` 57 | 58 | ## Useful `FetchShape`s to send 59 | 60 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 61 | 62 | - detailShape() 63 | - listShape() 64 | 65 | Feel free to add your own [FetchShape](./FetchShape.md) as well. 66 | -------------------------------------------------------------------------------- /docs/api/useRetrieve.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useRetrieve() 3 | --- 4 | 5 | 6 | 7 | 8 | ```typescript 9 | function useRetrieve( 10 | fetchShape: ReadShape, 11 | params: object | null, 12 | ): Promise | undefined; 13 | ``` 14 | 15 | 16 | 17 | ```typescript 18 | function useRetrieve< 19 | Params extends Readonly, 20 | S extends Schema 21 | >( 22 | fetchShape: ReadShape, 23 | params: Params | null, 24 | ): Promise | undefined; 25 | ``` 26 | 27 | 28 | 29 | Great for retrieving resources optimistically before they are needed. 30 | 31 | This can be useful for ensuring resources early in a render tree before they are needed. 32 | 33 | - Triggers fetch: 34 | - On first-render and when parameters change 35 | - and When not in cache or result is considered stale 36 | - and When no identical requests are in flight 37 | - and when params are not null 38 | - [On Error (404, 500, etc)](https://www.restapitutorial.com/httpstatuscodes.html): 39 | - Returned promise will reject 40 | - On fetch returns a promise else undefined. 41 | 42 | ## Example 43 | 44 | ### Simple 45 | 46 | ```tsx 47 | function MasterPost({ id }: { id: number }) { 48 | useRetrieve(PostResource.detailShape(), { id }); 49 | // ... 50 | } 51 | ``` 52 | 53 | ### Conditional 54 | 55 | ```tsx 56 | function MasterPost({ id, doNotFetch }: { id: number, doNotFetch: boolean }) { 57 | useRetrieve(PostResource.detailShape(), doNotFetch ? null : { id }); 58 | // ... 59 | } 60 | ``` 61 | 62 | ## Useful `FetchShape`s to send 63 | 64 | [Resource](./Resource.md#provided-and-overridable-methods) provides these built-in: 65 | 66 | - detailShape() 67 | - listShape() 68 | 69 | Feel free to add your own [FetchShape](./FetchShape.md) as well. 70 | --------------------------------------------------------------------------------