Skip to content

use typeless 1.0.0 #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions .blueprints/feature/{{name}}/interface.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import { DefaultSuspense } from 'src/components/DefaultSuspense';
import { RouteConfig } from 'src/types';
import { createActions } from 'typeless';
import { createModule } from 'typeless';
import { {{pascalCase name}}Symbol } from './symbol';

// --- Constants ---
export const MODULE = '{{name}}';

// --- Actions ---
export const {{pascalCase name}}Actions = createActions(MODULE, {});
export const [handle, {{pascalCase name}}Actions, get{{pascalCase name}}State] = createModule({{pascalCase name}}Symbol)
.withActions({})
.withState<{{pascalCase name}}State >();

// --- Routing ---
const ModuleLoader = React.lazy(() => import('./module'));
Expand All @@ -29,9 +30,3 @@ export const routeConfig: RouteConfig = {
export interface {{pascalCase name}}State {
foo: string;
}

declare module 'typeless/types' {
export interface DefaultState {
{{name}}: {{pascalCase name}}State;
}
}
14 changes: 4 additions & 10 deletions .blueprints/feature/{{name}}/module.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import React from 'react';
import { createEpic, createReducer, useModule } from 'typeless';
import { {{pascalCase name}}View } from './components/{{pascalCase name}}View';
import { {{pascalCase name}}Actions, {{pascalCase name}}State, MODULE } from './interface';
import { {{pascalCase name}}Actions, {{pascalCase name}}State, handle } from './interface';

// --- Epic ---
export const epic = createEpic(MODULE);
handle.epic();

// --- Reducer ---
const initialState: {{pascalCase name}}State = {
foo: 'bar',
};

export const reducer = createReducer(initialState);
handle.reducer(initialState);

// --- Module ---
export default () => {
useModule({
epic,
reducer,
reducerPath: ['{{name}}'],
actions: {{pascalCase name}}Actions,
});
handle();
return <{{pascalCase name}}View />;
};
1 change: 1 addition & 0 deletions .blueprints/feature/{{name}}/symbol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const {{pascalCase name}}Symbol = Symbol('{{name}}');
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ yarn build
- Logout button.
- Loading the initial user instance on the page load. Routing is ignored if the initial data is not loaded. Similar to `onEnter` functionality from old `react-router`.
- Routing
- A very simple implementation with routing and redux. No need for `react-router`!
- Implemented with `typeless-router`
- Dynamic configuration. [`RouteResolver`](/src/components/RouteResolver.tsx) scans all modules and loads all routes automatically.
- Example `RouteConfig`. Some routes are only for the authenticated user, and some routes are only for the anonymous user. Feel free to extend this functionality depending on your needs.
- Lazy modules
- Features `login`, `sample1`, `sample2` are dynamically loaded with `React.lazy`.
- A loader is visible during lazy loading.
- Example redux form.
- [`form`](/src/form/createForm.ts) contains a custom library for Redux Form integration. This is WIP and probably will be extracted to a separate library.
- Example form.
- Implemented with `typeless-form`



Expand Down
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,18 @@
"@types/react-dom": "16.8.2",
"@types/styled-components": "4.0.3",
"@types/webpack-env": "^1.13.7",
"history": "^4.9.0",
"prettier": "^1.16.4",
"react": "16.8.4",
"react-app-rewired": "2.1.1",
"react-dom": "16.8.4",
"react-scripts": "3.0.1",
"redux": "4.0.1",
"redux-logger": "^3.0.6",
"remeda": "^0.0.11",
"rxjs": "6.2.2",
"styled-components": "^4.1.3",
"typeless": "0.0.18",
"typescript": "^3.3.3333"
"typeless": "^1.0.0",
"typeless-form": "^1.0.0",
"typeless-router": "^1.0.0",
"typescript": "^3.5.1"
},
"scripts": {
"prettier": "prettier --write 'src/**/*.{ts,tsx}'",
Expand Down
1 change: 0 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
Expand Down
10 changes: 4 additions & 6 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React from 'react';
import * as R from 'remeda';
import { useGlobalModule } from 'src/features/global/module';
import { useRouterModule } from 'src/features/router/module';
import { createGlobalStyle } from 'styled-components';
import { useMappedState } from 'typeless';
import { RouteResolver } from './RouteResolver';
import { useRouterModule } from 'src/features/router';
import { getGlobalState } from 'src/features/global/interface';

const GlobalStyle = createGlobalStyle`
*, ::after, ::before {
Expand All @@ -29,9 +28,8 @@ const GlobalStyle = createGlobalStyle`
export const App = () => {
useRouterModule();
useGlobalModule();
const { isLoaded } = useMappedState(state =>
R.pick(state.global, ['isLoaded'])
);
const { isLoaded } = getGlobalState.useState();

return (
<>
{isLoaded && <RouteResolver />}
Expand Down
12 changes: 6 additions & 6 deletions src/components/FormInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import styled, { css } from 'styled-components';

export interface FormInputProps
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
className?: string;
label: string;
Expand All @@ -13,7 +13,7 @@ const Label = styled.label`
margin-bottom: 8px;
`;

const Input = styled.input`
const StyledInput = styled.input`
display: block;
width: 100%;
padding: 10px;
Expand Down Expand Up @@ -43,24 +43,24 @@ const Error = styled.div`
color: #dc3545;
`;

const _FormInput = (props: FormInputProps) => {
const _Input = (props: InputProps) => {
const { className, label, error, ...rest } = props;
return (
<div className={className}>
<Label>{label}</Label>
<Input {...rest} />
<StyledInput {...rest} />
{error && <Error>{error}</Error>}
</div>
);
};

export const FormInput = styled(_FormInput)`
export const Input = styled(_Input)`
width: 100%;
display: block;
${props =>
props.error &&
css`
${Input} {
${StyledInput} {
border-color: #dc3545;
&:focus {
box-shadow: 0 0 0 3px rgba(220, 53, 69, 0.25);
Expand Down
28 changes: 0 additions & 28 deletions src/components/Link.tsx

This file was deleted.

10 changes: 5 additions & 5 deletions src/components/ReduxInput.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useContext } from 'react';
import { FormContext } from 'src/form';
import { FormInput, FormInputProps } from './FormInput';
import { FormContext } from 'typeless-form';
import { Input, InputProps } from './FormInput';

interface ReduxFormControlProps extends FormInputProps {
interface ReduxFormControlProps extends InputProps {
name: string;
}

export const ReduxInput = (props: ReduxFormControlProps) => {
export const FormInput = (props: ReduxFormControlProps) => {
const { name, ...rest } = props;
const data = useContext(FormContext);
if (!data) {
Expand All @@ -15,7 +15,7 @@ export const ReduxInput = (props: ReduxFormControlProps) => {
const hasError = data.touched[name] && !!data.errors[name];
const value = data.values[name];
return (
<FormInput
<Input
value={value == null ? '' : value}
error={hasError ? data.errors[name] : null}
onBlur={() => data.actions.blur(name)}
Expand Down
14 changes: 9 additions & 5 deletions src/components/RouteResolver.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import * as R from 'remeda';
import { RouterActions, RouterLocation } from 'src/features/router/interface';
import { usePrevious } from 'src/hooks/usePrevious';
import { RouteConfig } from 'src/types';
import { useActions, useMappedState } from 'typeless';
import { getRouterState, RouterActions, RouterLocation } from 'typeless-router';
import { getGlobalState } from 'src/features/global/interface';

// load dynamically all routes from all interfaces
const req = require.context('../features', true, /interface.tsx?$/);
Expand All @@ -27,10 +28,13 @@ function getMatch(loc: RouterLocation | null, isLogged: boolean) {
}

export const RouteResolver = () => {
const { user, location } = useMappedState(state => ({
...R.pick(state.global, ['isLoaded', 'user']),
...R.pick(state.router, ['location']),
}));
const { user, location } = useMappedState(
[getGlobalState, getRouterState],
(global, router) => ({
...R.pick(global, ['isLoaded', 'user']),
...R.pick(router, ['location']),
})
);
const { push } = useActions(RouterActions);
const [component, setComponent] = useState(<div />);

Expand Down
26 changes: 11 additions & 15 deletions src/features/global/interface.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import { User } from 'src/types';
import { createActions } from 'typeless';
import { createModule } from 'typeless';
import { GlobalSymbol } from './symbol';

// --- Constants ---
export const MODULE = 'global';

// --- Actions ---
export const GlobalActions = createActions(MODULE, {
$mounted: null,
logout: null,
loggedIn: (user: User | null) => ({ payload: { user } }),
});
export const [handle, GlobalActions, getGlobalState] = createModule(
GlobalSymbol
)
.withActions({
$mounted: null,
logout: null,
loggedIn: (user: User | null) => ({ payload: { user } }),
})
.withState<GlobalState>();

// --- Types ---
export interface GlobalState {
isLoaded: boolean;
user: User | null;
}

declare module 'typeless/types' {
export interface DefaultState {
global: GlobalState;
}
}
23 changes: 10 additions & 13 deletions src/features/global/module.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import * as Rx from 'src/rx';
import { getUser } from 'src/services/API';
import { clearAccessToken, getAccessToken } from 'src/services/Storage';
import { createEpic, createReducer, useModule } from 'typeless';
import { RouterActions } from '../router/interface';
import { GlobalActions, GlobalState, MODULE } from './interface';
import { GlobalActions, GlobalState, handle } from './interface';
import { RouterActions } from 'typeless-router';

// --- Epic ---
export const epic = createEpic(MODULE)
handle
.epic()
.on(GlobalActions.$mounted, () => {
if (getAccessToken()) {
return getUser().pipe(Rx.map(GlobalActions.loggedIn));
}
return GlobalActions.loggedIn(null);
// TODO bug in typeless 1.0.0
// doesn't invoke re-render
return Rx.of(GlobalActions.loggedIn(null)).pipe(Rx.delay(0));
})
.on(GlobalActions.logout, () => {
clearAccessToken();
Expand All @@ -24,7 +26,8 @@ const initialState: GlobalState = {
user: null,
};

export const reducer = createReducer(initialState)
export const reducer = handle
.reducer(initialState)
.on(GlobalActions.loggedIn, (state, { user }) => {
state.isLoaded = true;
state.user = user;
Expand All @@ -34,10 +37,4 @@ export const reducer = createReducer(initialState)
});

// --- Module ---
export const useGlobalModule = () =>
useModule({
epic,
reducer,
reducerPath: ['global'],
actions: GlobalActions,
});
export const useGlobalModule = handle;
1 change: 1 addition & 0 deletions src/features/global/symbol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const GlobalSymbol = Symbol('global');
Loading