( 8 | App: React.ReactElement
,
9 | globalState: string | undefined,
10 | ): Promise
28 | {visibileTodos.map(todo => (
29 |
36 | )
37 | }
38 |
39 | export default TodoList;
--------------------------------------------------------------------------------
/src/examples/3-reactive-architecture-w-redux/2-rxjs/1-currying/bonus-flow.ts:
--------------------------------------------------------------------------------
1 | import { pipe } from 'fp-ts/lib/pipeable';
2 | import { flow } from 'fp-ts/lib/function';
3 |
4 | const plus = (a: number, b: number) => a + b
5 | const curriedPlus = (a: number) => (b: number) => a + b
6 |
7 | const allOps = (input: number) => pipe(
8 | input,
9 | curriedPlus(28),
10 | Math.floor,
11 | String,
12 | Array.of,
13 | Array.isArray
14 | )
15 | export const output1 = allOps(1234.5678);
16 | export const output2 = allOps(8765.4321);
17 |
18 | export const allOpsFlow = flow(
19 | curriedPlus(28),
20 | Math.floor,
21 | String,
22 | Array.of,
23 | Array.isArray
24 | )
25 | export const flowOutput1 = allOpsFlow(1234.5678);
26 | export const flowOutput2 = allOpsFlow(8765.4321);
27 |
28 | export const multiparam = flow(
29 | plus,
30 | Math.floor,
31 | String,
32 | Array.of,
33 | Array.isArray
34 | )
35 | export const multiparamOutput1 = multiparam(28, 1234.5678);
36 | export const multiparamOutput2 = multiparam(42, 8765.4321);
--------------------------------------------------------------------------------
/.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": "chrome",
9 | "request": "launch",
10 | "name": "Debug Create React App",
11 | "url": "http://localhost:3000",
12 | "webRoot": "${workspaceFolder}"
13 | },
14 | {
15 | "type": "chrome",
16 | "request": "launch",
17 | "name": "Debug SSR Client",
18 | "url": "http://localhost:3006",
19 | "webRoot": "${workspaceFolder}",
20 | "sourceMaps": true
21 | },
22 | {
23 | "name": "Debug SSR Server",
24 | "type": "node",
25 | "request": "launch",
26 | "cwd": "${workspaceRoot}",
27 | "program": "${workspaceRoot}/server/server.tsx",
28 | "outFiles": [
29 | "${workspaceRoot}/server-build/**/*.js"
30 | ],
31 | "sourceMaps": true
32 | }
33 | ]
34 | }
--------------------------------------------------------------------------------
/src/examples/optional-next-js/5-hydration-safe-fetch/NodeSafeWindow.ts:
--------------------------------------------------------------------------------
1 | const safeWindow = {
2 | __INITIAL__DATA__: '',
3 | ...(typeof window !== 'undefined'
4 | ? {
5 | ...window,
6 | addEventListener: Home
;
48 | }
49 |
50 | function About() {
51 | return About
;
52 | }
53 |
54 | function Users() {
55 | return Users
;
56 | }
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/examples/1-react-router/6-bonus-other-adts/FoldableHelpers.ts:
--------------------------------------------------------------------------------
1 | import { createFoldObject } from '@iadvize-oss/foldable-helpers';
2 |
3 | interface Home {
4 | readonly type: 'Home';
5 | }
6 |
7 | interface About {
8 | readonly type: 'About';
9 | }
10 |
11 | interface Topics {
12 | readonly type: 'Topics';
13 | }
14 |
15 | interface TopicsID {
16 | readonly type: 'TopicsID';
17 | readonly id: string;
18 | }
19 |
20 | interface NotFound {
21 | readonly type: 'NotFound';
22 | }
23 |
24 | type Location = Home | About | Topics | TopicsID | NotFound;
25 |
26 | const fold = createFoldObject({
27 | Home: (l): l is Home => l.type === 'Home',
28 | About: (l): l is About => l.type === 'About',
29 | Topics: (l): l is Topics => l.type === 'Topics',
30 | TopicsID: (l): l is TopicsID => l.type === 'TopicsID',
31 | NotFound: (l): l is NotFound => l.type === 'NotFound',
32 | });
33 |
34 | const format = fold({
35 | Home: () => '/',
36 | About: () => '/about',
37 | Topics: () => '/topics',
38 | TopicsID: ({ id }) => `/topics/${id}`,
39 | NotFound: () => '/',
40 | });
41 |
42 | const locations: Location[] = [
43 | { type: 'Home' },
44 | { type: 'About' },
45 | { type: 'Topics' },
46 | { type: 'TopicsID', id: 'someid' },
47 | ];
48 | locations.map(format).forEach(urls => {
49 | console.log(`formatted url: ${urls}`);
50 | });
51 |
--------------------------------------------------------------------------------
/src/examples/3-reactive-architecture-w-redux/3-redux-observable/1-popstate/App.tsx:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import { createEpicMiddleware } from 'redux-observable';
3 | import React from 'react';
4 | import { Provider } from 'react-redux';
5 | import ReduxApp from '../../1-redux/3-morphic-ts/ReduxApp';
6 | import { safeReducer, curriedReducer } from '../../1-redux/5-router-attempt/reducer';
7 | import { defaultAppState, AppState } from '../../1-redux/1-global-state/AppState';
8 | import { AppAction } from '../../1-redux/3-morphic-ts/AppAction';
9 | import { routeToVisibilityFilter, parse } from '../../../2-data-model/4-ssot/Location';
10 | import epic from './epic';
11 |
12 | const epicMiddleware = createEpicMiddleware => (
19 | nullableState,
20 | action
21 | ) => {
22 | if (!nullableState || !adt.verified(action)) return defaultState;
23 | return curriedReducer(action)(nullableState);
24 | }
25 |
26 | export const curriedReducer = AppAction.matchStrict<(a: AppState) => AppState>({
27 | ADD_TODO: ({ text }) => M.Lens
28 | .fromProp => (
12 | nullableState,
13 | action
14 | ) => {
15 | if (!nullableState || !adt.verified(action)) return defaultState;
16 | return curriedReducer(action)(nullableState);
17 | }
18 |
19 | export const curriedReducer = (
20 | pushUrl: (url: string) => void,
21 | ) => AppAction.matchStrict<(a: AppState) => AppState>({
22 | ADD_TODO: ({ text }) => (state) => {
23 | const { todos } = state;
24 | const highestID = todos.length > 0
25 | ? todos.reduce(
26 | (acc, cur) => cur.id > acc.id
27 | ? cur
28 | : acc
29 | ).id
30 | : 0;
31 | const newTodo: TodoType = {
32 | id: highestID + 1,
33 | text: text,
34 | completed: false,
35 | }
36 | return {
37 | ...state,
38 | todos: [...todos, newTodo]
39 | };
40 | },
41 | SET_VISIBILITY_FILTER: ({ filter }) => (state) => {
42 | pushUrl(format(
43 | visibilityFilterToRoute(
44 | filter
45 | )
46 | ))
47 | return {
48 | ...state,
49 | visibilityFilter: filter,
50 | };
51 | },
52 | TOGGLE_TODO: ({ id }) => (state) => {
53 | const { todos } = state;
54 | const newTodos = todos.map(
55 | t => t.id === id
56 | ? {
57 | ...t,
58 | completed: !t.completed,
59 | }
60 | : t
61 | );
62 | return {
63 | ...state,
64 | todos: newTodos,
65 | };
66 | },
67 | });
--------------------------------------------------------------------------------
/src/examples/3-reactive-architecture-w-redux/1-redux/5-router-attempt/reducer.ts:
--------------------------------------------------------------------------------
1 | import { Reducer, Action } from "redux";
2 | import { AppState, TodoType } from "../1-global-state/AppState";
3 | import { AppAction } from '../3-morphic-ts/AppAction';
4 | import { ADT } from "@morphic-ts/adt";
5 | import { format, visibilityFilterToRoute } from '../../../2-data-model/4-ssot/Location';
6 |
7 | export const safeReducer = (
8 | curriedReducer: (a: A) => (s: S) => S,
9 | defaultState: S,
10 | adt: ADT
11 | ): Reducer => (
12 | nullableState,
13 | action
14 | ) => {
15 | if (!nullableState || !adt.verified(action)) return defaultState;
16 | return curriedReducer(action)(nullableState);
17 | }
18 |
19 | export const curriedReducer = AppAction.matchStrict<(a: AppState) => AppState>({
20 | ADD_TODO: ({ text }) => (state) => {
21 | const { todos } = state;
22 | const highestID = todos.length > 0
23 | ? todos.reduce(
24 | (acc, cur) => cur.id > acc.id
25 | ? cur
26 | : acc
27 | ).id
28 | : 0;
29 | const newTodo: TodoType = {
30 | id: highestID + 1,
31 | text: text,
32 | completed: false,
33 | }
34 | return {
35 | ...state,
36 | todos: [...todos, newTodo]
37 | };
38 | },
39 | SET_VISIBILITY_FILTER: ({ filter }) => (state) => {
40 | window.history.pushState(
41 | null,
42 | '',
43 | format(
44 | visibilityFilterToRoute(
45 | filter
46 | )
47 | ),
48 | )
49 | return {
50 | ...state,
51 | visibilityFilter: filter,
52 | };
53 | },
54 | TOGGLE_TODO: ({ id }) => (state) => {
55 | const { todos } = state;
56 | const newTodos = todos.map(
57 | t => t.id === id
58 | ? {
59 | ...t,
60 | completed: !t.completed,
61 | }
62 | : t
63 | );
64 | return {
65 | ...state,
66 | todos: newTodos,
67 | };
68 | },
69 | });
--------------------------------------------------------------------------------
/src/examples/1-react-router/2-pushstate-onpopstate/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Link from '../common/Link';
3 |
4 | export default function App() {
5 | const [counter, setCounter] = useState(0);
6 | const [pathname, setPathname] = useState(window.location.pathname);
7 | const updatePathname = (newLocation: string) => {
8 | setPathname(newLocation);
9 | window.history.pushState(null, '', newLocation);
10 | }
11 | window.addEventListener('popstate', () => {
12 | setPathname(window.location.pathname);
13 | });
14 | let innerComponent: JSX.Element;
15 | switch (pathname) {
16 | case '/':
17 | innerComponent = Home
;
71 | }
72 |
73 | function About() {
74 | return About
;
75 | }
76 |
77 | function Users() {
78 | return Users
;
79 | }
80 |
81 |
82 |
--------------------------------------------------------------------------------
/src/examples/3-reactive-architecture-w-redux/1-redux/3-morphic-ts/reducer.ts:
--------------------------------------------------------------------------------
1 | import { Reducer, Action } from "redux";
2 | import { AppState, TodoType } from "../1-global-state/AppState";
3 | import { AppAction } from "./AppAction";
4 | import { ADT } from "@morphic-ts/adt";
5 |
6 | /* const safeReducer = (
7 | curriedReducer: (a: AppAction) => (s: AppState) => AppState,
8 | ): Reducer => (
21 | nullableState,
22 | action
23 | ) => {
24 | if (!nullableState || !adt.verified(action)) return defaultState;
25 | return curriedReducer(action)(nullableState);
26 | }
27 |
28 | export const curriedReducer = AppAction.matchStrict<(a: AppState) => AppState>({
29 | ADD_TODO: ({ text }) => (state) => {
30 | const { todos } = state;
31 | const highestID = todos.length > 0
32 | ? todos.reduce(
33 | (acc, cur) => cur.id > acc.id
34 | ? cur
35 | : acc
36 | ).id
37 | : 0;
38 | const newTodo: TodoType = {
39 | id: highestID + 1,
40 | text: text,
41 | completed: false,
42 | }
43 | return {
44 | ...state,
45 | todos: [...todos, newTodo]
46 | };
47 | },
48 | SET_VISIBILITY_FILTER: ({ filter }) => (state) => {
49 | return {
50 | ...state,
51 | visibilityFilter: filter,
52 | };
53 | },
54 | TOGGLE_TODO: ({ id }) => (state) => {
55 | const { todos } = state;
56 | const newTodos = todos.map(
57 | t => t.id === id
58 | ? {
59 | ...t,
60 | completed: !t.completed,
61 | }
62 | : t
63 | );
64 | return {
65 | ...state,
66 | todos: newTodos,
67 | };
68 | },
69 | });
--------------------------------------------------------------------------------
/src/examples/optional-next-js/1-router/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Link from '../../1-react-router/common/Link';
3 | import window from './NodeSafeWindow';
4 |
5 | export default function App() {
6 | const [counter, setCounter] = useState(0);
7 | const [pathname, setPathname] = useState(window.location.pathname);
8 | const updatePathname = (newLocation: string) => {
9 | setPathname(newLocation);
10 | window.history.pushState(null, '', newLocation);
11 | }
12 | window.addEventListener('popstate', () => {
13 | setPathname(window.location.pathname);
14 | })
15 | let innerComponent: JSX.Element;
16 | switch (pathname) {
17 | case '/':
18 | innerComponent = Home
;
72 | }
73 |
74 | function About() {
75 | return About
;
76 | }
77 |
78 | function Users() {
79 | return Users
;
80 | }
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/examples/1-react-router/2-bonus-hash-history-state/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Link from '../common/Link';
3 |
4 | export default function App() {
5 | const [counter, setCounter] = useState(0);
6 | const [pathname, setPathname] = useState(window.location.pathname);
7 | const updatePathname = (newurl: string) => {
8 | setPathname(newurl);
9 | window.history.pushState(counter, '', newurl);
10 | // window.history.replaceState(counter, '', newurl);
11 | }
12 | window.addEventListener('popstate', ev => {
13 | setCounter(ev.state as number)
14 | setPathname(window.location.pathname);
15 | });
16 | let innerComponent: JSX.Element;
17 | switch (pathname) {
18 | case '/':
19 | innerComponent = Home
;
73 | }
74 |
75 | function About() {
76 | return About
;
77 | }
78 |
79 | function Users() {
80 | return Users
;
81 | }
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/examples/1-react-router/3-union-types/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import PathnameLink from './PathnameLink';
3 |
4 | export type Pathname = '/' | '/about' | '/users'
5 |
6 | export default function App() {
7 | const [counter, setCounter] = useState(0);
8 | const [pathname, setPathname] = useStateHome
;
73 | }
74 |
75 | function About() {
76 | return About
;
77 | }
78 |
79 | function Users() {
80 | return Users
;
81 | }
82 |
--------------------------------------------------------------------------------
/src/examples/optional-next-js/2-flicker/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Link from '../../1-react-router/common/Link';
3 | import window from '../1-router/NodeSafeWindow';
4 |
5 | export default function App({
6 | initialUrl,
7 | }: {
8 | initialUrl: string;
9 | }) {
10 | const [counter, setCounter] = useState(0);
11 | const [pathname, setPathname] = useState(initialUrl);
12 | const updatePathname = (newLocation: string) => {
13 | setPathname(newLocation);
14 | window.history.pushState(null, '', newLocation);
15 | }
16 | window.addEventListener('popstate', () => {
17 | setPathname(window.location.pathname);
18 | })
19 | let innerComponent: JSX.Element;
20 | switch (pathname) {
21 | case '/':
22 | innerComponent = Home
;
76 | }
77 |
78 | function About() {
79 | return About
;
80 | }
81 |
82 | function Users() {
83 | return Users
;
84 | }
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/examples/3-reactive-architecture-w-redux/3-redux-observable/2-pushState/reducer.ts:
--------------------------------------------------------------------------------
1 | import { identity } from 'fp-ts/lib/function';
2 | import { pipe } from 'fp-ts/lib/pipeable';
3 | import * as O from 'fp-ts/lib/Option';
4 | import { Reducer, Action } from "redux";
5 | import { AppState, TodoType } from "../../1-redux/1-global-state/AppState";
6 | import { AppAction, TransformAction } from './AppAction';
7 | import { ADT } from "@morphic-ts/adt";
8 |
9 | export const safeReducer = (
10 | curriedReducer: (a: A) => (s: S) => S,
11 | defaultState: S,
12 | adt: ADT
13 | ): Reducer => (
14 | nullableState,
15 | action
16 | ) => {
17 | if (!nullableState || !adt.verified(action)) return defaultState;
18 | return curriedReducer(action)(nullableState);
19 | }
20 |
21 | export const curriedReducer = (
22 | a: AppAction,
23 | ) => pipe(
24 | a as TransformAction,
25 | O.fromPredicate(TransformAction.verified),
26 | O.map(TransformAction.matchStrict<(a: AppState) => AppState>({
27 | ADD_TODO: ({ text }) => (state) => {
28 | const { todos } = state;
29 | const highestID = todos.length > 0
30 | ? todos.reduce(
31 | (acc, cur) => cur.id > acc.id
32 | ? cur
33 | : acc
34 | ).id
35 | : 0;
36 | const newTodo: TodoType = {
37 | id: highestID + 1,
38 | text: text,
39 | completed: false,
40 | }
41 | return {
42 | ...state,
43 | todos: [...todos, newTodo]
44 | };
45 | },
46 | SET_VISIBILITY_FILTER: ({ filter }) => (state) => {
47 | return {
48 | ...state,
49 | visibilityFilter: filter,
50 | };
51 | },
52 | TOGGLE_TODO: ({ id }) => (state) => {
53 | const { todos } = state;
54 | const newTodos = todos.map(
55 | t => t.id === id
56 | ? {
57 | ...t,
58 | completed: !t.completed,
59 | }
60 | : t
61 | );
62 | return {
63 | ...state,
64 | todos: newTodos,
65 | };
66 | },
67 | })),
68 | O.getOrElse((): (s: AppState) => AppState => identity),
69 | );
70 |
--------------------------------------------------------------------------------
/src/examples/optional-next-js/4-data-fetch/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Link from '../../1-react-router/common/Link';
3 | import window from '../1-router/NodeSafeWindow';
4 |
5 | export default function App({
6 | initialUrl,
7 | fetched,
8 | }: {
9 | initialUrl: string;
10 | fetched: string;
11 | }) {
12 | const [counter, setCounter] = useState(0);
13 | const [pathname, setPathname] = useState(initialUrl);
14 | const updatePathname = (newLocation: string) => {
15 | setPathname(newLocation);
16 | window.history.pushState(null, '', newLocation);
17 | }
18 | window.addEventListener('popstate', () => {
19 | setPathname(window.location.pathname);
20 | })
21 | let innerComponent: JSX.Element;
22 | switch (pathname) {
23 | case '/':
24 | innerComponent = Home
;
79 | }
80 |
81 | function About() {
82 | return About
;
83 | }
84 |
85 | function Users() {
86 | return Users
;
87 | }
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/examples/optional-next-js/5-hydration-safe-fetch/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Link from '../../1-react-router/common/Link';
3 | import window from './NodeSafeWindow';
4 |
5 | export default function App({
6 | initialUrl,
7 | }: {
8 | initialUrl: string;
9 | }) {
10 | const fetched = window.__INITIAL__DATA__
11 | const [counter, setCounter] = useState(0);
12 | const [pathname, setPathname] = useState(initialUrl);
13 | const updatePathname = (newLocation: string) => {
14 | setPathname(newLocation);
15 | window.history.pushState(null, '', newLocation);
16 | }
17 | window.addEventListener('popstate', () => {
18 | setPathname(window.location.pathname);
19 | })
20 | let innerComponent: JSX.Element;
21 | switch (pathname) {
22 | case '/':
23 | innerComponent = Home
;
78 | }
79 |
80 | function About() {
81 | return About
;
82 | }
83 |
84 | function Users() {
85 | return Users
;
86 | }
87 |
88 |
89 |
--------------------------------------------------------------------------------
/src/examples/1-react-router/5-bonus-ints-queries-types/code.ts:
--------------------------------------------------------------------------------
1 | import * as t from 'io-ts'
2 | import { lit, str, query, Route, parse, format } from 'fp-ts-routing'
3 |
4 | const AorBCodec = t.union([
5 | t.literal('a'),
6 | t.literal('b')
7 | ])
8 | type AorB = t.TypeOf
39 |
64 | {ParseableLocation.matchStrictHome
;
83 | }
84 |
85 | function About() {
86 | return About
;
87 | }
88 |
89 | const Topics = ({
90 | location,
91 | updateLocation,
92 | }: {
93 | location: TopicLocation,
94 | updateLocation: (l: Location) => void,
95 | }) => (
96 | Topics
98 |
99 |
116 | {TopicLocation.match({
117 | Topics: () => Please select a topic.
,
118 | TopicsID: (l) => Requested topic ID: {topicId}
;
129 | }
130 |
--------------------------------------------------------------------------------
/src/examples/1-react-router/6-morphic-ts/App.tsx:
--------------------------------------------------------------------------------
1 | import { ADTType, makeADT, ofType } from '@morphic-ts/adt';
2 | import * as R from 'fp-ts-routing';
3 | import React, { useState } from 'react';
4 | import LocationLink from './LocationLink';
5 |
6 | const ParseableLocation = makeADT('type')({
7 | Home: ofType(),
8 | About: ofType(),
9 | Topics: ofType(),
10 | TopicsID: ofType<{ type: 'TopicsID'; id: string }>(),
11 | NotFound: ofType(),
12 | });
13 | type ParseableLocation = ADTType
57 |
82 | {ParseableLocation.matchStrictHome
;
101 | }
102 |
103 | function About() {
104 | return About
;
105 | }
106 |
107 | const Topics = ({
108 | location,
109 | updateLocation,
110 | }: {
111 | location: TopicLocation,
112 | updateLocation: (l: Location) => void,
113 | }) => (
114 | Topics
116 |
117 |
134 | {TopicLocation.match({
135 | Topics: () => Please select a topic.
,
136 | TopicsID: (l) => Requested topic ID: {topicId}
;
147 | }
148 |
149 |
--------------------------------------------------------------------------------
/src/examples/1-react-router/6-bonus-verified/App.tsx:
--------------------------------------------------------------------------------
1 | import { pipe } from 'fp-ts/lib/pipeable';
2 | import * as O from 'fp-ts/lib/Option';
3 | import { ADTType, makeADT, ofType } from '@morphic-ts/adt';
4 | import * as R from 'fp-ts-routing';
5 | import React, { useState } from 'react';
6 | import LocationLink from '../6-morphic-ts/LocationLink';
7 |
8 | const ParseableLocation = makeADT('type')({
9 | Home: ofType(),
10 | About: ofType(),
11 | Topics: ofType(),
12 | TopicsID: ofType<{ type: 'TopicsID'; id: string }>(),
13 | NotFound: ofType(),
14 | });
15 | type ParseableLocation = ADTType
64 |
89 | {pipe(
90 | location as HomeLocation,
91 | O.fromPredicate(HomeLocation.verified),
92 | O.map(HomeLocation.match({
93 | Home: () => Home
;
115 | }
116 |
117 | function About() {
118 | return About
;
119 | }
120 |
121 | const Topics = ({
122 | location,
123 | updateLocation,
124 | }: {
125 | location: TopicLocation,
126 | updateLocation: (l: Location) => void,
127 | }) => (
128 | Topics
130 |
131 |
148 | {TopicLocation.match({
149 | Topics: () => Please select a topic.
,
150 | TopicsID: (l) => Requested topic ID: {topicId}
;
161 | }
162 |
163 |
--------------------------------------------------------------------------------
/src/examples/1-react-router/5-fp-ts-routing/App.tsx:
--------------------------------------------------------------------------------
1 | import * as R from 'fp-ts-routing';
2 | import React, { useState } from 'react';
3 | import LocationLink from './LocationLink';
4 |
5 | interface Home {
6 | readonly type: 'Home'
7 | }
8 |
9 | interface About {
10 | readonly type: 'About'
11 | }
12 |
13 | interface Topics {
14 | readonly type: 'Topics'
15 | }
16 |
17 | interface TopicsID {
18 | readonly type: 'TopicsID'
19 | readonly id: string
20 | }
21 |
22 | interface NotFound {
23 | readonly type: 'NotFound'
24 | }
25 |
26 | export type Location = Home | About | Topics
27 | | TopicsID | NotFound
28 |
29 | const home = R.end
30 | const about = R.lit('about').then(R.end)
31 | const topics = R.lit('topics').then(R.end)
32 | const topicsID = R.lit('topics')
33 | .then(R.str('id'))
34 | .then(R.end)
35 |
36 | const router = /*zero
107 |
138 | {innerComponent}
139 | Home
;
145 | }
146 |
147 | function About() {
148 | return About
;
149 | }
150 |
151 | function Topics({
152 | location,
153 | updateLocation,
154 | }: {
155 | location: Topics | TopicsID,
156 | updateLocation: (l: Location) => void,
157 | }) {
158 | let innerComponent: JSX.Element;
159 | switch (location.type) {
160 | case 'Topics':
161 | innerComponent = Please select a topic.
;
162 | break;
163 | case 'TopicsID':
164 | innerComponent = Topics
170 |
171 |
188 | {innerComponent}
189 | Requested topic ID: {topicId}
;
199 | }
200 |
--------------------------------------------------------------------------------
/src/examples/1-react-router/4-tagged-unions/App.tsx:
--------------------------------------------------------------------------------
1 | import * as R from 'fp-ts-routing';
2 | import React, { useState } from 'react';
3 | import LocationLink from './LocationLink';
4 |
5 | interface Home {
6 | readonly type: 'Home'
7 | }
8 |
9 | interface About {
10 | readonly type: 'About'
11 | }
12 |
13 | interface Topics {
14 | readonly type: 'Topics'
15 | }
16 |
17 | interface TopicsID {
18 | readonly type: 'TopicsID'
19 | readonly id: string
20 | }
21 |
22 | interface NotFound {
23 | readonly type: 'NotFound'
24 | }
25 |
26 | export type Location = Home | About | Topics
27 | | TopicsID | NotFound
28 |
29 | const home = R.end;
30 | const about = R.lit('about').then(R.end);
31 | const topics = R.lit('topics').then(R.end);
32 | const topicsID = R.lit('topics')
33 | .then(R.str('id'))
34 | .then(R.end);
35 |
36 | const router: R.Parser
114 |
145 | {innerComponent}
146 | Home
;
152 | }
153 |
154 | function About() {
155 | return About
;
156 | }
157 |
158 | function Topics({
159 | location,
160 | updateLocation,
161 | }: {
162 | location: Topics | TopicsID,
163 | updateLocation: (l: Location) => void,
164 | }) {
165 | let innerComponent: JSX.Element;
166 | switch (location.type) {
167 | case 'Topics':
168 | innerComponent = Please select a topic.
;
169 | break;
170 | case 'TopicsID':
171 | innerComponent = Topics
177 |
178 |
195 | {innerComponent}
196 | Requested topic ID: {topicId}
;
206 | }
207 |
208 |
209 |
--------------------------------------------------------------------------------
/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
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 subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | process.env.PUBLIC_URL,
33 | window.location.pathname
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl, {
112 | headers: { 'Service-Worker': 'script' }
113 | })
114 | .then(response => {
115 | // Ensure service worker exists, and that we really are getting a JS file.
116 | const contentType = response.headers.get('content-type');
117 | if (
118 | response.status === 404 ||
119 | (contentType != null && contentType.indexOf('javascript') === -1)
120 | ) {
121 | // No service worker found. Probably a different app. Reload the page.
122 | navigator.serviceWorker.ready.then(registration => {
123 | registration.unregister().then(() => {
124 | window.location.reload();
125 | });
126 | });
127 | } else {
128 | // Service worker found. Proceed as normal.
129 | registerValidSW(swUrl, config);
130 | }
131 | })
132 | .catch(() => {
133 | console.log(
134 | 'No internet connection found. App is running in offline mode.'
135 | );
136 | });
137 | }
138 |
139 | export function unregister() {
140 | if ('serviceWorker' in navigator) {
141 | navigator.serviceWorker.ready
142 | .then(registration => {
143 | registration.unregister();
144 | })
145 | .catch(error => {
146 | console.error(error.message);
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------