Add redux for global app state management

This commit is contained in:
Kacper Donat 2020-07-13 20:41:38 +02:00
parent 6d8985e441
commit 1a81c7ead5
9 changed files with 187 additions and 38 deletions

View File

@ -13,7 +13,9 @@
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.9",
"@types/react-router-dom": "^5.1.5",
"@types/redux": "^3.6.0",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"babel-core": "^6.26.3",
@ -38,7 +40,10 @@
"react-app-polyfill": "^1.0.6",
"react-dev-utils": "^10.2.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
"react-router-dom": "^5.2.0",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"sass-loader": "8.0.2",
"style-loader": "0.23.1",
"ts-loader": "^7.0.5",

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { Dispatch } from 'react';
import { MuiThemeProvider as ThemeProvider, StylesProvider } from "@material-ui/core/styles";
import { studentTheme } from "./ui/theme";
import { MuiPickersUtilsProvider } from "@material-ui/pickers";
@ -11,7 +11,11 @@ import '@/styles/header.scss'
import moment, { Moment } from "moment";
import { route, routes } from "@/routing";
import { Button, Divider } from '@material-ui/core';
import { Provider, useDispatch, useSelector } from "react-redux";
import store from "@/state/store";
import { AppState } from "@/state/reducer";
import { StudentAction, StudentActions } from "@/state/actions/student";
import { sampleStudent } from "@/provider/dummy/student";
moment.locale("pl")
@ -21,42 +25,72 @@ class LocalizedMomentUtils extends MomentUtils {
}
}
const UserMenu = () => {
const student = useSelector<AppState>(state => state.student);
const dispatch = useDispatch<Dispatch<StudentAction>>();
const handleUserLogin = () => {
dispatch({
type: StudentActions.Login,
student: sampleStudent,
})
}
const handleUserLogout = () => {
dispatch({ type: StudentActions.Logout })
}
return student ? <>
zalogowany jako: <strong>Jan Kowalski</strong>
{' '}
(<Link to={'#'} onClick={ handleUserLogout }>wyloguj się</Link>)
</> : <>
<Link to={'#'} onClick={ handleUserLogin }>zaloguj się</Link>
</>;
}
const AppHeader = () => {
return <header className="header">
<div id="logo" className="header__logo">
<Link to={ route('home') }>
<img src="img/pg-logotyp.svg"/>
</Link>
</div>
<div className="header__nav">
<nav className="header__top">
<ul className="header__menu"></ul>
<div className="header__user">
<UserMenu />
</div>
<div className="header__divider" />
<ul className="header__language-switcher">
<li>pl</li>
<li>en</li>
</ul>
</nav>
<nav className="header__bottom">
<ul className="header__menu header__menu--main"></ul>
</nav>
</div>
</header>
}
function App() {
return (
<StylesProvider injectFirst>
<MuiPickersUtilsProvider utils={ LocalizedMomentUtils } libInstance={ moment }>
<ThemeProvider theme={ studentTheme }>
<BrowserRouter>
<header className="header">
<div id="logo" className="header__logo">
<Link to={ route('home') }>
<img src="img/eti-logo.svg"/>
</Link>
</div>
<div className="header__nav">
<nav className="header__top">
<ul className="header__menu"></ul>
<div className="header__user">
zalogowany jako: <strong>Jan Kowalski</strong>
{' '}
(<Link to={'#'}>wyloguj się</Link>)
</div>
<div className="header__divider" />
<ul className="header__language-switcher">
<li>pl</li>
<li>en</li>
</ul>
</nav>
<nav className="header__bottom">
<ul className="header__menu header__menu--main"></ul>
</nav>
</div>
</header>
<Switch>{ routes.map(({ name, content, ...route }) => <Route { ...route } key={ name }>{ content() }</Route>) }</Switch>
</BrowserRouter>
</ThemeProvider>
</MuiPickersUtilsProvider>
</StylesProvider>
<Provider store={ store }>
<StylesProvider injectFirst>
<MuiPickersUtilsProvider utils={ LocalizedMomentUtils } libInstance={ moment }>
<ThemeProvider theme={ studentTheme }>
<BrowserRouter>
<AppHeader />
<Switch>{ routes.map(({ name, content, ...route }) => <Route { ...route } key={ name }>{ content() }</Route>) }</Switch>
</BrowserRouter>
</ThemeProvider>
</MuiPickersUtilsProvider>
</StylesProvider>
</Provider>
);
}

View File

@ -0,0 +1,3 @@
export interface Action<TType extends string> {
readonly type: TType;
}

View File

View File

@ -0,0 +1,16 @@
import { Action } from "@/state/actions/base";
import { Student } from "@/data";
export enum StudentActions {
Login = 'LOGIN',
Logout = 'LOGOUT'
}
export interface LoginAction extends Action<StudentActions.Login> {
student: Student
}
export type LogoutAction = Action<StudentActions.Logout>;
export type StudentAction = LoginAction | LogoutAction;

View File

@ -0,0 +1,10 @@
import { combineReducers } from "redux";
import studentReducer from "./student"
const rootReducer = combineReducers({
student: studentReducer,
})
export type AppState = ReturnType<typeof rootReducer>;
export default rootReducer;

View File

@ -0,0 +1,20 @@
import { Student } from "@/data";
import { StudentAction, StudentActions } from "@/state/actions/student";
export type StudentState = Student | null;
const initialStudentState: StudentState = null;
const studentReducer = (state: StudentState = initialStudentState, action: StudentAction): StudentState => {
switch (action.type) {
case StudentActions.Login:
return action.student;
case StudentActions.Logout:
return null;
}
return state;
}
export default studentReducer;

7
src/state/store.ts Normal file
View File

@ -0,0 +1,7 @@
import { createStore } from "redux";
import rootReducer from "@/state/reducer";
import { devToolsEnhancer } from "redux-devtools-extension";
const store = createStore(rootReducer, devToolsEnhancer({ }));
export default store;

View File

@ -1146,6 +1146,14 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.6.tgz#ed8fc802c45b8e8f54419c2d054e55c9ea344356"
integrity sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w==
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
dependencies:
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4":
version "7.0.5"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
@ -1188,6 +1196,16 @@
dependencies:
"@types/react" "*"
"@types/react-redux@^7.1.9":
version "7.1.9"
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.9.tgz#280c13565c9f13ceb727ec21e767abe0e9b4aec3"
integrity sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w==
dependencies:
"@types/hoist-non-react-statics" "^3.3.0"
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react-router-dom@^5.1.5":
version "5.1.5"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090"
@ -1220,6 +1238,13 @@
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/redux@^3.6.0":
version "3.6.0"
resolved "https://registry.yarnpkg.com/@types/redux/-/redux-3.6.0.tgz#f1ebe1e5411518072e4fdfca5c76e16e74c1399a"
integrity sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=
dependencies:
redux "*"
"@types/source-list-map@*":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
@ -4293,7 +4318,7 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -7298,11 +7323,22 @@ react-error-overlay@^6.0.7:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1:
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.9.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-redux@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==
dependencies:
"@babel/runtime" "^7.5.5"
hoist-non-react-statics "^3.3.0"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^16.9.0"
react-router-dom@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
@ -7421,6 +7457,19 @@ redent@^1.0.0:
indent-string "^2.1.0"
strip-indent "^1.0.1"
redux-devtools-extension@^2.13.8:
version "2.13.8"
resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.8.tgz#37b982688626e5e4993ff87220c9bbb7cd2d96e1"
integrity sha512-8qlpooP2QqPtZHQZRhx3x3OP5skEV1py/zUdMY28WNAocbafxdG2tRD1MWE7sp8obGMNYuLWanhhQ7EQvT1FBg==
redux@*, redux@^4.0.0, redux@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
dependencies:
loose-envify "^1.4.0"
symbol-observable "^1.2.0"
regenerate-unicode-properties@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
@ -8405,6 +8454,11 @@ svgo@^1.0.0:
unquote "~1.1.1"
util.promisify "~1.0.0"
symbol-observable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
tapable@^1.0.0, tapable@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"