├── .all-contributorsrc
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .vscode
└── launch.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CONTRIBUTORS.md
├── LICENSE
├── README.md
├── README_SOURCE.md
├── configs
├── jest.config.json
└── jest.stubs.js
├── docs
├── build
│ ├── 0.6e57cfb5.js
│ └── bundle.a71e23e0.js
└── index.html
├── generate-readme.js
├── generate-readme.sh
├── generate-styleguide.sh
├── is-git-status-clean.sh
├── package-lock.json
├── package.json
└── playground
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── .storybook
├── addons.js
├── config.js
└── webpack.config.js
├── .vscode
└── settings.json
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src-old
├── App.css
├── App.test.tsx
├── App.tsx
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
├── reportWebVitals.ts
└── setupTests.ts
├── src
├── api
│ ├── agent.ts
│ ├── fixtures
│ │ └── todos.json
│ ├── index.ts
│ ├── models.ts
│ ├── todos.ts
│ └── utils.ts
├── app.test.tsx
├── app.tsx
├── components
│ ├── __snapshots__
│ │ ├── class-counter-with-default-props.stories.storyshot
│ │ ├── class-counter.stories.storyshot
│ │ ├── fc-counter-with-default-props.stories.storyshot
│ │ ├── fc-counter.stories.storyshot
│ │ ├── fc-spread-attributes.stories.storyshot
│ │ ├── generic-list.stories.storyshot
│ │ └── mouse-provider.stories.storyshot
│ ├── class-counter-with-default-props.md
│ ├── class-counter-with-default-props.stories.tsx
│ ├── class-counter-with-default-props.tsx
│ ├── class-counter-with-default-props.usage.tsx
│ ├── class-counter.md
│ ├── class-counter.stories.tsx
│ ├── class-counter.tsx
│ ├── class-counter.usage.tsx
│ ├── error-message.tsx
│ ├── fc-counter-with-default-props.md
│ ├── fc-counter-with-default-props.stories.tsx
│ ├── fc-counter-with-default-props.tsx
│ ├── fc-counter-with-default-props.usage.tsx
│ ├── fc-counter.md
│ ├── fc-counter.stories.tsx
│ ├── fc-counter.tsx
│ ├── fc-counter.usage.tsx
│ ├── fc-spread-attributes.md
│ ├── fc-spread-attributes.stories.tsx
│ ├── fc-spread-attributes.tsx
│ ├── fc-spread-attributes.usage.tsx
│ ├── generic-list.md
│ ├── generic-list.stories.tsx
│ ├── generic-list.tsx
│ ├── generic-list.usage.tsx
│ ├── index.ts
│ ├── mouse-provider.md
│ ├── mouse-provider.stories.tsx
│ ├── mouse-provider.tsx
│ ├── mouse-provider.usage.tsx
│ ├── name-provider.md
│ ├── name-provider.tsx
│ └── name-provider.usage.tsx
├── connected
│ ├── fc-counter-connected-bind-action-creators.tsx
│ ├── fc-counter-connected-bind-action-creators.usage.tsx
│ ├── fc-counter-connected-own-props.spec.tsx
│ ├── fc-counter-connected-own-props.tsx
│ ├── fc-counter-connected-own-props.usage.tsx
│ ├── fc-counter-connected.tsx
│ ├── fc-counter-connected.usage.tsx
│ └── index.ts
├── context
│ ├── theme-consumer-class.tsx
│ ├── theme-consumer.tsx
│ ├── theme-context.ts
│ └── theme-provider.tsx
├── features
│ ├── app
│ │ └── epics.ts
│ ├── counters
│ │ ├── actions.ts
│ │ ├── actions.usage.ts
│ │ ├── constants.ts
│ │ ├── index.ts
│ │ ├── reducer.ts
│ │ └── selectors.ts
│ ├── todos-typesafe
│ │ ├── actions.ts
│ │ ├── index.ts
│ │ ├── models.ts
│ │ ├── reducer.ts
│ │ └── selectors.ts
│ └── todos
│ │ ├── __snapshots__
│ │ └── reducer.spec.ts.snap
│ │ ├── actions.ts
│ │ ├── constants.ts
│ │ ├── epics.spec.ts
│ │ ├── epics.ts
│ │ ├── index.ts
│ │ ├── models.ts
│ │ ├── reducer-ta.ts
│ │ ├── reducer.spec.ts
│ │ ├── reducer.ts
│ │ └── selectors.ts
├── hoc
│ ├── index.ts
│ ├── with-connected-count.tsx
│ ├── with-connected-count.usage.tsx
│ ├── with-error-boundary.tsx
│ ├── with-error-boundary.usage.tsx
│ ├── with-state.tsx
│ └── with-state.usage.tsx
├── hooks
│ ├── react-redux-hooks.tsx
│ ├── use-reducer.tsx
│ ├── use-state.tsx
│ └── use-theme-context.tsx
├── index.css
├── index.tsx
├── layout
│ ├── layout-footer.tsx
│ ├── layout-header.tsx
│ └── layout.tsx
├── models
│ ├── index.ts
│ ├── nominal-types.ts
│ └── user.ts
├── react-app-env.d.ts
├── routes
│ ├── home.tsx
│ └── not-found.tsx
├── serviceWorker.ts
├── services
│ ├── index.ts
│ ├── local-storage-service.ts
│ ├── logger-service.ts
│ └── types.d.ts
├── store
│ ├── hooks.ts
│ ├── index.ts
│ ├── redux-router.ts
│ ├── root-action.ts
│ ├── root-epic.ts
│ ├── root-reducer.ts
│ ├── store.ts
│ ├── types.d.ts
│ └── utils.ts
└── storyshots.disabled-test.ts
├── styleguide
├── docs
│ └── intro.md
├── package.json
└── styleguide.config.js
├── tsconfig.json
├── tsconfig.test.json
└── typings
├── augmentations.d.ts
├── globals.d.ts
├── modules.d.ts
├── redux-thunk
└── index.d.ts
└── redux
└── index.d.ts
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "react-redux-typescript-guide",
3 | "projectOwner": "piotrwitek",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "./CONTRIBUTORS.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": false,
11 | "contributors": [
12 | {
13 | "login": "piotrwitek",
14 | "name": "Piotrek Witek",
15 | "avatar_url": "https://avatars0.githubusercontent.com/u/739075?v=4",
16 | "profile": "https://github.com/piotrwitek",
17 | "contributions": [
18 | "code",
19 | "doc",
20 | "ideas",
21 | "review",
22 | "question"
23 | ]
24 | },
25 | {
26 | "login": "kazup01",
27 | "name": "Kazz Yokomizo",
28 | "avatar_url": "https://avatars3.githubusercontent.com/u/8602615?v=4",
29 | "profile": "https://github.com/kazup01",
30 | "contributions": [
31 | "financial",
32 | "fundingFinding"
33 | ]
34 | },
35 | {
36 | "login": "jakeboone02",
37 | "name": "Jake Boone",
38 | "avatar_url": "https://avatars1.githubusercontent.com/u/366438?v=4",
39 | "profile": "https://github.com/jakeboone02",
40 | "contributions": [
41 | "doc"
42 | ]
43 | },
44 | {
45 | "login": "amitdahan",
46 | "name": "Amit Dahan",
47 | "avatar_url": "https://avatars1.githubusercontent.com/u/9748762?v=4",
48 | "profile": "https://github.com/amitdahan",
49 | "contributions": [
50 | "doc"
51 | ]
52 | },
53 | {
54 | "login": "gulderov",
55 | "name": "gulderov",
56 | "avatar_url": "https://avatars1.githubusercontent.com/u/98167?v=4",
57 | "profile": "https://github.com/gulderov",
58 | "contributions": [
59 | "doc"
60 | ]
61 | },
62 | {
63 | "login": "emp823",
64 | "name": "Erik Pearson",
65 | "avatar_url": "https://avatars1.githubusercontent.com/u/1964212?v=4",
66 | "profile": "https://github.com/emp823",
67 | "contributions": [
68 | "doc"
69 | ]
70 | },
71 | {
72 | "login": "flymason",
73 | "name": "Bryan Mason",
74 | "avatar_url": "https://avatars1.githubusercontent.com/u/5342677?v=4",
75 | "profile": "https://github.com/flymason",
76 | "contributions": [
77 | "doc"
78 | ]
79 | },
80 | {
81 | "login": "chodorowicz",
82 | "name": "Jakub Chodorowicz",
83 | "avatar_url": "https://avatars1.githubusercontent.com/u/119451?v=4",
84 | "profile": "http://www.jakub.chodorowicz.pl/",
85 | "contributions": [
86 | "code"
87 | ]
88 | },
89 | {
90 | "login": "mleg",
91 | "name": "Oleg Maslov",
92 | "avatar_url": "https://avatars1.githubusercontent.com/u/7266431?v=4",
93 | "profile": "https://github.com/mleg",
94 | "contributions": [
95 | "bug"
96 | ]
97 | },
98 | {
99 | "login": "awestbro",
100 | "name": "Aaron Westbrook",
101 | "avatar_url": "https://avatars0.githubusercontent.com/u/3393293?v=4",
102 | "profile": "https://github.com/awestbro",
103 | "contributions": [
104 | "bug"
105 | ]
106 | },
107 | {
108 | "login": "peterblazejewicz",
109 | "name": "Peter Blazejewicz",
110 | "avatar_url": "https://avatars3.githubusercontent.com/u/14539?v=4",
111 | "profile": "http://www.linkedin.com/in/peterblazejewicz",
112 | "contributions": [
113 | "doc"
114 | ]
115 | },
116 | {
117 | "login": "rubysolo",
118 | "name": "Solomon White",
119 | "avatar_url": "https://avatars3.githubusercontent.com/u/1642?v=4",
120 | "profile": "https://github.com/rubysolo",
121 | "contributions": [
122 | "doc"
123 | ]
124 | },
125 | {
126 | "login": "pino",
127 | "name": "Levi Rocha",
128 | "avatar_url": "https://avatars2.githubusercontent.com/u/8838006?v=4",
129 | "profile": "https://github.com/pino",
130 | "contributions": [
131 | "doc"
132 | ]
133 | },
134 | {
135 | "login": "loadbalance-sudachi-kun",
136 | "name": "Sudachi-kun",
137 | "avatar_url": "https://avatars1.githubusercontent.com/u/41281835?v=4",
138 | "profile": "http://cloudnative.co.jp",
139 | "contributions": [
140 | "financial"
141 | ]
142 | },
143 | {
144 | "login": "sosukesuzuki",
145 | "name": "Sosuke Suzuki",
146 | "avatar_url": "https://avatars1.githubusercontent.com/u/14838850?v=4",
147 | "profile": "http://sosukesuzuki.github.io",
148 | "contributions": [
149 | "code"
150 | ]
151 | },
152 | {
153 | "login": "chillitom",
154 | "name": "Tom Rathbone",
155 | "avatar_url": "https://avatars0.githubusercontent.com/u/74433?v=4",
156 | "profile": "https://github.com/chillitom",
157 | "contributions": [
158 | "doc"
159 | ]
160 | },
161 | {
162 | "login": "arshadkazmi42",
163 | "name": "Arshad Kazmi",
164 | "avatar_url": "https://avatars3.githubusercontent.com/u/4654382?v=4",
165 | "profile": "https://arshadkazmi42.github.io/",
166 | "contributions": [
167 | "doc"
168 | ]
169 | },
170 | {
171 | "login": "JeongUkJae",
172 | "name": "JeongUkJae",
173 | "avatar_url": "https://avatars1.githubusercontent.com/u/8815362?v=4",
174 | "profile": "https://jeongukjae.github.io",
175 | "contributions": [
176 | "doc"
177 | ]
178 | }
179 | ]
180 | }
181 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: piotrekwitek # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: piotrwitek # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ["https://www.buymeacoffee.com/piotrekwitek"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | ---
5 |
6 | ## Description
7 |
8 |
9 | ## Steps to Reproduce
10 |
17 |
18 | ## Expected behavior
19 |
20 |
21 | ## Suggested solution(s)
22 |
23 |
24 | ## Project Dependencies
25 | - TypeScript Version: X.X.X
26 | - tsconfig.json:
27 |
28 |
29 | ## Environment (optional)
30 |
31 | - Browser and Version: XXX
32 | - OS: XXX
33 | - Node Version: XXX
34 | - Package Manager and Version: XXX
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Have a question? Please check our spectrum community chat.
4 | ---
5 |
6 | First of all please check our spectrum community chat and we recommend to ask your question there for a quickest response and the indexing in search engines:
7 | - https://spectrum.chat/react-redux-ts
8 |
9 | The only good reason to use issue tracker for your questions would be for "special requests" that doesn't fit into bug reports and feature requests categories.
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | ---
5 |
6 | ## Is your feature request related to a real problem or use-case?
7 |
8 |
9 | ## Describe a solution including usage in code example
10 |
11 |
12 | ## Who does this impact? Who is this for?
13 |
14 |
15 | ## Describe alternatives you've considered (optional)
16 |
17 |
18 | ## Additional context (optional)
19 |
20 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ## Description
5 |
6 |
7 | ## Related issues:
8 | - Resolved #XXX
9 |
10 | ## Checklist
11 |
12 | * [ ] I have read [CONTRIBUTING.md](https://github.com/piotrwitek/react-redux-typescript-guide/blob/master/CONTRIBUTING.md)
13 | * [ ] I have edited `README_SOURCE.md` (NOT `README.md`)
14 | * [ ] I have run CI script locally `npm run ci-check` to generate an updated `README.md`
15 | * [ ] I have linked all related issues above
16 | * [ ] I have rebased my branch
17 |
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | temp/
40 | playground/dist
41 | playground/storybook-static
42 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "pwa-chrome",
9 | "request": "launch",
10 | "name": "Launch Chrome against localhost",
11 | "url": "http://localhost:3080",
12 | "webRoot": "${workspaceFolder}/playground/src",
13 | "sourceMapPathOverrides": {
14 | "webpack:///src/*": "${webRoot}/*"
15 | }
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at piotrek.witek@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guide
2 |
3 | ## General
4 | 1. Make sure you have read and understand the **Goals** section to be aligned with project goals.
5 | 2. Before submitting a PR please comment in the relevant issue (or create a new one if it doesn't exist yet) to discuss all the requirements (this will prevent rejecting the PR and wasting your work).
6 | 3. All workflow scripts (prettier, linter, tests) must pass successfully (it is run automatically on CI and will fail on github checks).
7 |
8 | ## Edit `README_SOURCE.md` to generate an updated `README.md`
9 | Don't edit `README.md` directly - it is generated automatically from `README_SOURCE.md` using an automated script.
10 | - Use `sh ./generate-readme.sh` script to generate updated `README.md` (this will inject code examples using type-checked source files from the `/playground` folder)
11 | - So to make changes in code examples edit source files in `/playground` folder
12 |
13 | **Source code inject directives:**
14 | ```
15 | # Inject code block with highlighter
16 | ::codeblock='playground/src/components/fc-counter.tsx'::
17 |
18 | # Inject code block with highlighter and expander
19 | ::expander='playground/src/components/fc-counter.usage.tsx'::
20 | ```
21 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
2 |
3 |
4 |
5 | | [ Piotrek Witek](https://github.com/piotrwitek) [💻](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=piotrwitek "Code") [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=piotrwitek "Documentation") [🤔](#ideas-piotrwitek "Ideas, Planning, & Feedback") [👀](#review-piotrwitek "Reviewed Pull Requests") [💬](#question-piotrwitek "Answering Questions") | [ Kazz Yokomizo](https://github.com/kazup01) [💵](#financial-kazup01 "Financial") [🔍](#fundingFinding-kazup01 "Funding Finding") | [ Jake Boone](https://github.com/jakeboone02) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=jakeboone02 "Documentation") | [ Amit Dahan](https://github.com/amitdahan) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=amitdahan "Documentation") | [ gulderov](https://github.com/gulderov) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=gulderov "Documentation") | [ Erik Pearson](https://github.com/emp823) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=emp823 "Documentation") | [ Bryan Mason](https://github.com/flymason) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=flymason "Documentation") |
6 | | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
7 | | [ Jakub Chodorowicz](http://www.jakub.chodorowicz.pl/) [💻](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=chodorowicz "Code") | [ Oleg Maslov](https://github.com/mleg) [🐛](https://github.com/piotrwitek/react-redux-typescript-guide/issues?q=author%3Amleg "Bug reports") | [ Aaron Westbrook](https://github.com/awestbro) [🐛](https://github.com/piotrwitek/react-redux-typescript-guide/issues?q=author%3Aawestbro "Bug reports") | [ Peter Blazejewicz](http://www.linkedin.com/in/peterblazejewicz) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=peterblazejewicz "Documentation") | [ Solomon White](https://github.com/rubysolo) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=rubysolo "Documentation") | [ Levi Rocha](https://github.com/pino) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=pino "Documentation") | [ Sudachi-kun](http://cloudnative.co.jp) [💵](#financial-loadbalance-sudachi-kun "Financial") |
8 | | [ Sosuke Suzuki](http://sosukesuzuki.github.io) [💻](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=sosukesuzuki "Code") | [ Tom Rathbone](https://github.com/chillitom) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=chillitom "Documentation") | [ Arshad Kazmi](https://arshadkazmi42.github.io/) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=arshadkazmi42 "Documentation") | [ JeongUkJae](https://jeongukjae.github.io) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=JeongUkJae "Documentation") |
9 |
10 |
11 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Piotr Witek (http://piotrwitek.github.io)
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_SOURCE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # React & Redux in TypeScript - Complete Guide
4 |
5 | _"This guide is a **living compendium** documenting the most important patterns and recipes on how to use **React** (and its Ecosystem) in a **functional style** using **TypeScript**. It will help you make your code **completely type-safe** while focusing on **inferring the types from implementation** so there is less noise coming from excessive type annotations and it's easier to write and maintain correct types in the long run."_
6 |
7 | [](https://spectrum.chat/react-redux-ts)
8 | [](https://gitter.im/react-redux-typescript-guide/Lobby)
9 |
10 | _Found it useful? Want more updates?_
11 |
12 | [**Show your support by giving a :star:**](https://github.com/piotrwitek/react-redux-typescript-guide/stargazers)
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## **What's new?**
24 |
25 | :tada: _Now updated to support **TypeScript v4.6**_ :tada:
26 | :rocket: _Updated to `typesafe-actions@5.x` :rocket:
27 |
28 |
29 |
30 |
31 |
32 | ### **Goals**
33 |
34 | - Complete type safety (with [`--strict`](https://www.typescriptlang.org/docs/handbook/compiler-options.html) flag) without losing type information downstream through all the layers of our application (e.g. no type assertions or hacking with `any` type)
35 | - Make type annotations concise by eliminating redundancy in types using advanced TypeScript Language features like **Type Inference** and **Control flow analysis**
36 | - Reduce repetition and complexity of types with TypeScript focused [complementary libraries](#react-redux-typescript-ecosystem)
37 |
38 | ### **React, Redux, Typescript Ecosystem**
39 |
40 | - [typesafe-actions](https://github.com/piotrwitek/typesafe-actions) - Typesafe utilities for "action-creators" in Redux / Flux Architecture
41 | - [utility-types](https://github.com/piotrwitek/utility-types) - Collection of generic types for TypeScript, complementing built-in mapped types and aliases - think lodash for reusable types.
42 | - [react-redux-typescript-scripts](https://github.com/piotrwitek/react-redux-typescript-scripts) - dev-tools configuration files shared between projects based on this guide
43 |
44 | ### **Examples**
45 |
46 | - Todo-App playground: [Codesandbox](https://codesandbox.io/s/github/piotrwitek/typesafe-actions/tree/master/codesandbox)
47 | - React, Redux, TypeScript - RealWorld App: [Github](https://github.com/piotrwitek/react-redux-typescript-realworld-app) | [Demo](https://react-redux-typescript-realworld-app.netlify.com/)
48 |
49 | ### **Playground Project**
50 |
51 | [](https://semaphoreci.com/piotrekwitek/react-redux-typescript-guide)
52 |
53 | Check out our Playground Project located in the `/playground` folder. It contains all source files of the code examples found in the guide. They are all tested with the most recent version of TypeScript and 3rd party type-definitions (like `@types/react` or `@types/react-redux`) to ensure the examples are up-to-date and not broken with updated definitions (It's based on `create-react-app --typescript`).
54 | > Playground project was created so that you can simply clone the repository locally and immediately play around with all the component patterns found in the guide. It will help you to learn all the examples from this guide in a real project environment without the need to create complicated environment setup by yourself.
55 |
56 | ## Contributing Guide
57 |
58 | You can help make this project better by contributing. If you're planning to contribute please make sure to check our contributing guide: [CONTRIBUTING.md](/CONTRIBUTING.md)
59 |
60 | ## Funding
61 |
62 | You can also help by funding issues.
63 | Issues like bug fixes or feature requests can be very quickly resolved when funded through the IssueHunt platform.
64 |
65 | I highly recommend to add a bounty to the issue that you're waiting for to increase priority and attract contributors willing to work on it.
66 |
67 | [](https://issuehunt.io/repos/76996763)
68 |
69 | ---
70 |
71 | 🌟 - _New or updated section_
72 |
73 | ## Table of Contents
74 |
75 |
76 |
77 |
78 |
79 | - [React Types Cheatsheet](#react-types-cheatsheet)
80 | - [`React.FC` | `React.FunctionComponent`](#reactfcprops--reactfunctioncomponentprops)
81 | - [`React.Component`](#reactcomponentprops-state)
82 | - [`React.ComponentType`](#reactcomponenttypeprops)
83 | - [`React.ComponentProps`](#reactcomponentpropstypeof-xxx)
84 | - [`React.ReactElement` | `JSX.Element`](#reactreactelement--jsxelement)
85 | - [`React.ReactNode`](#reactreactnode)
86 | - [`React.CSSProperties`](#reactcssproperties)
87 | - [`React.XXXHTMLAttributes`](#reactxxxhtmlattributeshtmlxxxelement)
88 | - [`React.ReactEventHandler`](#reactreacteventhandlerhtmlxxxelement)
89 | - [`React.XXXEvent`](#reactxxxeventhtmlxxxelement)
90 | - [React](#react)
91 | - [Function Components - FC](#function-components---fc)
92 | - [- Counter Component](#--counter-component)
93 | - [- Counter Component with default props](#--counter-component-with-default-props)
94 | - [- Spreading attributes in Component](#--spreading-attributes-in-component)
95 | - [Class Components](#class-components)
96 | - [- Class Counter Component](#--class-counter-component)
97 | - [- Class Component with default props](#--class-component-with-default-props)
98 | - [Generic Components](#generic-components)
99 | - [- Generic List Component](#--generic-list-component)
100 | - [Hooks](#hooks)
101 | - [- useState](#--usestate)
102 | - [- useContext](#--usecontext)
103 | - [- useReducer](#--usereducer)
104 | - [Render Props](#render-props)
105 | - [- Name Provider Component](#--name-provider-component)
106 | - [- Mouse Provider Component](#--mouse-provider-component)
107 | - [Higher-Order Components](#higher-order-components)
108 | - [- HOC wrapping a component](#--hoc-wrapping-a-component)
109 | - [- HOC wrapping a component and injecting props](#--hoc-wrapping-a-component-and-injecting-props)
110 | - [- Nested HOC - wrapping a component, injecting props and connecting to redux 🌟](#--nested-hoc---wrapping-a-component-injecting-props-and-connecting-to-redux-)
111 | - [Redux Connected Components](#redux-connected-components)
112 | - [- Redux connected counter](#--redux-connected-counter)
113 | - [- Redux connected counter with own props](#--redux-connected-counter-with-own-props)
114 | - [- Redux connected counter via hooks](#--redux-connected-counter-via-hooks)
115 | - [- Redux connected counter with `redux-thunk` integration](#--redux-connected-counter-with-redux-thunk-integration)
116 | - [Context](#context)
117 | - [ThemeContext](#themecontext)
118 | - [ThemeProvider](#themeprovider)
119 | - [ThemeConsumer](#themeconsumer)
120 | - [ThemeConsumer in class component](#themeconsumer-in-class-component)
121 | - [Redux](#redux)
122 | - [Store Configuration](#store-configuration)
123 | - [Create Global Store Types](#create-global-store-types)
124 | - [Create Store](#create-store)
125 | - [Action Creators 🌟](#action-creators-)
126 | - [Reducers](#reducers)
127 | - [State with Type-level Immutability](#state-with-type-level-immutability)
128 | - [Typing reducer](#typing-reducer)
129 | - [Typing reducer with `typesafe-actions`](#typing-reducer-with-typesafe-actions)
130 | - [Testing reducer](#testing-reducer)
131 | - [Async Flow with `redux-observable`](#async-flow-with-redux-observable)
132 | - [Typing epics](#typing-epics)
133 | - [Testing epics](#testing-epics)
134 | - [Selectors with `reselect`](#selectors-with-reselect)
135 | - [Connect with `react-redux`](#connect-with-react-redux)
136 | - [Typing connected component](#typing-connected-component)
137 | - [Typing `useSelector` and `useDispatch`](#typing-useselector-and-usedispatch)
138 | - [Typing connected component with `redux-thunk` integration](#typing-connected-component-with-redux-thunk-integration)
139 | - [Configuration & Dev Tools](#configuration--dev-tools)
140 | - [Common Npm Scripts](#common-npm-scripts)
141 | - [tsconfig.json](#tsconfigjson)
142 | - [TSLib](#tslib)
143 | - [ESLint](#eslint)
144 | - [.eslintrc.js](#eslintrcjs)
145 | - [Jest](#jest)
146 | - [jest.config.json](#jestconfigjson)
147 | - [jest.stubs.js](#jeststubsjs)
148 | - [Style Guides](#style-guides)
149 | - [react-styleguidist](#react-styleguidist)
150 | - [FAQ](#faq)
151 | - [Ambient Modules](#ambient-modules)
152 | - [Imports in ambient modules](#imports-in-ambient-modules)
153 | - [Type-Definitions](#type-definitions)
154 | - [Missing type-definitions error](#missing-type-definitions-error)
155 | - [Using custom `d.ts` files for npm modules](#using-custom-dts-files-for-npm-modules)
156 | - [Type Augmentation](#type-augmentation)
157 | - [Augmenting library internal declarations - using relative import](#augmenting-library-internal-declarations---using-relative-import)
158 | - [Augmenting library public declarations - using node_modules import](#augmenting-library-public-declarations---using-node_modules-import)
159 | - [Misc](#misc)
160 | - [- should I still use React.PropTypes in TS?](#--should-i-still-use-reactproptypes-in-ts)
161 | - [- when to use `interface` declarations and when `type` aliases?](#--when-to-use-interface-declarations-and-when-type-aliases)
162 | - [- what's better default or named exports?](#--whats-better-default-or-named-exports)
163 | - [- how to best initialize class instance or static properties?](#--how-to-best-initialize-class-instance-or-static-properties)
164 | - [- how to best declare component handler functions?](#--how-to-best-declare-component-handler-functions)
165 | - [Tutorials & Articles](#tutorials--articles)
166 | - [Contributors](#contributors)
167 |
168 |
169 |
170 | ---
171 |
172 | # Installation
173 |
174 | ## Types for React & Redux
175 |
176 | ```
177 | npm i -D @types/react @types/react-dom @types/react-redux
178 | ```
179 |
180 | "react" - `@types/react`
181 | "react-dom" - `@types/react-dom`
182 | "redux" - (types included with npm package)*
183 | "react-redux" - `@types/react-redux`
184 |
185 | > *NB: Guide is based on types for Redux >= v4.x.x.
186 |
187 | [⇧ back to top](#table-of-contents)
188 |
189 | ---
190 |
191 | ## React Types Cheatsheet
192 |
193 | ### `React.FC` | `React.FunctionComponent`
194 |
195 | Type representing a functional component
196 |
197 | ```tsx
198 | const MyComponent: React.FC = ...
199 | ```
200 |
201 | ### `React.Component`
202 |
203 | Type representing a class component
204 |
205 | ```tsx
206 | class MyComponent extends React.Component { ...
207 | ```
208 |
209 | ### `React.ComponentType`
210 |
211 | Type representing union of (`React.FC | React.Component`) - used in HOC
212 |
213 | ```tsx
214 | const withState =
(
215 | WrappedComponent: React.ComponentType
,
216 | ) => { ...
217 | ```
218 |
219 | ### `React.ComponentProps`
220 |
221 | Gets Props type of a specified component XXX (WARNING: does not work with statically declared default props and generic props)
222 |
223 | ```tsx
224 | type MyComponentProps = React.ComponentProps;
225 | ```
226 |
227 | ### `React.ReactElement` | `JSX.Element`
228 |
229 | Type representing a concept of React Element - representation of a native DOM component (e.g. ``), or a user-defined composite component (e.g. ``)
230 |
231 | ```tsx
232 | const elementOnly: React.ReactElement = || ;
233 | ```
234 |
235 | ### `React.ReactNode`
236 |
237 | Type representing any possible type of React node (basically ReactElement (including Fragments and Portals) + primitive JS types)
238 |
239 | ```tsx
240 | const elementOrPrimitive: React.ReactNode = 'string' || 0 || false || null || undefined || || ;
241 | const Component = ({ children: React.ReactNode }) => ...
242 | ```
243 |
244 | ### `React.CSSProperties`
245 |
246 | Type representing style object in JSX - for css-in-js styles
247 |
248 | ```tsx
249 | const styles: React.CSSProperties = { flexDirection: 'row', ...
250 | const element =
`
254 |
255 | Type representing HTML attributes of specified HTML Element - for extending HTML Elements
256 |
257 | ```tsx
258 | const Input: React.FC> = props => { ... }
259 |
260 |
261 | ```
262 |
263 | ### `React.ReactEventHandler`
264 |
265 | Type representing generic event handler - for declaring event handlers
266 |
267 | ```tsx
268 | const handleChange: React.ReactEventHandler = (ev) => { ... }
269 |
270 |
271 | ```
272 |
273 | ### `React.XXXEvent`
274 |
275 | Type representing more specific event. Some common event examples: `ChangeEvent, FormEvent, FocusEvent, KeyboardEvent, MouseEvent, DragEvent, PointerEvent, WheelEvent, TouchEvent`.
276 |
277 | ```tsx
278 | const handleChange = (ev: React.MouseEvent) => { ... }
279 |
280 |
281 | ```
282 |
283 | In code above `React.MouseEvent` is type of mouse event, and this event happened on `HTMLDivElement`
284 |
285 | [⇧ back to top](#table-of-contents)
286 |
287 | ---
288 |
289 | # React
290 |
291 | ## Function Components - FC
292 |
293 | ### - Counter Component
294 |
295 | ::codeblock='playground/src/components/fc-counter.tsx'::
296 |
297 | [⟩⟩⟩ demo](https://piotrwitek.github.io/react-redux-typescript-guide/#fccounter)
298 |
299 | [⇧ back to top](#table-of-contents)
300 |
301 | ### - Counter Component with default props
302 |
303 | ::codeblock='playground/src/components/fc-counter-with-default-props.tsx'::
304 |
305 | [⟩⟩⟩ demo](https://piotrwitek.github.io/react-redux-typescript-guide/#fccounterwithdefaultprops)
306 |
307 | [⇧ back to top](#table-of-contents)
308 |
309 | ### - [Spreading attributes](https://facebook.github.io/react/docs/jsx-in-depth.html#spread-attributes) in Component
310 |
311 | ::codeblock='playground/src/components/fc-spread-attributes.tsx'::
312 |
313 | [⟩⟩⟩ demo](https://piotrwitek.github.io/react-redux-typescript-guide/#fcspreadattributes)
314 |
315 | [⇧ back to top](#table-of-contents)
316 |
317 | ---
318 |
319 | ## Class Components
320 |
321 | ### - Class Counter Component
322 |
323 | ::codeblock='playground/src/components/class-counter.tsx'::
324 |
325 | [⟩⟩⟩ demo](https://piotrwitek.github.io/react-redux-typescript-guide/#classcounter)
326 |
327 | [⇧ back to top](#table-of-contents)
328 |
329 | ### - Class Component with default props
330 |
331 | ::codeblock='playground/src/components/class-counter-with-default-props.tsx'::
332 |
333 | [⟩⟩⟩ demo](https://piotrwitek.github.io/react-redux-typescript-guide/#classcounterwithdefaultprops)
334 |
335 | [⇧ back to top](#table-of-contents)
336 |
337 | ---
338 |
339 | ## Generic Components
340 |
341 | - easily create typed component variations and reuse common logic
342 | - common use case is a generic list components
343 |
344 | ### - Generic List Component
345 |
346 | ::codeblock='playground/src/components/generic-list.tsx'::
347 |
348 | [⟩⟩⟩ demo](https://piotrwitek.github.io/react-redux-typescript-guide/#genericlist)
349 |
350 | [⇧ back to top](#table-of-contents)
351 |
352 | ---
353 |
354 | ## Hooks
355 |
356 | >
357 |
358 | ### - useState
359 |
360 | >
361 |
362 | ::codeblock='playground/src/hooks/use-state.tsx'::
363 |
364 | [⇧ back to top](#table-of-contents)
365 |
366 | ### - useContext
367 |
368 | >
369 |
370 | ::codeblock='playground/src/hooks/use-theme-context.tsx'::
371 |
372 | [⇧ back to top](#table-of-contents)
373 |
374 | ### - useReducer
375 |
376 | >
377 |
378 | ::codeblock='playground/src/hooks/use-reducer.tsx'::
379 |
380 | [⇧ back to top](#table-of-contents)
381 |
382 | ---
383 |
384 | ## Render Props
385 |
386 | >
387 |
388 | ### - Name Provider Component
389 |
390 | Simple component using children as a render prop
391 |
392 | ::codeblock='playground/src/components/name-provider.tsx'::
393 |
394 | [⟩⟩⟩ demo](https://piotrwitek.github.io/react-redux-typescript-guide/#nameprovider)
395 |
396 | [⇧ back to top](#table-of-contents)
397 |
398 | ### - Mouse Provider Component
399 |
400 | `Mouse` component found in [Render Props React Docs](https://reactjs.org/docs/render-props.html#use-render-props-for-cross-cutting-concerns)
401 |
402 | ::codeblock='playground/src/components/mouse-provider.tsx'::
403 |
404 | [⟩⟩⟩ demo](https://piotrwitek.github.io/react-redux-typescript-guide/#mouseprovider)
405 |
406 | [⇧ back to top](#table-of-contents)
407 |
408 | ---
409 |
410 | ## Higher-Order Components
411 |
412 | >
413 |
414 | ### - HOC wrapping a component
415 |
416 | Adds state to a stateless counter
417 |
418 | ::codeblock='playground/src/hoc/with-state.tsx'::
419 | ::expander='playground/src/hoc/with-state.usage.tsx'::
420 |
421 | [⇧ back to top](#table-of-contents)
422 |
423 | ### - HOC wrapping a component and injecting props
424 |
425 | Adds error handling using componentDidCatch to any component
426 |
427 | ::codeblock='playground/src/hoc/with-error-boundary.tsx'::
428 | ::expander='playground/src/hoc/with-error-boundary.usage.tsx'::
429 |
430 | [⇧ back to top](#table-of-contents)
431 |
432 | ### - Nested HOC - wrapping a component, injecting props and connecting to redux 🌟
433 |
434 | Adds error handling using componentDidCatch to any component
435 |
436 | ::codeblock='playground/src/hoc/with-connected-count.tsx'::
437 | ::expander='playground/src/hoc/with-connected-count.usage.tsx'::
438 |
439 | [⇧ back to top](#table-of-contents)
440 |
441 | ---
442 |
443 | ## Redux Connected Components
444 |
445 | ### - Redux connected counter
446 |
447 | ::codeblock='playground/src/connected/fc-counter-connected.tsx'::
448 | ::expander='playground/src/connected/fc-counter-connected.usage.tsx'::
449 |
450 | [⇧ back to top](#table-of-contents)
451 |
452 | ### - Redux connected counter with own props
453 |
454 | ::codeblock='playground/src/connected/fc-counter-connected-own-props.tsx'::
455 | ::expander='playground/src/connected/fc-counter-connected-own-props.usage.tsx'::
456 |
457 | [⇧ back to top](#table-of-contents)
458 |
459 | ### - Redux connected counter via hooks
460 |
461 | ::codeblock='playground/src/hooks/react-redux-hooks.tsx'::
462 |
463 | [⇧ back to top](#table-of-contents)
464 |
465 | ### - Redux connected counter with `redux-thunk` integration
466 |
467 | ::codeblock='playground/src/connected/fc-counter-connected-bind-action-creators.tsx'::
468 | ::expander='playground/src/connected/fc-counter-connected-bind-action-creators.usage.tsx'::
469 |
470 | [⇧ back to top](#table-of-contents)
471 |
472 | ## Context
473 |
474 | >
475 |
476 | ### ThemeContext
477 |
478 | ::codeblock='playground/src/context/theme-context.ts'::
479 |
480 | [⇧ back to top](#table-of-contents)
481 |
482 | ### ThemeProvider
483 |
484 | ::codeblock='playground/src/context/theme-provider.tsx'::
485 |
486 | [⇧ back to top](#table-of-contents)
487 |
488 | ### ThemeConsumer
489 |
490 | ::codeblock='playground/src/context/theme-consumer.tsx'::
491 |
492 | ### ThemeConsumer in class component
493 |
494 | ::codeblock='playground/src/context/theme-consumer-class.tsx'::
495 |
496 | [Implementation with Hooks](#--usecontext)
497 |
498 | [⇧ back to top](#table-of-contents)
499 |
500 |
501 | ---
502 |
503 | # Redux
504 |
505 | ## Store Configuration
506 |
507 | ### Create Global Store Types
508 |
509 | #### `RootState` - type representing root state-tree
510 |
511 | Can be imported in connected components to provide type-safety to Redux `connect` function
512 |
513 | #### `RootAction` - type representing union type of all action objects
514 |
515 | Can be imported in various layers receiving or sending redux actions like: reducers, sagas or redux-observables epics
516 |
517 | ::codeblock='playground/src/store/types.d.ts'::
518 |
519 | [⇧ back to top](#table-of-contents)
520 |
521 | ### Create Store
522 |
523 | When creating a store instance we don't need to provide any additional types. It will set-up a **type-safe Store instance** using type inference.
524 | > The resulting store instance methods like `getState` or `dispatch` will be type checked and will expose all type errors
525 |
526 | ::codeblock='playground/src/store/store.ts'::
527 |
528 | ---
529 |
530 | ## Action Creators 🌟
531 |
532 | > We'll be using a battle-tested helper library [`typesafe-actions`](https://github.com/piotrwitek/typesafe-actions#typesafe-actions) [](https://www.npmjs.com/package/typesafe-actions) [](https://www.npmjs.com/package/typesafe-actions) that's designed to make it easy and fun working with **Redux** in **TypeScript**.
533 |
534 | > To learn more please check this in-depth tutorial: [Typesafe-Actions - Tutorial](https://github.com/piotrwitek/typesafe-actions#tutorial)!
535 |
536 | A solution below is using a simple factory function to automate the creation of type-safe action creators. The goal is to decrease maintenance effort and reduce code repetition of type annotations for actions and creators. The result is completely typesafe action-creators and their actions.
537 |
538 | ::codeblock='playground/src/features/counters/actions.ts'::
539 | ::expander='playground/src/features/counters/actions.usage.ts'::
540 |
541 | [⇧ back to top](#table-of-contents)
542 |
543 | ---
544 |
545 | ## Reducers
546 |
547 | ### State with Type-level Immutability
548 |
549 | Declare reducer `State` type with `readonly` modifier to get compile time immutability
550 |
551 | ```ts
552 | export type State = {
553 | readonly counter: number;
554 | readonly todos: ReadonlyArray;
555 | };
556 | ```
557 |
558 | Readonly modifier allow initialization, but will not allow reassignment by highlighting compiler errors
559 |
560 | ```ts
561 | export const initialState: State = {
562 | counter: 0,
563 | }; // OK
564 |
565 | initialState.counter = 3; // TS Error: cannot be mutated
566 | ```
567 |
568 | It's great for **Arrays in JS** because it will error when using mutator methods like (`push`, `pop`, `splice`, ...), but it'll still allow immutable methods like (`concat`, `map`, `slice`,...).
569 |
570 | ```ts
571 | state.todos.push('Learn about tagged union types') // TS Error: Property 'push' does not exist on type 'ReadonlyArray'
572 | const newTodos = state.todos.concat('Learn about tagged union types') // OK
573 | ```
574 |
575 | #### Caveat - `Readonly` is not recursive
576 |
577 | This means that the `readonly` modifier doesn't propagate immutability down the nested structure of objects. You'll need to mark each property on each level explicitly.
578 |
579 | > **TIP:** use `Readonly` or `ReadonlyArray` [Mapped types](https://www.typescriptlang.org/docs/handbook/advanced-types.html)
580 |
581 | ```ts
582 | export type State = Readonly<{
583 | counterPairs: ReadonlyArray>,
587 | }>;
588 |
589 | state.counterPairs[0] = { immutableCounter1: 1, immutableCounter2: 1 }; // TS Error: cannot be mutated
590 | state.counterPairs[0].immutableCounter1 = 1; // TS Error: cannot be mutated
591 | state.counterPairs[0].immutableCounter2 = 1; // TS Error: cannot be mutated
592 | ```
593 |
594 | #### Solution - recursive `Readonly` is called `DeepReadonly`
595 |
596 | To fix this we can use [`DeepReadonly`](https://github.com/piotrwitek/utility-types#deepreadonlyt) type (available from `utility-types`).
597 |
598 | ```ts
599 | import { DeepReadonly } from 'utility-types';
600 |
601 | export type State = DeepReadonly<{
602 | containerObject: {
603 | innerValue: number,
604 | numbers: number[],
605 | }
606 | }>;
607 |
608 | state.containerObject = { innerValue: 1 }; // TS Error: cannot be mutated
609 | state.containerObject.innerValue = 1; // TS Error: cannot be mutated
610 | state.containerObject.numbers.push(1); // TS Error: cannot use mutator methods
611 | ```
612 |
613 | [⇧ back to top](#table-of-contents)
614 |
615 | ### Typing reducer
616 |
617 | > to understand following section make sure to learn about [Type Inference](https://www.typescriptlang.org/docs/handbook/type-inference.html), [Control flow analysis](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#control-flow-based-type-analysis) and [Tagged union types](https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#tagged-union-types)
618 |
619 | ::codeblock='playground/src/features/todos/reducer.ts'::
620 |
621 | [⇧ back to top](#table-of-contents)
622 |
623 | ### Typing reducer with `typesafe-actions`
624 |
625 | > Notice we are not required to use any generic type parameter in the API. Try to compare it with regular reducer as they are equivalent.
626 |
627 | ::codeblock='playground/src/features/todos/reducer-ta.ts'::
628 |
629 | [⇧ back to top](#table-of-contents)
630 |
631 | ### Testing reducer
632 |
633 | ::codeblock='playground/src/features/todos/reducer.spec.ts'::
634 |
635 | [⇧ back to top](#table-of-contents)
636 |
637 | ---
638 |
639 | ## Async Flow with `redux-observable`
640 |
641 | ### Typing epics
642 |
643 | ::codeblock='playground/src/features/todos/epics.ts'::
644 |
645 | [⇧ back to top](#table-of-contents)
646 |
647 | ### Testing epics
648 |
649 | ::codeblock='playground/src/features/todos/epics.spec.ts'::
650 |
651 | [⇧ back to top](#table-of-contents)
652 |
653 | ---
654 |
655 | ## Selectors with `reselect`
656 |
657 | ::codeblock='playground/src/features/todos/selectors.ts'::
658 |
659 | [⇧ back to top](#table-of-contents)
660 |
661 | ---
662 |
663 | ## Connect with `react-redux`
664 |
665 | ### Typing connected component
666 |
667 | _**NOTE**: Below you'll find a short explanation of concepts behind using `connect` with TypeScript. For more detailed examples please check [Redux Connected Components](#redux-connected-components) section._
668 |
669 | ```tsx
670 | import MyTypes from 'MyTypes';
671 |
672 | import { bindActionCreators, Dispatch, ActionCreatorsMapObject } from 'redux';
673 | import { connect } from 'react-redux';
674 |
675 | import { countersActions } from '../features/counters';
676 | import { FCCounter } from '../components';
677 |
678 | // Type annotation for "state" argument is mandatory to check
679 | // the correct shape of state object and injected props you can also
680 | // extend connected component Props interface by annotating `ownProps` argument
681 | const mapStateToProps = (state: MyTypes.RootState, ownProps: FCCounterProps) => ({
682 | count: state.counters.reduxCounter,
683 | });
684 |
685 | // "dispatch" argument needs an annotation to check the correct shape
686 | // of an action object when using dispatch function
687 | const mapDispatchToProps = (dispatch: Dispatch) =>
688 | bindActionCreators({
689 | onIncrement: countersActions.increment,
690 | }, dispatch);
691 |
692 | // shorter alternative is to use an object instead of mapDispatchToProps function
693 | const dispatchToProps = {
694 | onIncrement: countersActions.increment,
695 | };
696 |
697 | // Notice we don't need to pass any generic type parameters to neither
698 | // the connect function below nor map functions declared above
699 | // because type inference will infer types from arguments annotations automatically
700 | // This is much cleaner and idiomatic approach
701 | export const FCCounterConnected =
702 | connect(mapStateToProps, mapDispatchToProps)(FCCounter);
703 |
704 | // You can add extra layer of validation of your action creators
705 | // by using bindActionCreators generic type parameter and RootAction type
706 | const mapDispatchToProps = (dispatch: Dispatch) =>
707 | bindActionCreators>({
708 | invalidActionCreator: () => 1, // Error: Type 'number' is not assignable to type '{ type: "todos/ADD"; payload: Todo; } | { ... }
709 | }, dispatch);
710 |
711 | ```
712 |
713 | [⇧ back to top](#table-of-contents)
714 |
715 | ### Typing `useSelector` and `useDispatch`
716 |
717 | ::codeblock='playground/src/store/hooks.ts'::
718 |
719 | [⇧ back to top](#table-of-contents)
720 |
721 | ### Typing connected component with `redux-thunk` integration
722 |
723 | _**NOTE**: When using thunk action creators you need to use `bindActionCreators`. Only this way you can get corrected dispatch props type signature like below.*_
724 |
725 | _**WARNING**: As of now (Apr 2019) `bindActionCreators` signature of the latest `redux-thunk` release will not work as below, you need to use our modified type definitions that you can find here [`/playground/typings/redux-thunk/index.d.ts`](./playground/typings/redux-thunk/index.d.ts) and then add `paths` overload in your tsconfig like this: [`"paths":{"redux-thunk":["typings/redux-thunk"]}`](./playground/tsconfig.json)._
726 |
727 | ```tsx
728 | const thunkAsyncAction = () => async (dispatch: Dispatch): Promise => {
729 | // dispatch actions, return Promise, etc.
730 | }
731 |
732 | const mapDispatchToProps = (dispatch: Dispatch) =>
733 | bindActionCreators(
734 | {
735 | thunkAsyncAction,
736 | },
737 | dispatch
738 | );
739 |
740 | type DispatchProps = ReturnType;
741 | // { thunkAsyncAction: () => Promise; }
742 |
743 | /* Without "bindActionCreators" fix signature will be the same as the original "unbound" thunk function: */
744 | // { thunkAsyncAction: () => (dispatch: Dispatch) => Promise; }
745 | ```
746 |
747 | [⇧ back to top](#table-of-contents)
748 |
749 | ---
750 |
751 | # Configuration & Dev Tools
752 |
753 | ## Common Npm Scripts
754 |
755 | > Common TS-related npm scripts shared across projects
756 |
757 | ```json
758 | "prettier": "prettier --list-different 'src/**/*.ts' || (echo '\nPlease fix code formatting by running:\nnpm run prettier:fix\n'; exit 1)",
759 | "prettier:fix": "prettier --write 'src/**/*.ts'",
760 | "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx",
761 | "tsc": "tsc -p ./ --noEmit",
762 | "tsc:watch": "tsc -p ./ --noEmit -w",
763 | "test": "jest --config jest.config.json",
764 | "test:watch": "jest --config jest.config.json --watch",
765 | "test:update": "jest --config jest.config.json -u"
766 | "ci-check": "npm run prettier && npm run lint && npm run tsc && npm run test",
767 | ```
768 |
769 | [⇧ back to top](#table-of-contents)
770 |
771 | ## tsconfig.json
772 |
773 | We have recommended `tsconfig.json` that you can easily add to your project thanks to [`react-redux-typescript-scripts`](https://github.com/piotrwitek/react-redux-typescript-scripts) package.
774 |
775 | ::expander='playground/tsconfig.json'::
776 |
777 | [⇧ back to top](#table-of-contents)
778 |
779 | ## TSLib
780 |
781 | This library will cut down on your bundle size, thanks to using external runtime helpers instead of adding them per each file.
782 |
783 | >
784 |
785 | > Installation
786 | `npm i tslib`
787 |
788 |
789 | Then add this to your `tsconfig.json`:
790 |
791 | ```ts
792 | "compilerOptions": {
793 | "importHelpers": true
794 | }
795 | ```
796 |
797 | [⇧ back to top](#table-of-contents)
798 |
799 | ## ESLint
800 |
801 | We have recommended config that will automatically add a parser & plugin for TypeScript thanks to [`react-redux-typescript-scripts`](https://github.com/piotrwitek/react-redux-typescript-scripts) package.
802 |
803 | >
804 |
805 | > Installation
806 | `npm i -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin`
807 |
808 |
809 | ### .eslintrc.js
810 |
811 | ::expander='playground/.eslintrc.js'::
812 |
813 | [⇧ back to top](#table-of-contents)
814 |
815 | ## Jest
816 |
817 | >
818 |
819 | > Installation
820 | `npm i -D jest ts-jest @types/jest`
821 |
822 | ### jest.config.json
823 |
824 | ::expander='configs/jest.config.json'::
825 |
826 | ### jest.stubs.js
827 |
828 | ::expander='configs/jest.stubs.js'::
829 |
830 | [⇧ back to top](#table-of-contents)
831 |
832 | ## Style Guides
833 |
834 | ### [react-styleguidist](https://github.com/styleguidist/react-styleguidist)
835 |
836 | [⟩⟩⟩ styleguide.config.js](/playground/styleguide.config.js)
837 |
838 | [⟩⟩⟩ demo](https://piotrwitek.github.io/react-redux-typescript-guide/)
839 |
840 | [⇧ back to top](#table-of-contents)
841 |
842 | ---
843 |
844 | # FAQ
845 |
846 |
847 | ## Ambient Modules
848 |
849 | ### Imports in ambient modules
850 |
851 | For type augmentation imports should stay outside of module declaration.
852 |
853 | ```ts
854 | import { Operator } from 'rxjs/Operator';
855 | import { Observable } from 'rxjs/Observable';
856 |
857 | declare module 'rxjs/Subject' {
858 | interface Subject {
859 | lift(operator: Operator): Observable;
860 | }
861 | }
862 | ```
863 |
864 | When creating 3rd party type-definitions all the imports should be kept inside the module declaration, otherwise it will be treated as augmentation and show error
865 |
866 | ```ts
867 | declare module "react-custom-scrollbars" {
868 | import * as React from "react";
869 | export interface positionValues {
870 | ...
871 | ```
872 |
873 | [⇧ back to top](#table-of-contents)
874 |
875 | ## Type-Definitions
876 |
877 | ### Missing type-definitions error
878 |
879 | if you cannot find types for a third-party module you can provide your own types or disable type-checking for this module using [Shorthand Ambient Modules](https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Modules.md#shorthand-ambient-modules)
880 |
881 | ::codeblock='playground/typings/modules.d.ts'::
882 |
883 | ### Using custom `d.ts` files for npm modules
884 |
885 | If you want to use an alternative (customized) type-definitions for some npm module (that usually comes with it's own type-definitions), you can do it by adding an override in `paths` compiler option.
886 |
887 | ```ts
888 | {
889 | "compilerOptions": {
890 | "baseUrl": ".",
891 | "paths": {
892 | "redux": ["typings/redux"], // use an alternative type-definitions instead of the included one
893 | ...
894 | },
895 | ...,
896 | }
897 | }
898 | ```
899 |
900 | [⇧ back to top](#table-of-contents)
901 |
902 | ## Type Augmentation
903 |
904 | Strategies to fix issues coming from external type-definitions files (*.d.ts)
905 |
906 | ### Augmenting library internal declarations - using relative import
907 |
908 | ```ts
909 | // added missing autoFocus Prop on Input component in "antd@2.10.0" npm package
910 | declare module '../node_modules/antd/lib/input/Input' {
911 | export interface InputProps {
912 | autoFocus?: boolean;
913 | }
914 | }
915 | ```
916 |
917 | ### Augmenting library public declarations - using node_modules import
918 |
919 | ```ts
920 | // fixed broken public type-definitions in "rxjs@5.4.1" npm package
921 | import { Operator } from 'rxjs/Operator';
922 | import { Observable } from 'rxjs/Observable';
923 |
924 | declare module 'rxjs/Subject' {
925 | interface Subject {
926 | lift(operator: Operator): Observable;
927 | }
928 | }
929 | ```
930 |
931 | > More advanced scenarios for working with vendor type-definitions can be found here [Official TypeScript Docs](https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Modules.md#working-with-other-javascript-libraries)
932 |
933 | [⇧ back to top](#table-of-contents)
934 |
935 | ## Misc
936 |
937 | ### - should I still use React.PropTypes in TS?
938 |
939 | No. With TypeScript, using PropTypes is an unnecessary overhead. When declaring Props and State interfaces, you will get complete intellisense and design-time safety with static type checking. This way you'll be safe from runtime errors and you will save a lot of time on debugging. Additional benefit is an elegant and standardized method of documenting your component public API in the source code.
940 |
941 | [⇧ back to top](#table-of-contents)
942 |
943 | ### - when to use `interface` declarations and when `type` aliases?
944 |
945 | From practical side, using `interface` declaration will create an identity (interface name) in compiler errors, on the contrary `type` aliases doesn't create an identity and will be unwinded to show all the properties and nested types it consists of.
946 | Although I prefer to use `type` most of the time there are some places this can become too noisy when reading compiler errors and that's why I like to leverage this distinction to hide some of not so important type details in errors using interfaces identity.
947 | Related `ts-lint` rule:
948 |
949 | [⇧ back to top](#table-of-contents)
950 |
951 | ### - what's better default or named exports?
952 |
953 | A common flexible solution is to use module folder pattern, because you can leverage both named and default import when you see fit.
954 | With this solution you'll achieve better encapsulation and be able to safely refactor internal naming and folders structure without breaking your consumer code:
955 |
956 | ```ts
957 | // 1. create your component files (`select.tsx`) using default export in some folder:
958 |
959 | // components/select.tsx
960 | const Select: React.FC = (props) => {
961 | ...
962 | export default Select;
963 |
964 | // 2. in this folder create an `index.ts` file that will re-export components with named exports:
965 |
966 | // components/index.ts
967 | export { default as Select } from './select';
968 | ...
969 |
970 | // 3. now you can import your components in both ways, with named export (better encapsulation) or using default export (internal access):
971 |
972 | // containers/container.tsx
973 | import { Select } from '@src/components';
974 | or
975 | import Select from '@src/components/select';
976 | ...
977 | ```
978 |
979 | [⇧ back to top](#table-of-contents)
980 |
981 | ### - how to best initialize class instance or static properties?
982 |
983 | Prefered modern syntax is to use class Property Initializers
984 |
985 | ```tsx
986 | class ClassCounterWithInitialCount extends React.Component {
987 | // default props using Property Initializers
988 | static defaultProps: DefaultProps = {
989 | className: 'default-class',
990 | initialCount: 0,
991 | };
992 |
993 | // initial state using Property Initializers
994 | state: State = {
995 | count: this.props.initialCount,
996 | };
997 | ...
998 | }
999 | ```
1000 |
1001 | [⇧ back to top](#table-of-contents)
1002 |
1003 | ### - how to best declare component handler functions?
1004 |
1005 | Prefered modern syntax is to use Class Fields with arrow functions
1006 |
1007 | ```tsx
1008 | class ClassCounter extends React.Component {
1009 | // handlers using Class Fields with arrow functions
1010 | handleIncrement = () => {
1011 | this.setState({ count: this.state.count + 1 });
1012 | };
1013 | ...
1014 | }
1015 | ```
1016 |
1017 | [⇧ back to top](#table-of-contents)
1018 |
1019 | ---
1020 |
1021 | # Tutorials & Articles
1022 |
1023 | > Curated list of relevant in-depth tutorials
1024 |
1025 | Higher-Order Components:
1026 |
1027 | -
1028 |
1029 | [⇧ back to top](#table-of-contents)
1030 |
1031 | ---
1032 |
1033 | # Contributors
1034 |
1035 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
1036 |
1037 |
1038 |
1039 | | [ Piotrek Witek](https://github.com/piotrwitek) [💻](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=piotrwitek "Code") [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=piotrwitek "Documentation") [🤔](#ideas-piotrwitek "Ideas, Planning, & Feedback") [👀](#review-piotrwitek "Reviewed Pull Requests") [💬](#question-piotrwitek "Answering Questions") | [ Kazz Yokomizo](https://github.com/kazup01) [💵](#financial-kazup01 "Financial") [🔍](#fundingFinding-kazup01 "Funding Finding") | [ Jake Boone](https://github.com/jakeboone02) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=jakeboone02 "Documentation") | [ Amit Dahan](https://github.com/amitdahan) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=amitdahan "Documentation") | [ gulderov](https://github.com/gulderov) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=gulderov "Documentation") | [ Erik Pearson](https://github.com/emp823) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=emp823 "Documentation") | [ Bryan Mason](https://github.com/flymason) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=flymason "Documentation") |
1040 | | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
1041 | | [ Jakub Chodorowicz](http://www.jakub.chodorowicz.pl/) [💻](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=chodorowicz "Code") | [ Oleg Maslov](https://github.com/mleg) [🐛](https://github.com/piotrwitek/react-redux-typescript-guide/issues?q=author%3Amleg "Bug reports") | [ Aaron Westbrook](https://github.com/awestbro) [🐛](https://github.com/piotrwitek/react-redux-typescript-guide/issues?q=author%3Aawestbro "Bug reports") | [ Peter Blazejewicz](http://www.linkedin.com/in/peterblazejewicz) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=peterblazejewicz "Documentation") | [ Solomon White](https://github.com/rubysolo) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=rubysolo "Documentation") | [ Levi Rocha](https://github.com/pino) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=pino "Documentation") | [ Sudachi-kun](http://cloudnative.co.jp) [💵](#financial-loadbalance-sudachi-kun "Financial") |
1042 | | [ Sosuke Suzuki](http://sosukesuzuki.github.io) [💻](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=sosukesuzuki "Code") | [ Tom Rathbone](https://github.com/chillitom) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=chillitom "Documentation") | [ Arshad Kazmi](https://arshadkazmi42.github.io/) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=arshadkazmi42 "Documentation") | [ JeongUkJae](https://jeongukjae.github.io) [📖](https://github.com/piotrwitek/react-redux-typescript-guide/commits?author=JeongUkJae "Documentation") |
1043 |
1044 |
1045 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
1046 |
1047 | ---
1048 |
1049 | MIT License
1050 |
1051 | Copyright (c) 2017 Piotr Witek ()
1052 |
--------------------------------------------------------------------------------
/configs/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "verbose": true,
3 | "transform": {
4 | ".(ts|tsx)": "ts-jest"
5 | },
6 | "testRegex": "(/spec/.*|\\.(test|spec))\\.(ts|tsx|js)$",
7 | "moduleFileExtensions": ["ts", "tsx", "js"],
8 | "moduleNameMapper": {
9 | "^Components/(.*)": "./src/components/$1"
10 | },
11 | "globals": {
12 | "window": {},
13 | "ts-jest": {
14 | "tsConfig": "./tsconfig.json"
15 | }
16 | },
17 | "setupFiles": ["./jest.stubs.js"],
18 | "testURL": "http://localhost/"
19 | }
20 |
--------------------------------------------------------------------------------
/configs/jest.stubs.js:
--------------------------------------------------------------------------------
1 | // Global/Window object Stubs for Jest
2 | window.matchMedia = window.matchMedia || function () {
3 | return {
4 | matches: false,
5 | addListener: function () { },
6 | removeListener: function () { },
7 | };
8 | };
9 |
10 | window.requestAnimationFrame = function (callback) {
11 | setTimeout(callback);
12 | };
13 |
14 | window.localStorage = {
15 | getItem: function () { },
16 | setItem: function () { },
17 | };
18 |
19 | Object.values = () => [];
20 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React & Redux in TypeScript - Component Typing Patterns
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/generate-readme.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | const ROOT_PATH = `${__dirname}/`;
4 | const inputFiles = [ROOT_PATH + 'README_SOURCE.md'];
5 | const outputFile = ROOT_PATH + 'README.md';
6 |
7 | const result = inputFiles
8 | .map(filePath => fs.readFileSync(filePath, 'utf8'))
9 | .map(injectCodeBlocks)
10 | .map(injectExpanders)
11 | .toString();
12 |
13 | fs.writeFileSync(outputFile, result, 'utf8');
14 |
15 | function injectCodeBlocks(text) {
16 | const regex = /::codeblock='(.+?)'::/g;
17 | return text.replace(regex, createMatchReplacer(withSourceWrapper));
18 | }
19 |
20 | function injectExpanders(text) {
21 | const regex = /::expander='(.+?)'::/g;
22 | return text.replace(regex, createMatchReplacer(withDetailsWrapper));
23 | }
24 |
25 | function createMatchReplacer(wrapper) {
26 | return (match, filePath) => {
27 | console.log(ROOT_PATH + filePath);
28 | const text = fs.readFileSync(ROOT_PATH + filePath, 'utf8');
29 | return wrapper(text);
30 | };
31 | }
32 |
33 | function withSourceWrapper(text) {
34 | return `
35 | ${'```tsx'}
36 | ${text}
37 | ${'```'}
38 | `.trim();
39 | }
40 |
41 | function withDetailsWrapper(text) {
42 | return `
43 | Click to expand
25 | {/*
26 | Instead of providing a static representation of what renders,
27 | use the `render` prop to dynamically determine what to render.
28 | */}
29 | {this.props.render(this.state)}
30 |