├── .dockerignore
├── .gitignore
├── Dockerfile
├── README.md
├── now.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── App.css
├── App.tsx
├── Counter.tsx
├── CounterReducer.tsx
├── CounterTitle.tsx
├── GitHubLogo.tsx
├── Title.css
├── Title.tsx
├── index.css
├── index.tsx
├── react-app-env.d.ts
└── registerServiceWorker.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | *
2 | !src
3 | !public
4 | !package.json
5 | !yarn.lock
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # From https://zeit.co/docs/examples/create-react-app
2 |
3 | # Use Node.js version 10
4 | FROM mhart/alpine-node:10
5 |
6 | # Set the working directory
7 | WORKDIR /usr/src
8 |
9 | # Copy package manager files to the working directory and run install
10 | COPY package.json yarn.lock ./
11 | RUN yarn install
12 |
13 | # Copy all files to the working directory
14 | COPY . .
15 |
16 | # Build the app and move the resulting build to the `/public` directory
17 | RUN yarn build
18 | RUN mv ./build /public
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 🎣 React Hooks TypeScript Examples
2 |
3 | [View the live demo here](https://react-hooks-typescript-example-ljpijrbetn.now.sh/) or [edit it live in CodeSandbox](https://codesandbox.io/s/github/skovy/react-hooks-typescript-example).
4 |
5 | ### Getting Started
6 |
7 | Check out [this post to create a React TypeScript app from scratch with support for React Hooks](https://medium.com/@skovy/using-react-hooks-with-typescript-aae6c7b2a3a9), or:
8 |
9 | - Clone this repository `git clone git@github.com:skovy/react-hooks-typescript-example.git`
10 | - Install the dependencies `yarn install`
11 | - Start the app `yarn start`
12 | - Experiment with React Hooks!
13 |
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "static",
3 | "static": {
4 | "rewrites": [
5 | {
6 | "source": "**",
7 | "destination": "/index.html"
8 | }
9 | ]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-hooks-typescript-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "main": "src/index.tsx",
6 | "dependencies": {
7 | "@types/react": "16.8.7",
8 | "@types/react-dom": "^16.8.2",
9 | "react": "16.8.4",
10 | "react-dom": "16.8.4",
11 | "react-scripts": "2.1.8"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test",
17 | "eject": "react-scripts eject"
18 | },
19 | "devDependencies": {
20 | "@types/jest": "^23.3.9",
21 | "@types/node": "^10.12.5",
22 | "typescript": "^3.1.6"
23 | },
24 | "browserslist": [
25 | ">0.2%",
26 | "not dead",
27 | "not ie <= 11",
28 | "not op_mini all"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skovy/react-hooks-typescript-example/e49307f802dff207eb1986ce48a87229f28737f4/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
24 | React TypeScript Hooks
25 |
26 |
27 |
28 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | min-height: 100vh;
6 | }
7 |
8 | .app {
9 | text-align: center;
10 | padding: 32px;
11 | border-radius: 8px;
12 | box-shadow: 0 2px 16px 0 rgba(0, 0, 0, 0.5);
13 | background-color: #444444;
14 | min-width: 600px;
15 | }
16 |
17 | .separator {
18 | margin: 16px 0;
19 | height: 4px;
20 | border-radius: 2px;
21 | width: 100%;
22 | background-color: #f353d0;
23 | }
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import "./App.css";
3 | import Counter from "./Counter";
4 | import CounterReducer from "./CounterReducer";
5 | import CounterTitle from "./CounterTitle";
6 | import GitHubLogo from "./GitHubLogo";
7 | import Title from "./Title";
8 |
9 | // An object of all possible example components that can be rendered
10 | const EXAMPLES = {
11 | Counter,
12 | CounterTitle,
13 | CounterReducer
14 | };
15 |
16 | type Examples = keyof typeof EXAMPLES;
17 |
18 | const EXAMPLE_NAMES = Object.keys(EXAMPLES) as Examples[];
19 |
20 | const App = () => {
21 | // Use state to keep track of the current displayed example component
22 | const [example, setExample] = React.useState("Counter");
23 |
24 | // The currently selected example component that should be rendered
25 | const ExampleComponent = EXAMPLES[example];
26 |
27 | // A list of buttons for all examples to render
28 | const exampleButtons = EXAMPLE_NAMES.map(name => (
29 |
37 | ));
38 |
39 | return (
40 | <>
41 |
42 |
43 |
44 |
45 | {exampleButtons}
46 |
47 |
48 |
49 |
50 | >
51 | );
52 | };
53 |
54 | export default App;
55 |
--------------------------------------------------------------------------------
/src/Counter.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const Counter = () => {
4 | const [count, setCount] = React.useState(0);
5 |
6 | return (
7 |
8 |
You clicked {count} times
9 |
10 |
11 | );
12 | };
13 |
14 | export default Counter;
15 |
--------------------------------------------------------------------------------
/src/CounterReducer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | interface State {
4 | count: number;
5 | }
6 |
7 | type Actions = "reset" | "increment" | "decrement";
8 |
9 | interface Action {
10 | type: Actions;
11 | }
12 |
13 | const initialState: State = { count: 0 };
14 |
15 | const reducer: React.Reducer = (state, action) => {
16 | switch (action.type) {
17 | case "reset":
18 | return initialState;
19 | case "increment":
20 | return { count: state.count + 1 };
21 | case "decrement":
22 | return { count: state.count - 1 };
23 | default:
24 | return state;
25 | }
26 | };
27 |
28 | const CounterReducer = () => {
29 | const [state, dispatch] = React.useReducer>(
30 | reducer,
31 | initialState
32 | );
33 |
34 | return (
35 | <>
36 | Count: {state.count}
37 |
38 |
39 |
40 | >
41 | );
42 | };
43 |
44 | export default CounterReducer;
45 |
--------------------------------------------------------------------------------
/src/CounterTitle.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const CounterTitle = () => {
4 | const [count, setCount] = React.useState(0);
5 |
6 | React.useEffect(
7 | () => {
8 | // Update the document title to the current count
9 | document.title = `You clicked ${count} times`;
10 |
11 | // Cleanup the effect so that the document title is properly restored
12 | return () => {
13 | document.title = "React TypeScript Hooks";
14 | };
15 | },
16 | [count]
17 | );
18 |
19 | return (
20 |
21 |
You clicked {count} times
22 |
23 |
24 | );
25 | };
26 |
27 | export default CounterTitle;
28 |
--------------------------------------------------------------------------------
/src/GitHubLogo.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const GitHubLogo = () => {
4 | return (
5 |
10 |
37 |
38 | );
39 | };
40 |
41 | export default GitHubLogo;
42 |
--------------------------------------------------------------------------------
/src/Title.css:
--------------------------------------------------------------------------------
1 | .title {
2 | position: absolute;
3 | top: 8px;
4 | left: 16px;
5 | }
6 |
7 | .title h1 {
8 | color: #fff;
9 | margin: 0;
10 | padding: 0;
11 | font-size: 32px;
12 | }
13 |
--------------------------------------------------------------------------------
/src/Title.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import "./Title.css";
3 |
4 | const Title = () => {
5 | return (
6 |
7 |
React Hooks TypeScript
8 |
9 | );
10 | };
11 |
12 | export default Title;
13 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: "Poppins", sans-serif;
5 | font-weight: 500;
6 | background-image: radial-gradient(circle at 62% 44%, #f353d0, #5801bc);
7 | min-height: 100vh;
8 | font-size: 16px;
9 | color: #fff;
10 | }
11 |
12 | #root {
13 | min-height: 100vh;
14 | }
15 |
16 | p {
17 | margin: 0 0 8px;
18 | }
19 |
20 | button {
21 | border-radius: 16px;
22 | background-color: #9339f9;
23 | padding: 8px 16px;
24 | border: 0;
25 | color: #fff;
26 | font-size: 16px;
27 | transition: background-color 200ms ease;
28 | outline: none;
29 | cursor: pointer;
30 | font-weight: 700;
31 | }
32 |
33 | button + button {
34 | margin-left: 8px;
35 | }
36 |
37 | button:hover,
38 | button:focus,
39 | button.active {
40 | background-color: #f353d0;
41 | }
42 |
43 | .github-corner:hover .octo-arm {
44 | animation: octocat-wave 560ms ease-in-out;
45 | }
46 |
47 | @keyframes octocat-wave {
48 | 0%,
49 | 100% {
50 | transform: rotate(0);
51 | }
52 | 20%,
53 | 60% {
54 | transform: rotate(-25deg);
55 | }
56 | 40%,
57 | 80% {
58 | transform: rotate(10deg);
59 | }
60 | }
61 |
62 | @media (max-width: 500px) {
63 | .github-corner:hover .octo-arm {
64 | animation: none;
65 | }
66 |
67 | .github-corner .octo-arm {
68 | animation: octocat-wave 560ms ease-in-out;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import App from './App';
4 | import './index.css';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(
8 | ,
9 | document.getElementById('root') as HTMLElement
10 | );
11 | registerServiceWorker();
12 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable:no-console
2 | // In production, we register a service worker to serve assets from local cache.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on the 'N+1' visit to a page, since previously
7 | // cached resources are updated in the background.
8 |
9 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
10 | // This link also includes instructions on opting out of this behavior.
11 |
12 | const isLocalhost = Boolean(
13 | window.location.hostname === 'localhost' ||
14 | // [::1] is the IPv6 localhost address.
15 | window.location.hostname === '[::1]' ||
16 | // 127.0.0.1/8 is considered localhost for IPv4.
17 | window.location.hostname.match(
18 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
19 | )
20 | );
21 |
22 | export default function register() {
23 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
24 | // The URL constructor is available in all browsers that support SW.
25 | const publicUrl = new URL(
26 | process.env.PUBLIC_URL!,
27 | window.location.toString()
28 | );
29 | if (publicUrl.origin !== window.location.origin) {
30 | // Our service worker won't work if PUBLIC_URL is on a different origin
31 | // from what our page is served on. This might happen if a CDN is used to
32 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
33 | return;
34 | }
35 |
36 | window.addEventListener('load', () => {
37 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
38 |
39 | if (isLocalhost) {
40 | // This is running on localhost. Lets check if a service worker still exists or not.
41 | checkValidServiceWorker(swUrl);
42 |
43 | // Add some additional logging to localhost, pointing developers to the
44 | // service worker/PWA documentation.
45 | navigator.serviceWorker.ready.then(() => {
46 | console.log(
47 | 'This web app is being served cache-first by a service ' +
48 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
49 | );
50 | });
51 | } else {
52 | // Is not local host. Just register service worker
53 | registerValidSW(swUrl);
54 | }
55 | });
56 | }
57 | }
58 |
59 | function registerValidSW(swUrl: string) {
60 | navigator.serviceWorker
61 | .register(swUrl)
62 | .then(registration => {
63 | registration.onupdatefound = () => {
64 | const installingWorker = registration.installing;
65 | if (installingWorker) {
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the old content will have been purged and
70 | // the fresh content will have been added to the cache.
71 | // It's the perfect time to display a 'New content is
72 | // available; please refresh.' message in your web app.
73 | console.log('New content is available; please refresh.');
74 | } else {
75 | // At this point, everything has been precached.
76 | // It's the perfect time to display a
77 | // 'Content is cached for offline use.' message.
78 | console.log('Content is cached for offline use.');
79 | }
80 | }
81 | };
82 | }
83 | };
84 | })
85 | .catch(error => {
86 | console.error('Error during service worker registration:', error);
87 | });
88 | }
89 |
90 | function checkValidServiceWorker(swUrl: string) {
91 | // Check if the service worker can be found. If it can't reload the page.
92 | fetch(swUrl)
93 | .then(response => {
94 | // Ensure service worker exists, and that we really are getting a JS file.
95 | if (
96 | response.status === 404 ||
97 | response.headers.get('content-type')!.indexOf('javascript') === -1
98 | ) {
99 | // No service worker found. Probably a different app. Reload the page.
100 | navigator.serviceWorker.ready.then(registration => {
101 | registration.unregister().then(() => {
102 | window.location.reload();
103 | });
104 | });
105 | } else {
106 | // Service worker found. Proceed as normal.
107 | registerValidSW(swUrl);
108 | }
109 | })
110 | .catch(() => {
111 | console.log(
112 | 'No internet connection found. App is running in offline mode.'
113 | );
114 | });
115 | }
116 |
117 | export function unregister() {
118 | if ('serviceWorker' in navigator) {
119 | navigator.serviceWorker.ready.then(registration => {
120 | registration.unregister();
121 | });
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "allowJs": true,
5 | "skipLibCheck": false,
6 | "esModuleInterop": true,
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "preserve",
16 | "lib": [
17 | "dom",
18 | "dom.iterable",
19 | "esnext"
20 | ]
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
3 | "linterOptions": {
4 | "exclude": [
5 | "config/**/*.js",
6 | "node_modules/**/*.ts",
7 | "coverage/lcov-report/*.js"
8 | ]
9 | },
10 | "rules": {
11 | "jsx-no-lambda": false,
12 | "object-literal-sort-keys": false,
13 | "interface-name": [true, "never-prefix"]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------