├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── __tests__
├── csr.test.tsx
└── ssr.test.tsx
├── package-lock.json
├── package.json
├── src
└── index.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | index.js
3 | index.d.ts
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | __tests__/
3 | tsconfig.json
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Raiden
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-router-prop-types
2 |
3 | Runtime type checking for [react-router](https://github.com/ReactTraining/react-router) props
4 |
5 | ## Installation
6 |
7 | ```shell
8 | npm install react-router-prop-types --save
9 | ```
10 |
11 | ## Usage
12 |
13 | ```jsx
14 | import React from 'react';
15 | import ReactRouterPropTypes from 'react-router-prop-types';
16 |
17 | class MyComponent extends React.Component {
18 | static propTypes = {
19 | // You can chain any of the above with `isRequired` to make sure a warning
20 | // is shown if the prop isn't provided.
21 | history: ReactRouterPropTypes.history.isRequired,
22 | location: ReactRouterPropTypes.location.isRequired,
23 | match: ReactRouterPropTypes.match.isRequired,
24 | route: ReactRouterPropTypes.route.isRequired, // for react-router-config
25 | }
26 | render() {
27 | // ...
28 | }
29 | }
30 | ```
31 |
--------------------------------------------------------------------------------
/__tests__/csr.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Router, Route } from 'react-router';
4 | import { createBrowserHistory, createHashHistory, createMemoryHistory } from 'history';
5 | import ReactRouterPropTypes from '../src';
6 |
7 | describe('client side rendering', () => {
8 | const warning = expect.stringMatching(/(Invalid prop|Failed prop type)/gi);
9 |
10 | const node = document.createElement('div');
11 |
12 | afterEach(() => {
13 | ReactDOM.unmountComponentAtNode(node);
14 | });
15 |
16 | class TestComponent extends React.Component {
17 | static propTypes = {
18 | history: ReactRouterPropTypes.history.isRequired,
19 | location: ReactRouterPropTypes.location.isRequired,
20 | match: ReactRouterPropTypes.match.isRequired,
21 | }
22 | render() {
23 | return null;
24 | }
25 | }
26 |
27 | test('browser history', () => {
28 | spyOn(console, 'error');
29 | ReactDOM.render(
30 |
31 |
32 | ,
33 | node,
34 | );
35 | expect(console.error).not.toHaveBeenCalledWith(warning);
36 | });
37 |
38 | test('hash history', () => {
39 | spyOn(console, 'error');
40 | ReactDOM.render(
41 |
42 |
43 | ,
44 | node,
45 | );
46 | expect(console.error).not.toHaveBeenCalledWith(warning);
47 | });
48 |
49 | test('memory history', () => {
50 | spyOn(console, 'error');
51 | ReactDOM.render(
52 |
53 |
54 | ,
55 | node,
56 | );
57 | expect(console.error).not.toHaveBeenCalledWith(warning);
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/__tests__/ssr.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOMServer from 'react-dom/server';
3 | import { StaticRouter, Route } from 'react-router';
4 | import { renderRoutes } from 'react-router-config';
5 | import ReactRouterPropTypes from '../src';
6 |
7 | describe('server side rendering', () => {
8 | const warning = expect.stringMatching(/(Invalid prop|Failed prop type)/gi);
9 |
10 | test('simple ssr', () => {
11 | spyOn(console, 'error');
12 | class TestComponent extends React.Component {
13 | static propTypes = {
14 | history: ReactRouterPropTypes.history.isRequired,
15 | location: ReactRouterPropTypes.location.isRequired,
16 | match: ReactRouterPropTypes.match.isRequired,
17 | }
18 | render() {
19 | return null;
20 | }
21 | }
22 | ReactDOMServer.renderToString(
23 |
24 |
25 |
26 | );
27 | expect(console.error).not.toHaveBeenCalledWith(warning);
28 | });
29 |
30 | test('react-router-config', () => {
31 | spyOn(console, 'error');
32 | class TestComponent extends React.Component {
33 | static propTypes = {
34 | history: ReactRouterPropTypes.history.isRequired,
35 | location: ReactRouterPropTypes.location.isRequired,
36 | match: ReactRouterPropTypes.match.isRequired,
37 | route: ReactRouterPropTypes.route.isRequired,
38 | }
39 | render() {
40 | return null;
41 | }
42 | }
43 | const routes = [
44 | {
45 | path: '/root',
46 | component: TestComponent,
47 | routes: [
48 | {
49 | path: '/child',
50 | component: TestComponent,
51 | }
52 | ],
53 | }
54 | ];
55 | ReactDOMServer.renderToString(
56 |
57 | {renderRoutes(routes)}
58 |
59 | );
60 | expect(console.error).not.toHaveBeenCalledWith(warning);
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-router-prop-types",
3 | "version": "1.0.5",
4 | "description": "Runtime type checking for react-router props",
5 | "main": "index.js",
6 | "types": "index.d.js",
7 | "scripts": {
8 | "prepublishOnly": "tsc",
9 | "test": "jest"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/Javascript-Ninja/react-router-prop-types.git"
14 | },
15 | "keywords": [
16 | "react",
17 | "route",
18 | "router",
19 | "react-router",
20 | "proptype",
21 | "proptypes",
22 | "props"
23 | ],
24 | "author": "Raiden ",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/Javascript-Ninja/react-router-prop-types/issues"
28 | },
29 | "homepage": "https://github.com/Javascript-Ninja/react-router-prop-types#readme",
30 | "dependencies": {
31 | "@types/prop-types": "^15.7.3",
32 | "prop-types": "^15.7.2"
33 | },
34 | "devDependencies": {
35 | "@types/history": "^4.7.7",
36 | "@types/jest": "^26.0.12",
37 | "@types/react": "^16.9.49",
38 | "@types/react-dom": "^16.9.8",
39 | "@types/react-router": "^5.1.8",
40 | "@types/react-router-config": "^5.0.1",
41 | "history": "^5.0.0",
42 | "jest": "^26.4.2",
43 | "react": "^16.13.1",
44 | "react-dom": "^16.13.1",
45 | "react-router": "^5.2.0",
46 | "react-router-config": "^5.1.1",
47 | "ts-jest": "^26.3.0",
48 | "typescript": "^4.0.2"
49 | },
50 | "jest": {
51 | "transform": {
52 | "^.+\\.tsx?$": "ts-jest"
53 | },
54 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
55 | "moduleFileExtensions": [
56 | "ts",
57 | "tsx",
58 | "js",
59 | "jsx",
60 | "json"
61 | ]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as PropTypes from 'prop-types';
2 |
3 | export const location = PropTypes.shape({
4 | hash: PropTypes.string.isRequired,
5 | key: PropTypes.string, // only in createBrowserHistory and createMemoryHistory
6 | pathname: PropTypes.string.isRequired,
7 | search: PropTypes.string.isRequired,
8 | state: PropTypes.oneOfType([
9 | PropTypes.array,
10 | PropTypes.bool,
11 | PropTypes.number,
12 | PropTypes.object,
13 | PropTypes.string,
14 | ]), // only in createBrowserHistory and createMemoryHistory
15 | });
16 |
17 | export const history = PropTypes.shape({
18 | action: PropTypes.oneOf(['PUSH', 'REPLACE', 'POP']).isRequired,
19 | block: PropTypes.func.isRequired,
20 | createHref: PropTypes.func.isRequired,
21 | go: PropTypes.func.isRequired,
22 | goBack: PropTypes.func, // only in server side rendering
23 | goForward: PropTypes.func, // only in server side rendering
24 | back: PropTypes.func, // only in client side rendering
25 | forward: PropTypes.func, // only in client side rendering
26 | index: PropTypes.number, // only in createMemoryHistory
27 | length: PropTypes.number,
28 | listen: PropTypes.func.isRequired,
29 | location: location.isRequired,
30 | push: PropTypes.func.isRequired,
31 | replace: PropTypes.func.isRequired,
32 | });
33 |
34 | export const match = PropTypes.shape({
35 | isExact: PropTypes.bool,
36 | params: PropTypes.object.isRequired,
37 | path: PropTypes.string.isRequired,
38 | url: PropTypes.string.isRequired,
39 | });
40 |
41 | const routeShape: any = {
42 | path: PropTypes.oneOfType([
43 | PropTypes.string,
44 | PropTypes.arrayOf(PropTypes.string),
45 | ]),
46 | exact: PropTypes.bool,
47 | strict: PropTypes.bool,
48 | sensitive: PropTypes.bool,
49 | component: PropTypes.func,
50 | };
51 | routeShape.routes = PropTypes.arrayOf(PropTypes.shape(routeShape));
52 |
53 | export const route = PropTypes.shape(routeShape);
54 |
55 | export default {
56 | location,
57 | history,
58 | match,
59 | route,
60 | };
61 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 | // "lib": [], /* Specify library files to be included in the compilation. */
10 | // "allowJs": true, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
13 | "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | // "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | "outDir": "./", /* Redirect output structure to the directory. */
18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | "removeComments": true, /* Do not emit comments to output. */
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true, /* Enable all strict type-checking options. */
29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | "noUnusedLocals": true, /* Report errors on unused locals. */
39 | "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 |
43 | /* Module Resolution Options */
44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
48 | // "typeRoots": [], /* List of folders to include type definitions from. */
49 | // "types": [], /* Type declaration files to be included in compilation. */
50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
54 |
55 | /* Source Map Options */
56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
60 |
61 | /* Experimental Options */
62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
64 |
65 | /* Advanced Options */
66 | "skipLibCheck": true, /* Skip type checking of declaration files. */
67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
68 | },
69 | "include": ["src/**/*"],
70 | "exclude": ["node_modules"],
71 | }
72 |
--------------------------------------------------------------------------------