The given page has not been found. Sure you did not mistype?
9 | = () => {
6 | const [value, setValue] = React.useState(0);
7 |
8 | React.useEffect(() => {
9 | const tid = setTimeout(() => setValue(value + 1), 1000);
10 | return () => clearTimeout(tid);
11 | }, [value]);
12 |
13 | return (
14 | <>
15 | Ticker
16 | The following ticker will increment every second:
17 |
18 | Note: We use a local state, so page changes won't preserve the value.
19 | >
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/common/pages/TodoPage.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { RouteComponentProps } from 'react-router';
3 | import { useGlobalState, actions } from '../state';
4 |
5 | export const AddTodo: React.FC = () => {
6 | const [message, setMessage] = React.useState('');
7 | return (
8 |
17 | );
18 | };
19 |
20 | const AvailableTodos: React.FC = () => {
21 | const [todos] = useGlobalState('todos');
22 | return (
23 |
30 | );
31 | };
32 |
33 | export const TodoPage: React.FC = () => (
34 | <>
35 | ToDos
36 |
37 |
38 | >
39 | );
40 |
--------------------------------------------------------------------------------
/src/common/state.ts:
--------------------------------------------------------------------------------
1 | import * as rawActions from './actions';
2 | import { createStore } from 'react-hooks-global-state';
3 | import { GlobalState } from './types';
4 |
5 | type RawActions = typeof rawActions;
6 | type ActionNames = keyof RawActions;
7 | type RemainingArgs = T extends (_: any, ...args: infer U) => any ? U : never;
8 | type Actions = {
9 | [P in ActionNames]: {
10 | type: P;
11 | payload: RemainingArgs;
12 | };
13 | }[ActionNames];
14 |
15 | function reducer(state: GlobalState, action: Actions) {
16 | const fn = rawActions[action.type];
17 | return fn(state, ...action.payload);
18 | }
19 |
20 | const initialState: GlobalState = { todos: [] };
21 |
22 | export const { GlobalStateProvider, useGlobalState, dispatch } = createStore(reducer, initialState);
23 |
24 | type NewActions = {
25 | [P in ActionNames]: (...args: RemainingArgs) => void;
26 | };
27 |
28 | export const actions = Object.keys(rawActions).reduce((obj, type: ActionNames) => {
29 | obj[type] = (...payload) =>
30 | dispatch({
31 | type,
32 | payload,
33 | });
34 | return obj;
35 | }, {} as NewActions);
36 |
--------------------------------------------------------------------------------
/src/common/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from './state';
2 | export * from './todo';
3 |
--------------------------------------------------------------------------------
/src/common/types/state.ts:
--------------------------------------------------------------------------------
1 | import { Todo } from './todo';
2 |
3 | export interface GlobalState {
4 | todos: Array;
5 | }
6 |
--------------------------------------------------------------------------------
/src/common/types/todo.ts:
--------------------------------------------------------------------------------
1 | export interface Todo {
2 | id: string;
3 | done: boolean;
4 | message: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/server/createView.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { StaticRouter } from 'react-router';
3 | import { App, GlobalStateProvider } from '../common';
4 |
5 | export function createView(path: string) {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/server/index.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 | import { renderIndex } from './renderIndex';
3 | import { retrieveAsset } from './retrieveAsset';
4 |
5 | const app = express();
6 | const port = process.env.PORT || 3000;
7 |
8 | // happy path
9 | app.get('/', renderIndex);
10 |
11 | // define more routes here (e.g., for POSTing data)
12 |
13 | // resolve any static content, such as /app.js
14 | app.get('*', retrieveAsset);
15 |
16 | // SPA fallback
17 | app.get('*', renderIndex);
18 |
19 | app.listen(port, () => {
20 | console.log(`Server running at [ http://localhost:${port} ] ...`);
21 | });
22 |
--------------------------------------------------------------------------------
/src/server/renderIndex.ts:
--------------------------------------------------------------------------------
1 | import { RequestHandler } from 'express';
2 | import { renderToString } from 'react-dom/server';
3 | import { createView } from './createView';
4 |
5 | export const renderIndex: RequestHandler = (req, res) => {
6 | const view = createView(req.path);
7 | const content = renderToString(view);
8 | res.send(`
9 |
10 |
11 |
12 |
13 |
14 | Sample Document
15 |
16 |
17 | ${content}
18 |
19 |
20 |
21 | `);
22 | };
23 |
--------------------------------------------------------------------------------
/src/server/retrieveAsset.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 | import { resolve } from 'path';
3 |
4 | const clientDir = resolve(__dirname, '..', 'client');
5 |
6 | export const retrieveAsset = express.static(clientDir, {
7 | fallthrough: true,
8 | });
9 |
--------------------------------------------------------------------------------
/tsconfig.client.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "inlineSourceMap": true,
5 | "declaration": false,
6 | "outDir": "dist/client",
7 | "lib": [
8 | "es2015",
9 | "es2016",
10 | "dom"
11 | ]
12 | },
13 | "include": [
14 | "src/client/**/*"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": false,
4 | "noImplicitAny": true,
5 | "alwaysStrict": true,
6 | "strictNullChecks": true,
7 | "module": "commonjs",
8 | "target": "es6",
9 | "moduleResolution": "node",
10 | "jsx": "react"
11 | },
12 | "include": [
13 | "./src/**/*"
14 | ],
15 | "exclude": [
16 | "node_modules/**/*"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/tsconfig.server.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "sourceMap": true,
5 | "declaration": true,
6 | "outDir": "dist",
7 | "lib": [
8 | "es2015",
9 | "es2016",
10 | "dom",
11 | "esnext.asynciterable"
12 | ]
13 | },
14 | "include": [
15 | "src/server/**/*"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: ['./src/client/index.ts'],
5 | module: {
6 | rules: [
7 | {
8 | test: /\.tsx?$/,
9 | use: {
10 | loader: 'ts-loader',
11 | options: {
12 | configFile: 'tsconfig.client.json',
13 | },
14 | },
15 | exclude: /node_modules/,
16 | },
17 | ],
18 | },
19 | resolve: {
20 | extensions: ['.tsx', '.ts', '.js'],
21 | },
22 | output: {
23 | filename: 'app.js',
24 | path: path.resolve(__dirname, 'dist', 'client'),
25 | },
26 | };
27 |
--------------------------------------------------------------------------------