This repository has been archived by the owner on Jun 26, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor for using Better Context Api pattern
- no need to declare initial state for using context upfront - SRP satisfied context(providers) using multiple providers are preferred - facebook/react#15156 (comment)
- Loading branch information
Showing
8 changed files
with
148 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,3 +62,5 @@ coverage/ | |
package-lock.json | ||
|
||
ios/Pods/ | ||
|
||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
//eslint extension options | ||
"eslint.enable": true, | ||
"eslint.validate": [ | ||
"javascript", | ||
"javascriptreact", | ||
"typescript", | ||
"typescriptreact" | ||
], | ||
|
||
// prettier extension setting | ||
"editor.formatOnSave": true, | ||
"[javascript]": { | ||
"editor.defaultFormatter": "esbenp.prettier-vscode" | ||
}, | ||
"[javascriptreact]": { | ||
"editor.defaultFormatter": "esbenp.prettier-vscode" | ||
}, | ||
"[typescript]": { | ||
"editor.defaultFormatter": "esbenp.prettier-vscode" | ||
}, | ||
"[typescriptreact]": { | ||
"editor.defaultFormatter": "esbenp.prettier-vscode" | ||
}, | ||
"prettier.singleQuote": true, | ||
"prettier.trailingComma": "all", | ||
"prettier.arrowParens": "always", | ||
"prettier.jsxSingleQuote": true, | ||
// relative path is preferred | ||
"javascript.preferences.importModuleSpecifier": "relative", | ||
"typescript.preferences.importModuleSpecifier": "relative" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1 @@ | ||
import * as React from 'react'; | ||
|
||
export const AppContext = React.createContext<any>(null); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +1,73 @@ | ||
import React, { useReducer } from 'react'; | ||
|
||
import { AppContext } from '../contexts'; | ||
import { ThemeType } from '../theme'; | ||
import { User } from '../types'; | ||
import createCtx from 'utils/createCtx'; | ||
|
||
const AppConsumer = AppContext.Consumer; | ||
|
||
interface Action { | ||
type: 'reset-user' | 'set-user' | 'change-theme-mode'; | ||
payload: { | ||
theme: ThemeType; | ||
user: { | ||
displayName: string; | ||
age: number; | ||
job: string; | ||
}; | ||
}; | ||
interface Context { | ||
state: State; | ||
setUser: (user: User) => void; | ||
resetUser: () => void; | ||
} | ||
const [useCtx, Provider] = createCtx<Context>(); | ||
|
||
interface Props { | ||
children?: React.ReactElement; | ||
} | ||
type dispatchType = 'reset-user' | 'set-user'; | ||
|
||
export interface State { | ||
user: User; | ||
theme: ThemeType; | ||
} | ||
|
||
const initialState: State = { | ||
theme: ThemeType.LIGHT, | ||
user: { | ||
displayName: '', | ||
age: 0, | ||
job: '', | ||
}, | ||
}; | ||
|
||
const reducer = (state: State, action: Action): State => { | ||
// prettier-ignore | ||
interface Action { | ||
type: dispatchType; | ||
payload: State; | ||
} | ||
|
||
interface Props { | ||
children?: React.ReactElement; | ||
} | ||
|
||
type Reducer = (state: State, action: Action) => State; | ||
|
||
const setUser = (dispatch: React.Dispatch<Action>) => (user: User) => { | ||
dispatch({ | ||
type: 'set-user', | ||
payload: { user }, | ||
}); | ||
}; | ||
|
||
const resetUser = (dispatch: React.Dispatch<Action>) => () => { | ||
dispatch({ | ||
type: 'reset-user', | ||
payload: initialState, | ||
}); | ||
}; | ||
|
||
const reducer: Reducer = (state = initialState, action) => { | ||
switch (action.type) { | ||
case 'change-theme-mode': | ||
return { ...state, theme: action.payload.theme }; | ||
case 'reset-user': | ||
return { ...state, user: initialState.user }; | ||
case 'set-user': | ||
return { ...state, user: action.payload.user }; | ||
case 'reset-user': | ||
case 'set-user': | ||
return { ...state, user: action.payload.user }; | ||
default: | ||
return state; | ||
} | ||
}; | ||
|
||
function AppProvider(props: Props): React.ReactElement { | ||
const [state, dispatch] = useReducer(reducer, initialState); | ||
const value = { state, dispatch }; | ||
const [state, dispatch] = useReducer<Reducer>(reducer, initialState); | ||
|
||
const actions = { | ||
setUser: setUser(dispatch), | ||
resetUser: resetUser(dispatch), | ||
}; | ||
|
||
return ( | ||
<AppContext.Provider value={value}>{props.children}</AppContext.Provider> | ||
); | ||
return <Provider value={{ state, ...actions }}>{props.children}</Provider>; | ||
} | ||
|
||
export { AppConsumer, AppProvider, AppContext }; | ||
export { useCtx as useAppContext, AppProvider }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React, { useState } from 'react'; | ||
import { Theme, ThemeType, createTheme } from 'theme'; | ||
|
||
import { ThemeProvider as OriginalThemeProvider } from 'styled-components'; | ||
import createCtx from 'utils/createCtx'; | ||
|
||
interface Context { | ||
theme: Theme; | ||
themeType: ThemeType; | ||
changeTheme: React.Dispatch<React.SetStateAction<ThemeType>>; | ||
} | ||
const [useCtx, Provider] = createCtx<Context>(); | ||
|
||
const initialThemeType: ThemeType = ThemeType.LIGHT; | ||
|
||
interface Props { | ||
children?: React.ReactElement; | ||
} | ||
|
||
function ThemeProvider(props: Props): React.ReactElement { | ||
const [themeType, changeTheme] = useState(initialThemeType); | ||
const theme = createTheme(themeType); | ||
return ( | ||
<Provider | ||
value={{ | ||
changeTheme, | ||
themeType, | ||
theme, | ||
}} | ||
> | ||
<OriginalThemeProvider theme={theme}> | ||
{props.children} | ||
</OriginalThemeProvider> | ||
</Provider> | ||
); | ||
} | ||
|
||
export { useCtx as useThemeProvicer, ThemeProvider }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react'; | ||
|
||
// create context with no upfront defaultValue | ||
// without having to do undefined check all the time | ||
// prettier-ignore | ||
function createCtx<A>(): readonly [ | ||
() => A, | ||
React.ProviderExoticComponent<React.ProviderProps<A | undefined>>, | ||
] { | ||
const ctx = React.createContext<A | undefined>(undefined); | ||
function useCtx(): A { | ||
const c = React.useContext(ctx); | ||
if (!c) throw new Error('useCtx must be inside a Provider with a value'); | ||
return c; | ||
} | ||
return [useCtx, ctx.Provider] as const; // make TypeScript infer a tuple, not an array of union types | ||
} | ||
export default createCtx; |