22
loading...
This website collects cookies to deliver better user experience
npx create-react-app app_name --template typescript --use-npm
for anyone with both npm
and yarn
installed in the system or npx create-react-app app_name
for just npm
to setup our initial projectclient
for the startsrc
folder named state
for keeping all files related to our global state. For reducer
functions we'll create a folder in the state named reducers.createContext
from react to create a context instance for holding our global state and sharing the state value across all children bellow it.user
and theme
.combined reducer
function takes in a given state and passess it to the appropriate reducer
function. We destructure the state in the combinedReducer
arguments and return the state after any update.APP_STATE_NAME
variable to ensure consistency and ease of access to the localstorage varible.useReducer
hook from react
for dispatching events on our state.useEffect
hook to watch for changes in the state and refresh the state in case of any change./**
* AppProvider.tsx
*/
import React, { createContext, Dispatch, useEffect, useReducer } from "react";
import { IState, IThemeAction, StateActions, UserActions } from "./interfaces";
import themeReducer from "./reducers/themeReducer";
import userReducer from "./reducers/userReducer";
const APP_STATE_NAME = "testing";
//Check if state already exist and take the instance or set a default value
//in case there is no state in the localstorage
const initialState: IState = JSON.parse(localStorage.getItem(APP_STATE_NAME)!)
? JSON.parse(localStorage.getItem(APP_STATE_NAME)!)
: {
user: {
username: "",
active: false,
},
theme: {
dark: false,
},
};
const AppContext = createContext<{
state: IState;
dispatch: Dispatch<StateActions>;
}>({ state: initialState, dispatch: () => null });
const combinedReducers = (
{ user, theme }: IState,
action: UserActions | IThemeAction
) => ({
user: userReducer(user, action),
theme: themeReducer(theme, action),
});
const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(combinedReducers, initialState);
// Watches for any changes in the state and keeps the state update in sync
//Refresh state on any action dispatched
useEffect(() => {
//Update the localstorage after detected change
localStorage.setItem(APP_STATE_NAME, JSON.stringify(state));
}, [state]);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
export default AppProvider;
export { AppContext, AppProvider };
/**
* interfaces.tsx
*/
import { LOGIN, LOGOUT, THEME } from "./ActionTypes";
export interface IUser {
username: string;
active: boolean;
}
export interface ITheme {
dark: boolean;
}
export interface IState {
user: IUser;
theme: ITheme;
}
export interface IUserLogin {
type: typeof LOGIN;
payload: IUser;
}
export interface IUserLogout {
type: typeof LOGOUT;
payload: {};
}
export interface IThemeAction {
type: typeof THEME;
payload: { toggle: boolean };
}
export type UserActions = IUserLogin | IUserLogout;
export type StateActions = UserActions | IThemeAction;
/**
* ActionTypes.tsx
*/
const LOGIN = "LOGIN";
const LOGOUT = "LOGOUT";
const THEME = "THEME";
// const LOGIN = "LOGIN"
// const LOGIN = "LOGIN"
export default Object.freeze({ LOGIN, LOGOUT, THEME });
export { LOGIN, LOGOUT, THEME };
import { THEME } from "../ActionTypes";
import { ITheme, StateActions } from "../interfaces";
const themeReducer = (theme: ITheme, action: StateActions) => {
switch (action.type) {
case THEME:
return { ...theme, ...action.payload };
default:
return theme;
}
};
export default themeReducer;
import { LOGIN, LOGOUT } from "../ActionTypes";
import { IUser, StateActions } from "../interfaces";
const userReducer = (user: IUser, action: StateActions) => {
const { type, payload } = action;
switch (type) {
case LOGIN:
return { ...user, ...payload };
case LOGOUT:
return { ...user, username: "", active: false };
default:
return user;
}
};
export default userReducer;
AppProvider
/**
* index.tsx
*/
import ReactDOM from "react-dom";
import App from "./App";
import AppProvider from "./state/AppProvider";
ReactDOM.render(
<AppProvider>
<App />
</AppProvider>,
document.getElementById("root")
);
useContext
hook to get access to the state and pass our AppContext
instance to get current state of the application/**
* Header.tsx
*/
import { useContext } from "react";
import { AppContext } from "../state/AppProvider";
const Header = () => {
const { state } = useContext(AppContext);
return (
<header>
<div className="left">LOGO</div>
<div className="right">
<ul>
<li>
<a href="/">My pages</a>
</li>
<li>
<a href="/">{state.user.active ? state.user.username : "Login"}</a>
</li>
</ul>
</div>
</header>
);
};
export default Header;
Home.tsx
using the useContext
hook we can destructure the context value object to get access to the state and the dispatch method for invoking our reducers/**
* Home.tsx
*/
import { useContext } from "react";
import { LOGIN, LOGOUT } from "../state/ActionTypes";
import { AppContext } from "../state/AppProvider";
const Home = () => {
const { state, dispatch } = useContext(AppContext);
const { user } = state;
const hendleLogin = () => {
dispatch({
type: LOGIN,
payload: { active: true, username: "Mike" },
});
console.log(state);
};
const hendleLogout = () => {
dispatch({
type: LOGOUT,
payload: { username: "", active: false },
});
};
return (
<div className="home-container">
<p>{user.active ? user.username : "No user"}</p>
<div>
<button
className="login"
{...(user.active ? { disabled: true } : { disabled: false })}
onClick={hendleLogin}
>
Login
</button>
<button
className="logout"
{...(!user.active ? { disabled: true } : { disabled: false })}
onClick={hendleLogout}
>
Logout
</button>
</div>
</div>
);
};
export default Home;
/**
* App.tsx
*/
import "./App.css";
import Header from "./components/Header";
import Home from "./components/Home";
const App = () => {
return (
<div>
<Header />
<Home />
</div>
);
};
export default App;