├── .gitignore ├── README.md ├── jsconfig.json ├── media ├── 0-components.png ├── 1-counter-state.png ├── 2-counter-state.png ├── 3-counter-state-props.png ├── 4-counter-functions.png ├── 5-counter-function-props.png └── 6-counter-context.png ├── package-lock.json ├── package.json ├── public └── index.html └── src ├── App.css ├── App.js ├── Counter.js ├── index.js ├── pages ├── Info.js └── Login.js └── providers ├── AuthProvider.js └── CounterProvider.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .eslintcache 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Context Demo 2 | 3 | - Prop Drilling 4 | - React Hooks 5 | - Context API 6 | - Context Providers 7 | - Shared State 8 | - Interface vs Implementation Functions !! 9 | 10 | - Simple Auth / Login App 11 | - Demonstrates Context Providers and useContext 12 | - accepts any username / email address & password 13 | - creates a dummy userId for the newly logged in user 14 | - sets `auth` state if logged in and shows User Info if auth=true 15 | 16 | - uses `CounterProvider` component to wrap `` and ` in `Appjs` 17 | - uses `AuthProvider` component to wrap `` in `index.js` 18 | 19 | - Do not share "implementation" functions: `setCounter(), setUser()`. 20 | - Only share "interface" functions: `increment(), clear(), login()` 21 | - Can share state primitives, just don't share their `setState` functions 22 | - Its generally "OK" to share more complex state objects eg `user` 23 | - Sharing a function is better: `isloggedIn(), getUserName()` 24 | - Avoid deeply nested State. eg: No `state` object 25 | - Keep your state as flat as possible -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /media/0-components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-jipp/demo-react-context/d0feb818481708ee9c0d6ccb82ba5a46b01bab16/media/0-components.png -------------------------------------------------------------------------------- /media/1-counter-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-jipp/demo-react-context/d0feb818481708ee9c0d6ccb82ba5a46b01bab16/media/1-counter-state.png -------------------------------------------------------------------------------- /media/2-counter-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-jipp/demo-react-context/d0feb818481708ee9c0d6ccb82ba5a46b01bab16/media/2-counter-state.png -------------------------------------------------------------------------------- /media/3-counter-state-props.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-jipp/demo-react-context/d0feb818481708ee9c0d6ccb82ba5a46b01bab16/media/3-counter-state-props.png -------------------------------------------------------------------------------- /media/4-counter-functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-jipp/demo-react-context/d0feb818481708ee9c0d6ccb82ba5a46b01bab16/media/4-counter-functions.png -------------------------------------------------------------------------------- /media/5-counter-function-props.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-jipp/demo-react-context/d0feb818481708ee9c0d6ccb82ba5a46b01bab16/media/5-counter-function-props.png -------------------------------------------------------------------------------- /media/6-counter-context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gary-jipp/demo-react-context/d0feb818481708ee9c0d6ccb82ba5a46b01bab16/media/6-counter-context.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-context", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^17.0.1", 7 | "react-dom": "^17.0.1", 8 | "react-scripts": "4.0.1" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "build": "react-scripts build" 13 | }, 14 | "browserslist": { 15 | "production": [ 16 | ">0.2%", 17 | "not dead", 18 | "not op_mini all" 19 | ], 20 | "development": [ 21 | "last 1 chrome version", 22 | "last 1 firefox version", 23 | "last 1 safari version" 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Context Demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .UserInfo div { 2 | background-color: lightgrey; 3 | margin: 1px; 4 | /* border: solid 1px; */ 5 | padding: 0.5em; 6 | width: 14em; 7 | } 8 | 9 | input { 10 | width: 16em; 11 | } 12 | 13 | p { 14 | margin-bottom: 0.5em; 15 | margin-top: 0.5em; 16 | } 17 | 18 | span.counter { 19 | display: inline-block; 20 | width: 2em; 21 | } 22 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import Info from 'pages/Info'; 2 | import Login from 'pages/Login'; 3 | import {useAuth} from 'providers/AuthProvider'; 4 | import CounterProvider from 'providers/CounterProvider'; 5 | import 'App.css'; 6 | 7 | export default function App() { 8 | const {user} = useAuth(); 9 | 10 | return ( 11 |
12 | 13 |

My App

14 | {!user && } 15 | {!!user && } 16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/Counter.js: -------------------------------------------------------------------------------- 1 | import 'App.css'; 2 | import {useCounter} from 'providers/CounterProvider'; 3 | 4 | export default function Counter() { 5 | const {counter, increment, decrement, clear} = useCounter(); 6 | 7 | return ( 8 |
9 | Counter: {counter} 10 | 11 | 12 | 13 |
14 | ); 15 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from 'App'; 4 | import AuthProvider from 'providers/AuthProvider'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); -------------------------------------------------------------------------------- /src/pages/Info.js: -------------------------------------------------------------------------------- 1 | import {useAuth} from 'providers/AuthProvider'; 2 | import Counter from 'Counter'; 3 | 4 | export default function Info() { 5 | const {user, logout} = useAuth(); 6 | 7 | // Show user Info 8 | return ( 9 |
10 | 11 |
12 |
You are logged in
13 |
Email: {user.email}
14 |
Name: {user.name}
15 |
UserId: {user.id}
16 |
17 |
18 | 19 |
20 | 21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/pages/Login.js: -------------------------------------------------------------------------------- 1 | import {useState} from 'react'; 2 | import {useAuth} from 'providers/AuthProvider'; 3 | import Counter from 'Counter'; 4 | 5 | export default function Login() { 6 | const [email, setEmail] = useState(""); 7 | const [password, setPassword] = useState(""); 8 | const {login} = useAuth(); 9 | 10 | const onSubmit = function(event) { 11 | event.preventDefault(); 12 | email && login(email, password); 13 | }; 14 | 15 | return ( 16 |
17 | 18 |
19 |
20 | setEmail(event.target.value)} /> 23 |
24 |
25 | setPassword(event.target.value)} /> 28 |
29 |
30 | 31 |
32 |
33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/providers/AuthProvider.js: -------------------------------------------------------------------------------- 1 | import {createContext, useContext, useState} from 'react'; 2 | 3 | const context = createContext(); 4 | 5 | export const useAuth = function() { 6 | return useContext(context); 7 | }; 8 | 9 | export default function AuthProvider(props) { 10 | const [auth, setAuth] = useState(false); 11 | const [user, setUser] = useState(null); 12 | 13 | // Perform login for the user & save authID, etc 14 | const login = function(email, password) { 15 | setAuth(true); 16 | const id = "1234-1234-1234"; // Some random userId 17 | setUser({email, id, name: "Test User"}); 18 | }; 19 | 20 | const logout = function() { 21 | setAuth(false); 22 | setUser(null); 23 | }; 24 | 25 | // authContext will expose these items 26 | const value = {auth, user, login, logout}; 27 | 28 | // We can use this component to wrap any content we want to share this context 29 | return ( 30 | 31 | {props.children} 32 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/providers/CounterProvider.js: -------------------------------------------------------------------------------- 1 | import {createContext, useContext, useState} from 'react'; 2 | 3 | // Create a Context 4 | const context = createContext(); 5 | 6 | // Custom Hook to use Counter Context 7 | export const useCounter = function() { 8 | return useContext(context); 9 | }; 10 | 11 | // Create a Component wrapper from Context.Provider 12 | export default function CounterProvider(props) { 13 | 14 | // Here is our Shared State Object 15 | const [counter, setCounter] = useState(0); 16 | 17 | // Functions to change the counter state item 18 | const increment = function() { 19 | setCounter(counter + 1); 20 | }; 21 | const decrement = function() { 22 | setCounter(counter - 1); 23 | }; 24 | const clear = function() { 25 | setCounter(0); 26 | }; 27 | 28 | // This list can get long with a lot of functions. Reducer may be a better choice 29 | const value = {counter, increment, decrement, clear}; 30 | 31 | // We can now use this as a component to wrap anything 32 | // that needs our state 33 | return ( 34 | 35 | {props.children} 36 | 37 | ); 38 | }; --------------------------------------------------------------------------------