├── .atlassian └── OWNER ├── .changeset ├── README.md └── config.json ├── .eslintrc.js ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .prettierrc.js ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── babel.config.json ├── codemods ├── codeshift.config.js └── transforms │ └── use-resource-imports │ ├── README.md │ ├── index.ts │ └── test.ts ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── api │ ├── README.md │ ├── components.md │ ├── hooks.md │ ├── plugins.md │ └── utilities.md ├── index.html ├── resources │ ├── README.md │ ├── adding.md │ ├── concept.md │ ├── creation.md │ ├── creation │ │ ├── code-splitting.md │ │ └── error-handling.md │ ├── interaction.md │ ├── nested-routes.md │ └── usage.md └── router │ ├── README.md │ ├── configuration.md │ ├── ssr.md │ └── state.md ├── examples ├── basic-routing │ ├── about.tsx │ ├── home.tsx │ ├── index.html │ └── index.tsx ├── hash-routing │ ├── about.tsx │ ├── home.tsx │ ├── index.html │ └── index.tsx ├── hooks │ ├── home.tsx │ ├── index.html │ ├── index.tsx │ ├── use-path-param.tsx │ └── use-query-param.tsx ├── hydration │ ├── home.tsx │ ├── index.html │ ├── index.tsx │ └── routes.tsx └── routing-with-resources │ ├── about.tsx │ ├── home.tsx │ ├── index.html │ ├── index.tsx │ └── routes.tsx ├── jest.config.js ├── jest.setup.js ├── mocks └── package.json ├── package-lock.json ├── package.json ├── resources └── package.json ├── security-assistant.yml ├── src ├── __tests__ │ └── integration.test.tsx ├── common │ ├── constants.ts │ ├── mocks │ │ └── index.ts │ ├── types.ts │ └── utils │ │ ├── create-legacy-history │ │ ├── index.ts │ │ └── test.ts │ │ ├── event │ │ ├── index.ts │ │ └── test.ts │ │ ├── generate-location │ │ └── index.ts │ │ ├── generate-path │ │ ├── index.ts │ │ └── test.ts │ │ ├── index.ts │ │ ├── is-same-route │ │ ├── index.ts │ │ └── test.ts │ │ ├── is-server-environment │ │ └── index.ts │ │ ├── match-route │ │ ├── exec-route-matching.ts │ │ ├── index.ts │ │ ├── matchPath.ts │ │ ├── matchQuery.ts │ │ ├── test.ts │ │ └── utils.ts │ │ ├── router-context │ │ ├── index.ts │ │ └── test.tsx │ │ └── should-reload-when-route-match-changes │ │ ├── index.ts │ │ └── test.ts ├── controllers │ ├── index.ts │ ├── plugins │ │ ├── index.ts │ │ └── test.ts │ ├── redirect │ │ ├── index.tsx │ │ └── test.tsx │ ├── router-actions │ │ ├── index.tsx │ │ └── test.tsx │ ├── router-store │ │ ├── index.tsx │ │ ├── test.tsx │ │ ├── types.ts │ │ └── utils │ │ │ ├── index.ts │ │ │ └── test.ts │ ├── router-subscriber │ │ ├── index.tsx │ │ └── test.tsx │ ├── router │ │ ├── index.tsx │ │ ├── test.tsx │ │ └── types.ts │ ├── use-path-param │ │ ├── index.ts │ │ └── test.tsx │ ├── use-query-param │ │ ├── index.ts │ │ └── test.tsx │ ├── use-router-actions │ │ ├── index.ts │ │ └── test.tsx │ ├── use-router │ │ ├── index.ts │ │ └── test.tsx │ ├── use-timeout │ │ ├── index.ts │ │ └── test.tsx │ └── with-router │ │ ├── index.tsx │ │ └── test.tsx ├── index.ts ├── mocks.ts ├── resources │ ├── __tests__ │ │ └── integration.test.tsx │ ├── common │ │ ├── mocks │ │ │ └── index.ts │ │ └── types.ts │ ├── controllers │ │ ├── add-resource-listener │ │ │ └── index.ts │ │ ├── resource-store │ │ │ ├── index.tsx │ │ │ ├── selectors.ts │ │ │ ├── test.tsx │ │ │ ├── types.ts │ │ │ └── utils │ │ │ │ ├── accessed-at │ │ │ │ ├── index.ts │ │ │ │ └── test.ts │ │ │ │ ├── create-loading-slice │ │ │ │ ├── constants.ts │ │ │ │ └── index.ts │ │ │ │ ├── create-resource │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ └── test.ts │ │ │ │ ├── dependent-resources │ │ │ │ ├── README.md │ │ │ │ ├── index.ts │ │ │ │ └── test.tsx │ │ │ │ ├── expires-at │ │ │ │ ├── index.ts │ │ │ │ └── test.ts │ │ │ │ ├── generate-time-guard │ │ │ │ ├── index.ts │ │ │ │ ├── test.ts │ │ │ │ └── types.ts │ │ │ │ ├── get-default-state-slice │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ └── test.ts │ │ │ │ ├── get-resource-identifier │ │ │ │ ├── index.ts │ │ │ │ └── test.ts │ │ │ │ ├── get-resources-for-next-location │ │ │ │ ├── index.ts │ │ │ │ └── test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── lru-cache │ │ │ │ ├── index.ts │ │ │ │ ├── test.ts │ │ │ │ └── types.ts │ │ │ │ ├── manage-resource-state │ │ │ │ └── index.ts │ │ │ │ ├── route-checks │ │ │ │ ├── index.ts │ │ │ │ └── test.ts │ │ │ │ ├── serialize-error │ │ │ │ └── index.ts │ │ │ │ ├── should-use-cache │ │ │ │ ├── index.tsx │ │ │ │ └── test.ts │ │ │ │ ├── ssr-data-promise │ │ │ │ └── index.ts │ │ │ │ ├── timeout-error │ │ │ │ └── index.ts │ │ │ │ └── transform-data │ │ │ │ ├── index.ts │ │ │ │ └── test.ts │ │ ├── resource-subscriber │ │ │ ├── index.tsx │ │ │ └── test.tsx │ │ └── use-resource │ │ │ ├── index.ts │ │ │ └── test.tsx │ ├── index.ts │ └── plugin │ │ ├── index.ts │ │ └── test.ts ├── ui │ ├── index.ts │ ├── link │ │ ├── index.tsx │ │ ├── test.tsx │ │ └── utils │ │ │ ├── get-valid-link-type.tsx │ │ │ ├── handle-navigation.tsx │ │ │ └── index.ts │ └── route-component │ │ ├── index.tsx │ │ └── test.tsx └── utils.ts ├── tsconfig.build.json ├── tsconfig.json ├── utils └── package.json └── webpack.config.js /.atlassian/OWNER: -------------------------------------------------------------------------------- 1 | kchua 2 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:import/recommended', 10 | 'plugin:react/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | globals: { 14 | // enable webpack require 15 | require: 'readonly', 16 | }, 17 | parser: '@babel/eslint-parser', 18 | parserOptions: { 19 | ecmaFeatures: { 20 | jsx: true, 21 | }, 22 | ecmaVersion: 2018, 23 | sourceType: 'module', 24 | }, 25 | plugins: ['react', 'react-hooks', 'import'], 26 | rules: { 27 | 'import/order': [ 28 | 'error', 29 | { 30 | alphabetize: { 31 | order: 'asc', 32 | }, 33 | 'newlines-between': 'always', 34 | }, 35 | ], 36 | indent: 'off', 37 | 'linebreak-style': 'off', 38 | 'newline-before-return': 'error', 39 | 'no-shadow': 'error', 40 | 'prettier/prettier': 'warn', 41 | quotes: 'off', 42 | 'react/display-name': 'off', 43 | 'react/no-direct-mutation-state': 'off', 44 | 'react/prop-types': 'off', 45 | 'react-hooks/exhaustive-deps': 'warn', 46 | 'react-hooks/rules-of-hooks': 'error', 47 | semi: 'off', 48 | 'import/no-cycle': 'error', 49 | }, 50 | overrides: [ 51 | { 52 | // TypeScript specific rules 53 | files: ['*.{ts,tsx}'], 54 | extends: [ 55 | 'plugin:@typescript-eslint/recommended', 56 | 'plugin:import/typescript', 57 | ], 58 | rules: { 59 | '@typescript-eslint/ban-ts-comment': 'off', 60 | '@typescript-eslint/no-empty-function': 'off', 61 | '@typescript-eslint/no-empty-interface': 'off', 62 | '@typescript-eslint/no-explicit-any': 'off', 63 | '@typescript-eslint/no-non-null-assertion': 'off', 64 | '@typescript-eslint/no-unused-vars': [ 65 | 'error', 66 | { vars: 'all', args: 'after-used', ignoreRestSiblings: true }, 67 | ], 68 | '@typescript-eslint/no-unnecessary-type-constraint': 'off', 69 | }, 70 | settings: { 71 | 'import/resolver': { 72 | typescript: true, 73 | }, 74 | }, 75 | }, 76 | { 77 | // Jest env 78 | files: ['test.{js,ts,tsx}'], 79 | env: { 80 | jest: true, 81 | }, 82 | }, 83 | ], 84 | settings: { 85 | react: { 86 | version: 'detect', 87 | }, 88 | }, 89 | }; 90 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [20.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm ci 27 | - run: npm run lint 28 | - run: npm run lint:deps 29 | - run: npm run lint:types 30 | - run: npm run test 31 | - run: npm run build 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: 20 20 | 21 | - name: Install dependencies 22 | run: npm ci 23 | 24 | - name: ESLint 25 | run: npm run lint 26 | 27 | - name: Lint dependencies 28 | run: npm run lint:deps 29 | 30 | - name: Lint types 31 | run: npm run lint:types 32 | 33 | - name: Test source 34 | run: npm run test 35 | 36 | - name: Build source 37 | run: npm run build 38 | 39 | - name: Raise release or publish to npm 40 | id: changesets 41 | uses: changesets/action@v1 42 | with: 43 | publish: npm run release 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | coverage 5 | yarn.lock 6 | 7 | # misc 8 | .local 9 | .idea 10 | .vscode 11 | src/webpack/__tests__/__fixtures__/webpack/output 12 | .DS_Store 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore dev files 3 | .github 4 | .editorconfig 5 | .eslintrc.js 6 | .prettierrc.js 7 | jest.config.js 8 | webpack.config.js 9 | *.config.*.js 10 | *.setup.js 11 | tsconfig.json 12 | tsconfig.build.json 13 | 14 | # Ignore source files 15 | src 16 | 17 | # Ignore examples 18 | examples 19 | docs 20 | 21 | # Ignore tests 22 | **/__tests__ 23 | tests 24 | coverage 25 | 26 | # Other stuff 27 | .atlassian 28 | .DS_Store 29 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | printWidth: 80, 4 | semi: true, 5 | singleQuote: true, 6 | tabWidth: 2, 7 | trailingComma: 'es5', 8 | }; 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # react-resource-router 2 | 3 | ## 0.30.0 4 | 5 | ### Minor Changes 6 | 7 | - bf5a671: Supporting navigation to / and basePath when basePath is defined 8 | 9 | ## 0.29.2 10 | 11 | ### Patch Changes 12 | 13 | - 7d56bc3: Support basePath for relative href prop 14 | 15 | ## 0.29.1 16 | 17 | ### Patch Changes 18 | 19 | - a627bb6: Remove cyclic imports and add eslint rule to stop future issues 20 | - ea984a4: Bumped path-to-regexp 21 | 22 | ## 0.29.0 23 | 24 | ### Minor Changes 25 | 26 | - dfd9be0: Bump path-to-regexp to 6.3.0 27 | 28 | ## 0.28.0 29 | 30 | ### Minor Changes 31 | 32 | - 87ca96e: Updated the path regex matching method added test to make sure complex routing is not broken 33 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 6 | 7 | Examples of unacceptable behavior by participants include: 8 | 9 | * The use of sexualized language or imagery 10 | * Personal attacks 11 | * Trolling or insulting/derogatory comments 12 | * Public or private harassment 13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 14 | * Submitting contributions or comments that you know to violate the intellectual property or privacy rights of others 15 | * Other unethical or unprofessional conduct 16 | 17 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 18 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 19 | 20 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 21 | 22 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer. Complaints will result in a response and be reviewed and investigated in a way that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. 23 | 24 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/][version] 25 | 26 | [homepage]: http://contributor-covenant.org 27 | [version]: http://contributor-covenant.org/version/1/3/0/ 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to react-resource-router 2 | 3 | Thank you for considering a contribution to react-resource-router! Pull requests, issues and comments are welcome. For pull requests, please: 4 | 5 | * Add tests for new features and bug fixes 6 | * Follow the existing style 7 | * Separate unrelated changes into multiple pull requests 8 | 9 | See the existing issues for things to start contributing. 10 | 11 | For bigger changes, please make sure you start a discussion first by creating an issue and explaining the intended change. 12 | 13 | Atlassian requires contributors to sign a Contributor License Agreement, known as a CLA. This serves as a record stating that the contributor is entitled to contribute the code/documentation/translation to the project and is willing to have it used in distributions and derivative works (or is willing to transfer ownership). 14 | 15 | Prior to accepting your contributions we ask that you please follow the appropriate link below to digitally sign the CLA. The Corporate CLA is for those who are contributing as a member of an organization and the individual CLA is for those contributing as an individual. 16 | 17 | * [CLA for corporate contributors](https://opensource.atlassian.com/corporate) 18 | * [CLA for individuals](https://opensource.atlassian.com/individual) 19 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": [ 7 | "last 2 chrome versions", 8 | "last 2 firefox versions", 9 | "last 2 safari versions", 10 | "last 2 and_chr versions", 11 | "last 2 ios_saf versions", 12 | "edge >= 18" 13 | ], 14 | "modules": false, 15 | "loose": true 16 | } 17 | ], 18 | "@babel/preset-react", 19 | "@babel/preset-typescript" 20 | ], 21 | "plugins": [ 22 | ["@babel/plugin-proposal-class-properties"], 23 | "@babel/plugin-proposal-export-namespace-from", 24 | "@babel/plugin-syntax-dynamic-import", 25 | "@babel/plugin-syntax-import-meta", 26 | "@babel/plugin-transform-runtime" 27 | ], 28 | "env": { 29 | "development": { 30 | "ignore": [ 31 | "**/*.test.ts", 32 | "**/*.test.tsx", 33 | "**/test.ts", 34 | "**/test.tsx" 35 | ] 36 | }, 37 | "test": { 38 | "presets": ["@babel/preset-env", "@babel/preset-typescript"], 39 | "plugins": ["@babel/plugin-transform-runtime"] 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /codemods/codeshift.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: { 3 | 'use-resource-imports': require.resolve( 4 | './transforms/use-resource-imports' 5 | ), 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /codemods/transforms/use-resource-imports/README.md: -------------------------------------------------------------------------------- 1 | # use-resource-imports codemod 2 | 3 | This codemod transforms relevant `react-resource-router` imports to `react-resource-router/resources` 4 | 5 | ## Usage 6 | 7 | ```sh 8 | npx @codeshift/cli --packages "react-resource-router#use-resource-imports" source_file.tsx 9 | ``` 10 | 11 | ```sh 12 | npx @codeshift/cli --packages "react-resource-router#use-resource-imports" ~my-project/**/*.tsx 13 | ``` -------------------------------------------------------------------------------- /codemods/transforms/use-resource-imports/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getImportDeclaration, 3 | removeImportDeclaration, 4 | } from '@codeshift/utils'; 5 | import type { FileInfo, API } from 'jscodeshift'; 6 | 7 | const resourcesImportSpecifiers = [ 8 | 'createResourcesPlugin', 9 | 'ResourceSubscriber', 10 | 'useResource', 11 | 'addResourcesListener', 12 | 'createResource', 13 | 'useResourceStoreContext', 14 | 'ResourceDependencyError', 15 | 'getResourceStore', 16 | 'ResourceStore', 17 | 'CreateResourceArgBase', 18 | 'CreateResourceArgSync', 19 | 'CreateResourceArgAsync', 20 | 'RouteResources', 21 | 'ResourceStoreContext', 22 | 'ResourceStoreData', 23 | 'RouteResource', 24 | 'RouteResourceError', 25 | 'RouteResourceLoading', 26 | 'RouteResourceResponse', 27 | 'RouteResourceUpdater', 28 | 'RouterDataContext', 29 | 'UseResourceHookResponse', 30 | 'mockRouteResourceResponse', 31 | ]; 32 | 33 | const packageName = 'react-resource-router'; 34 | const resourcesPackageName = 'react-resource-router/resources'; 35 | 36 | export default function transformer( 37 | file: FileInfo, 38 | { jscodeshift: j }: API 39 | ): string | undefined { 40 | const source = j(file.source); 41 | 42 | const rrrImportDeclaration = getImportDeclaration(j, source, packageName); 43 | 44 | if (rrrImportDeclaration.length === 0) { 45 | return file.source; 46 | } 47 | 48 | // Here we narrow our search to only relevant import nodes 49 | const importSpecifiersToMove = rrrImportDeclaration 50 | .find(j.ImportSpecifier) 51 | .filter(path => 52 | resourcesImportSpecifiers.includes(path.node.imported.name) 53 | ); 54 | 55 | if (importSpecifiersToMove.length > 0) { 56 | const newImport = j.importDeclaration( 57 | importSpecifiersToMove.nodes(), 58 | j.stringLiteral(resourcesPackageName) 59 | ); 60 | 61 | rrrImportDeclaration.insertAfter(newImport); 62 | 63 | // remove "resource" specifiers from rrr import 64 | rrrImportDeclaration 65 | .find(j.ImportSpecifier) 66 | .filter(path => 67 | resourcesImportSpecifiers.includes(path.node.imported.name) 68 | ) 69 | .remove(); 70 | 71 | // remove rrr import if empty 72 | if (rrrImportDeclaration.find(j.ImportSpecifier).length === 0) { 73 | rrrImportDeclaration.remove(); 74 | removeImportDeclaration(j, source, packageName); 75 | } 76 | } 77 | 78 | return source.toSource(); 79 | } 80 | 81 | export const parser = 'tsx'; 82 | -------------------------------------------------------------------------------- /codemods/transforms/use-resource-imports/test.ts: -------------------------------------------------------------------------------- 1 | import { applyTransform } from '@codeshift/test-utils'; 2 | 3 | import * as transformer from './index'; 4 | 5 | it('codemod should move resources imports to react-resource-router/resources', async () => { 6 | const result = await applyTransform( 7 | transformer, 8 | ` 9 | import { Link, createResource, useResource } from 'react-resource-router'; 10 | 11 | const Button = (props) => 27 | ); 28 | }; 29 | ``` 30 | 31 | ## Refreshing 32 | 33 | The refresh function is bound to the resource that you provide to [`useResource`](../api/hooks.md#useresource) hook or the [`ResourceSubscriber`](../api/components.md#resourcesubscriber). Calling this function will cause the router to call the `getData` function on your resource, and bypass any `expiresAt` checks. 34 | 35 | When using the `refresh` function, the resource will always be fetched from remote and the resource state will be updated with any result, including errors. 36 | 37 | ```js 38 | import { useResource } from 'react-resource-router/resources'; 39 | import { accountInfoResource } from '../routing/resources'; 40 | 41 | export const UsernameResetter = ({ newUsername }) => { 42 | const { data, refresh } = useResource(accountInfoResource); 43 | 44 | return ( 45 | 48 | ); 49 | }; 50 | ``` 51 | 52 | ## Clearing 53 | 54 | The `clear` method is bound to the resource that you provide to [`useResource`](../api/hooks.md#useresource) hook or the [`ResourceSubscriber`](../api/components.md#resourcesubscriber). Calling this function will clear the resource so that the resource will be fetched from remote next time it's needed. 55 | 56 | ```js 57 | import { useResource } from 'react-resource-router/resources'; 58 | import { accountInfoResource } from '../routing/resources'; 59 | 60 | export const UsernameResetter = ({ newUsername }) => { 61 | const { data, clear } = useResource(accountInfoResource); 62 | 63 | return ( 64 | 67 | ); 68 | }; 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/resources/nested-routes.md: -------------------------------------------------------------------------------- 1 | # How to Create Nested Routes Using Slot in React Resource Router 2 | 3 | The React Resource Router library generally encourages you to keep your routing flat. However, there may be situations where you need nested components associated with different URLs. To accomplish this, you can create a Slot component. 4 | 5 | Here's how to do it: 6 | 7 | ## Slot Component 8 | 9 | ```js 10 | import { useRouter, Redirect } from 'react-resource-router'; 11 | 12 | // Define a object of URLs to Components. 13 | // Make sure the keys in this object match the URLs you intend to handle. 14 | const slots = { 15 | 'url-one': ComponentOne, 16 | 'url-two': ComponentTwo, 17 | }; 18 | 19 | const Slot = () => { 20 | const [{ route }] = useRouter(); 21 | const name = route.name; 22 | 23 | // If the specified slot doesn't exist, redirect to a 404 page 24 | if (!slots?.[name]) { 25 | return ; 26 | } 27 | 28 | // Dynamically load and render the component based on the current route name 29 | const DynamicComponent = slots[name]; 30 | return ; 31 | }; 32 | 33 | ``` 34 | 35 | ## Add Slot Component to Parent 36 | 37 | ```js 38 | const Page = () => { 39 | return ( 40 |
41 | 42 |
43 | ) 44 | } 45 | ``` -------------------------------------------------------------------------------- /docs/resources/usage.md: -------------------------------------------------------------------------------- 1 | # How to use Router Resources in your components 2 | 3 | Resources expose properties and functions via the [`useResource`](../api/hooks.md#useresource) hook or the [`ResourceSubscriber`](../api/components.md#resourcesubscriber), which allow their current state to be accessed or interacted with in your components. These are 4 | 5 | | Property | Type | Description | 6 | | --------- | ----------------- | --------------------------------------------------------------------------------------- | 7 | | `data` | `any` | The result which your getData function will resolve with | 8 | | `loading` | `boolean` | Determines if the resource is fetching its data or not | 9 | | `error` | `error` or `null` | If your getData function throws an error, it will be stored here | 10 | | `update` | `function` | Allows you to imperatively update the resource's current state bypassing its `maxAge` | 11 | | `refresh` | `function` | Allows you to imperatively refresh the resource's state by calling its `getData` method | 12 | | `clear` | `function` | Allows you to imperatively clear the resource's state | 13 | | `clearAll`| `function` | Clears all resource data of the particular type regardless of its keys | 14 | | `key` | `string` | Unique key for the resource | 15 | 16 | You can use these properties and functions to implement your own customised render logic inside your resource consuming components. 17 | 18 | ## Hook 19 | 20 | Using resources via the [`useResource`](../api/hooks.md#useresource) hook is the **recommended** way to access your current resource state in a component. Here is an example of how you can do that 21 | 22 | ```jsx 23 | import { useResource } from 'react-resource-router/resources'; 24 | import { avatarResource } from '../routing/resources'; 25 | import { Circle } from './primitives'; 26 | 27 | export const Avatar = () => { 28 | const { data, loading } = useResource(avatarResource); 29 | const image = loading ? '' : data; 30 | 31 | return ; 32 | }; 33 | ``` 34 | 35 | ## Component 36 | 37 | If you are unable to use the [`useResource`](../api/hooks.md#useresource) hook for whatever reason, you can also use the [`ResourceSubscriber`](../api/components.md#resourcesubscriber) component which provides your resource state via render props 38 | 39 | ```jsx 40 | import { ResourceSubscriber } from 'react-resource-router'; 41 | import { avatarResource } from '../routing/resources'; 42 | import { Circle } from './primitives'; 43 | 44 | export class Avatar extends Component { 45 | render() { 46 | 47 | {({ data, loading }) => } 48 | ; 49 | } 50 | } 51 | ``` 52 | 53 | ## Accessing resource state for another route or url 54 | 55 | By default, the hook and the subscriber access data and state of a resource given current router context. So if your resource key is based on some parameters for instance, the resource state will be bound to such key and the hooks/subscribers will calculate it based on current router state. 56 | 57 | There are situations however, where you might want to access a different key for a resource, like to triggering an ahead of time fetch, refresh or just invalidating data. For this reasons the hooks/subscribers accept an optional `routerContext` that will be passed to `getData` and `getKey`. 58 | 59 | As an example, assuming your `blogPostRoute` has path `/blogs/:id` and you are on `/`, you can populate the resurce of blog `id: 1` by creating a custom `routerContext`: 60 | 61 | ```jsx 62 | import { createRouterContext } from 'react-resource-router'; 63 | import { useResource } from 'react-resource-router/resources'; 64 | import { blogPostResource } from '../routing/resources'; 65 | import { blogPostRoute } from '../routing'; 66 | 67 | export const PrefetchBlogPost = ({ id }) => { 68 | const { refresh } = useResource(blogPostResource, { 69 | routerContext: createRouterContext(blogPostRoute, { params: { id } }), 70 | }); 71 | 72 | useEffect(() => { 73 | refresh(); 74 | }, [refresh]); 75 | 76 | return null; 77 | }; 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/router/README.md: -------------------------------------------------------------------------------- 1 | - **Router** 2 | 3 | - [Configuration](./configuration.md) 4 | - [State](./state.md) 5 | - [SSR](./ssr.md) 6 | -------------------------------------------------------------------------------- /docs/router/ssr.md: -------------------------------------------------------------------------------- 1 | # How to use the Router in SSR 2 | 3 | RRR supports server side rendered apps out of the box. If this is something that you would like to take advantage of, there are a few things we encourage you to think about first 4 | 5 | ## Tree Composition 6 | 7 | Universally (on both client and server), we recommend using the following tree composition pattern 8 | 9 | ```js 10 | import { App } from './components'; 11 | 12 | 13 | 14 | ; 15 | ``` 16 | 17 | Where `App` contains the `RouteComponent` as a child 18 | 19 | ```js 20 | // App.js 21 | import { RouteComponent } from 'react-resource-router'; 22 | 23 | export const App = () => ( 24 | <> 25 | 26 | 27 |