Repository: alex83130/advanced-react-patterns Branch: main Commit: 72fa7786f2e5 Files: 57 Total size: 29.8 KB Directory structure: gitextract_uzcte344/ ├── .gitignore ├── README.md ├── package.json ├── public/ │ ├── index.html │ ├── manifest.json │ └── robots.txt └── src/ ├── App.js ├── App.test.js ├── index.js └── patterns/ ├── compound-component/ │ ├── Counter.js │ ├── Usage.js │ ├── components/ │ │ ├── Count.js │ │ ├── Decrement.js │ │ ├── Increment.js │ │ ├── Label.js │ │ ├── index.js │ │ └── styles.js │ └── useCounterContext.js ├── control-props/ │ ├── Counter.js │ ├── Usage.js │ ├── components/ │ │ ├── Count.js │ │ ├── Decrement.js │ │ ├── Increment.js │ │ ├── Label.js │ │ ├── index.js │ │ └── styles.js │ └── useCounterContext.js ├── custom-hooks/ │ ├── Counter.js │ ├── Usage.js │ ├── components/ │ │ ├── Count.js │ │ ├── Decrement.js │ │ ├── Increment.js │ │ ├── Label.js │ │ ├── index.js │ │ └── styles.js │ ├── useCounter.js │ └── useCounterContext.js ├── props-getters/ │ ├── Counter.js │ ├── Usage.js │ ├── components/ │ │ ├── Count.js │ │ ├── Decrement.js │ │ ├── Increment.js │ │ ├── Label.js │ │ ├── index.js │ │ └── styles.js │ ├── useCounter.js │ └── useCounterContext.js └── state-reducer/ ├── Counter.js ├── Usage.js ├── components/ │ ├── Count.js │ ├── Decrement.js │ ├── Increment.js │ ├── Label.js │ ├── index.js │ └── styles.js ├── useCounter.js └── useCounterContext.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: README.md ================================================ # Getting Started with Advanced React Patterns Repository used within the article : https://javascript.plainenglish.io/5-advanced-react-patterns-a6b7624267a6 ## Available Scripts In the project directory, you can run: ### `npm start` Runs the app in the development mode.\ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits.\ You will also see any lint errors in the console. ### `npm run build` Builds the app for production to the `build` folder.\ It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes.\ Your app is ready to be deployed! See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. ================================================ FILE: package.json ================================================ { "name": "advanced-react-patterns", "version": "0.1.0", "private": true, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.35", "@fortawesome/free-solid-svg-icons": "^5.15.3", "@fortawesome/react-fontawesome": "^0.1.14", "react": "^17.0.1", "react-dom": "^17.0.1", "react-scripts": "4.0.3", "styled-components": "^5.2.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: public/index.html ================================================ React App
================================================ FILE: public/manifest.json ================================================ { "short_name": "React App", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: src/App.js ================================================ import React from "react"; import styled from "styled-components"; import { Usage as CompoundComponent } from "./patterns/compound-component/Usage"; import { Usage as ControlProps } from "./patterns/control-props/Usage"; import { Usage as CustomHooks } from "./patterns/custom-hooks/Usage"; import { Usage as PropsGetters } from "./patterns/props-getters/Usage"; import { Usage as StateReducer } from "./patterns/state-reducer/Usage"; import { library } from "@fortawesome/fontawesome-svg-core"; import { faPlus, faPlusCircle, faPlusSquare, faMinus, faMinusCircle, faMinusSquare } from "@fortawesome/free-solid-svg-icons"; library.add( faPlus, faPlusCircle, faPlusSquare, faMinus, faMinusCircle, faMinusSquare ); export default function App() { return (

Advanced React Pattern

Compound component pattern

Control props pattern

Custom hooks pattern

Props PropsGetters pattern

State reducer Pattern

); } const StyledContainer = styled.div` text-align: center; font-family: sans-serif; `; const StyledTitleContainer = styled.div` background-color: #1428a0; color: white; padding: 35px; > h1 { margin: 0; } `; const StyledPatternContainer = styled.div` padding: 30px; border-bottom: 2px solid #d3d3d3; &:last-child { border: none; } > h2 { margin-top: 0; } `; ================================================ FILE: src/App.test.js ================================================ import { render, screen } from '@testing-library/react'; import App from './App'; test('renders learn react link', () => { render(); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); }); ================================================ FILE: src/index.js ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render( , document.getElementById('root') ); ================================================ FILE: src/patterns/compound-component/Counter.js ================================================ import React, { useEffect, useRef, useState } from "react"; import styled from "styled-components"; import { CounterProvider } from "./useCounterContext"; import { Count, Label, Decrement, Increment } from "./components"; function Counter({ children, onChange, initialValue = 0 }) { const [count, setCount] = useState(initialValue); const firstMounded = useRef(true); useEffect(() => { if (!firstMounded.current) { onChange && onChange(count); } firstMounded.current = false; }, [count, onChange]); const handleIncrement = () => { setCount(count + 1); }; const handleDecrement = () => { setCount(Math.max(0, count - 1)); }; return ( {children} ); } const StyledCounter = styled.div` display: inline-flex; border: 1px solid #17a2b8; line-height: 1.5; border-radius: 0.25rem; overflow: hidden; `; Counter.Count = Count; Counter.Label = Label; Counter.Increment = Increment; Counter.Decrement = Decrement; export { Counter }; ================================================ FILE: src/patterns/compound-component/Usage.js ================================================ import React from "react"; import { Counter } from "./Counter"; function Usage() { const handleChangeCounter = (count) => { console.log("count", count); }; return ( Counter ); } export { Usage }; ================================================ FILE: src/patterns/compound-component/components/Count.js ================================================ import React from "react"; import styled from "styled-components"; import { useCounterContext } from "../useCounterContext"; function Count({ max }) { const { count } = useCounterContext(); const hasError = max ? count >= max : false; return {count}; } const StyledCount = styled.div` background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; color: white; padding: 5px 7px; `; export { Count }; ================================================ FILE: src/patterns/compound-component/components/Decrement.js ================================================ import React from "react"; import { StyledButton } from "./styles.js"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useCounterContext } from "../useCounterContext"; function Decrement({ icon = "minus" }) { const { handleDecrement } = useCounterContext(); return ( ); } export { Decrement }; ================================================ FILE: src/patterns/compound-component/components/Increment.js ================================================ import React from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { StyledButton } from "./styles.js"; import { useCounterContext } from "../useCounterContext"; function Increment({ icon = "plus" }) { const { handleIncrement } = useCounterContext(); return ( ); } export { Increment }; ================================================ FILE: src/patterns/compound-component/components/Label.js ================================================ import React from "react"; import styled from "styled-components"; function Label({ children }) { return {children}; } const StyledLabel = styled.div` background-color: #e9ecef; color: #495057; padding: 5px 7px; `; export { Label }; ================================================ FILE: src/patterns/compound-component/components/index.js ================================================ export * from "./Count"; export * from "./Decrement"; export * from "./Increment"; export * from "./Label"; ================================================ FILE: src/patterns/compound-component/components/styles.js ================================================ import styled from "styled-components"; const StyledButton = styled.button` background-color: white; border: none; &:hover { cursor: pointer; } &:active, &:focus { outline: none; } `; export { StyledButton }; ================================================ FILE: src/patterns/compound-component/useCounterContext.js ================================================ import React from "react"; const CounterContext = React.createContext(undefined); function CounterProvider({ children, value }) { return ( {children} ); } function useCounterContext() { const context = React.useContext(CounterContext); if (context === undefined) { throw new Error("useCounterContext must be used within a CounterProvider"); } return context; } export { CounterProvider, useCounterContext }; ================================================ FILE: src/patterns/control-props/Counter.js ================================================ import React, { useState, useRef, useEffect } from "react"; import styled from "styled-components"; import { CounterProvider } from "./useCounterContext"; import { Count, Label, Decrement, Increment } from "./components"; function Counter({ children, value = null, initialValue = 0, onChange }) { const [count, setCount] = useState(initialValue); const isControlled = value !== null && !!onChange; const getCount = () => (isControlled ? value : count); const firstMounded = useRef(true); useEffect(() => { if (!firstMounded.current && !isControlled) { onChange && onChange(count); } firstMounded.current = false; }, [count, onChange, isControlled]); const handleIncrement = () => { handleCountChange(getCount() + 1); }; const handleDecrement = () => { handleCountChange(Math.max(0, getCount() - 1)); }; const handleCountChange = (newValue) => { isControlled ? onChange(newValue) : setCount(newValue); }; return ( {children} ); } const StyledCounter = styled.div` display: inline-flex; border: 1px solid #17a2b8; line-height: 1.5; border-radius: 0.25rem; overflow: hidden; `; Counter.Count = Count; Counter.Label = Label; Counter.Increment = Increment; Counter.Decrement = Decrement; export { Counter }; ================================================ FILE: src/patterns/control-props/Usage.js ================================================ import React, { useState } from "react"; import { Counter } from "./Counter"; function Usage() { const [count, setCount] = useState(0); const handleChangeCounter = (newCount) => { setCount(newCount); }; return ( Counter ); } export { Usage }; ================================================ FILE: src/patterns/control-props/components/Count.js ================================================ import React from "react"; import styled from "styled-components"; import { useCounterContext } from "../useCounterContext"; function Count({ max }) { const { count } = useCounterContext(); const hasError = max ? count >= max : false; return {count}; } const StyledCount = styled.div` background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; color: white; padding: 5px 7px; `; export { Count }; ================================================ FILE: src/patterns/control-props/components/Decrement.js ================================================ import React from "react"; import { StyledButton } from "./styles.js"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useCounterContext } from "../useCounterContext"; function Decrement({ icon = "minus" }) { const { handleDecrement } = useCounterContext(); return ( ); } export { Decrement }; ================================================ FILE: src/patterns/control-props/components/Increment.js ================================================ import React from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { StyledButton } from "./styles.js"; import { useCounterContext } from "../useCounterContext"; function Increment({ icon = "plus" }) { const { handleIncrement } = useCounterContext(); return ( ); } export { Increment }; ================================================ FILE: src/patterns/control-props/components/Label.js ================================================ import React from "react"; import styled from "styled-components"; function Label({ children }) { return {children}; } const StyledLabel = styled.div` background-color: #e9ecef; color: #495057; padding: 5px 7px; `; export { Label }; ================================================ FILE: src/patterns/control-props/components/index.js ================================================ export * from "./Count"; export * from "./Decrement"; export * from "./Increment"; export * from "./Label"; ================================================ FILE: src/patterns/control-props/components/styles.js ================================================ import styled from "styled-components"; const StyledButton = styled.button` background-color: white; border: none; &:hover { cursor: pointer; } &:active, &:focus { outline: none; } `; export { StyledButton }; ================================================ FILE: src/patterns/control-props/useCounterContext.js ================================================ import React from "react"; const CounterContext = React.createContext(undefined); function CounterProvider({ children, value }) { return ( {children} ); } function useCounterContext() { const context = React.useContext(CounterContext); if (context === undefined) { throw new Error("useCounterContext must be used within a CounterProvider"); } return context; } export { CounterProvider, useCounterContext }; ================================================ FILE: src/patterns/custom-hooks/Counter.js ================================================ import React, { useRef, useEffect } from "react"; import styled from "styled-components"; import { CounterProvider } from "./useCounterContext"; import { Count, Label, Decrement, Increment } from "./components"; function Counter({ children, value: count, onChange }) { const firstMounded = useRef(true); useEffect(() => { if (!firstMounded.current) { onChange && onChange(count); } firstMounded.current = false; }, [count, onChange]); return ( {children} ); } const StyledCounter = styled.div` display: inline-flex; border: 1px solid #17a2b8; line-height: 1.5; border-radius: 0.25rem; overflow: hidden; `; Counter.Count = Count; Counter.Label = Label; Counter.Increment = Increment; Counter.Decrement = Decrement; export { Counter }; ================================================ FILE: src/patterns/custom-hooks/Usage.js ================================================ import React from "react"; import styled from "styled-components"; import { Counter } from "./Counter"; import { useCounter } from "./useCounter"; function Usage() { const { count, handleIncrement, handleDecrement } = useCounter(0); const MAX_COUNT = 10; const handleClickIncrement = () => { //Put your custom logic if (count < MAX_COUNT) { handleIncrement(); } }; return ( <> Counter ); } export { Usage }; const StyledContainer = styled.div` margin-top: 20px; `; ================================================ FILE: src/patterns/custom-hooks/components/Count.js ================================================ import React from "react"; import styled from "styled-components"; import { useCounterContext } from "../useCounterContext"; function Count({ max }) { const { count } = useCounterContext(); const hasError = max ? count > max : false; return {count}; } const StyledCount = styled.div` background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; color: white; padding: 5px 7px; `; export { Count }; ================================================ FILE: src/patterns/custom-hooks/components/Decrement.js ================================================ import React from "react"; import { StyledButton } from "./styles.js"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; function Decrement({ icon = "minus", onClick }) { return ( ); } export { Decrement }; ================================================ FILE: src/patterns/custom-hooks/components/Increment.js ================================================ import React from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { StyledButton } from "./styles.js"; function Increment({ icon = "plus", onClick }) { return ( ); } export { Increment }; ================================================ FILE: src/patterns/custom-hooks/components/Label.js ================================================ import React from "react"; import styled from "styled-components"; function Label({ children }) { return {children}; } const StyledLabel = styled.div` background-color: #e9ecef; color: #495057; padding: 5px 7px; `; export { Label }; ================================================ FILE: src/patterns/custom-hooks/components/index.js ================================================ export * from "./Count"; export * from "./Decrement"; export * from "./Increment"; export * from "./Label"; ================================================ FILE: src/patterns/custom-hooks/components/styles.js ================================================ import styled from "styled-components"; const StyledButton = styled.button` background-color: white; border: none; &:hover { cursor: pointer; } &:active, &:focus { outline: none; } `; export { StyledButton }; ================================================ FILE: src/patterns/custom-hooks/useCounter.js ================================================ import { useState } from "react"; function useCounter(intialeCount) { const [count, setCount] = useState(intialeCount); const handleIncrement = () => { setCount((prevCount) => prevCount + 1); }; const handleDecrement = () => { setCount((prevCount) => Math.max(0, prevCount - 1)); }; return { count, handleIncrement, handleDecrement }; } export { useCounter }; ================================================ FILE: src/patterns/custom-hooks/useCounterContext.js ================================================ import React from "react"; const CounterContext = React.createContext(undefined); function CounterProvider({ children, value }) { return ( {children} ); } function useCounterContext() { const context = React.useContext(CounterContext); if (context === undefined) { throw new Error("useCounterContext must be used within a CounterProvider"); } return context; } export { CounterProvider, useCounterContext }; ================================================ FILE: src/patterns/props-getters/Counter.js ================================================ import React, { useRef, useEffect } from "react"; import styled from "styled-components"; import { CounterProvider } from "./useCounterContext"; import { Count, Label, Decrement, Increment } from "./components"; function Counter({ children, value: count, onChange }) { const firstMounded = useRef(true); useEffect(() => { if (!firstMounded.current) { onChange && onChange(count); } firstMounded.current = false; }, [count, onChange]); return ( {children} ); } const StyledCounter = styled.div` display: inline-flex; border: 1px solid #17a2b8; line-height: 1.5; border-radius: 0.25rem; overflow: hidden; `; Counter.Count = Count; Counter.Label = Label; Counter.Increment = Increment; Counter.Decrement = Decrement; export { Counter }; ================================================ FILE: src/patterns/props-getters/Usage.js ================================================ import React from "react"; import styled from "styled-components"; import { Counter } from "./Counter"; import { useCounter } from "./useCounter"; const MAX_COUNT = 10; function Usage() { const { count, getCounterProps, getIncrementProps, getDecrementProps } = useCounter({ initial: 0, max: MAX_COUNT }); const handleBtn1Clicked = () => { console.log("btn 1 clicked"); }; return ( <> Counter ); } export { Usage }; const StyledContainer = styled.div` margin-top: 20px; `; ================================================ FILE: src/patterns/props-getters/components/Count.js ================================================ import React from "react"; import styled from "styled-components"; import { useCounterContext } from "../useCounterContext"; function Count({ max }) { const { count } = useCounterContext(); const hasError = max ? count >= max : false; return {count}; } const StyledCount = styled.div` background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; color: white; padding: 5px 7px; `; export { Count }; ================================================ FILE: src/patterns/props-getters/components/Decrement.js ================================================ import React from "react"; import { StyledButton } from "./styles.js"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; function Decrement({ icon = "minus", onClick, ...props }) { return ( ); } export { Decrement }; ================================================ FILE: src/patterns/props-getters/components/Increment.js ================================================ import React from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { StyledButton } from "./styles.js"; function Increment({ icon = "plus", onClick, ...props }) { return ( ); } export { Increment }; ================================================ FILE: src/patterns/props-getters/components/Label.js ================================================ import React from "react"; import styled from "styled-components"; function Label({ children }) { return {children}; } const StyledLabel = styled.div` background-color: #e9ecef; color: #495057; padding: 5px 7px; `; export { Label }; ================================================ FILE: src/patterns/props-getters/components/index.js ================================================ export * from "./Count"; export * from "./Decrement"; export * from "./Increment"; export * from "./Label"; ================================================ FILE: src/patterns/props-getters/components/styles.js ================================================ import styled from "styled-components"; const StyledButton = styled.button` background-color: white; border: none; &:hover { cursor: pointer; } &:active, &:focus { outline: none; } `; export { StyledButton }; ================================================ FILE: src/patterns/props-getters/useCounter.js ================================================ import { useState } from "react"; //Function which concat all functions together const callFnsInSequence = (...fns) => (...args) => fns.forEach((fn) => fn && fn(...args)); function useCounter({ initial, max }) { const [count, setCount] = useState(initial); const handleIncrement = () => { setCount((prevCount) => Math.min(prevCount + 1, max)); }; const handleDecrement = () => { setCount((prevCount) => Math.max(0, prevCount - 1)); }; //props getter for 'Counter' const getCounterProps = ({ ...otherProps } = {}) => ({ value: count, "aria-valuemax": max, "aria-valuemin": 0, "aria-valuenow": count, ...otherProps }); //props getter for 'Decrement' const getDecrementProps = ({ onClick, ...otherProps } = {}) => ({ onClick: callFnsInSequence(handleDecrement, onClick), disabled: count === 0, ...otherProps }); //props getter for 'Increment' const getIncrementProps = ({ onClick, ...otherProps } = {}) => ({ onClick: callFnsInSequence(handleIncrement, onClick), disabled: count === max, ...otherProps }); return { count, handleIncrement, handleDecrement, getCounterProps, getDecrementProps, getIncrementProps }; } export { useCounter }; ================================================ FILE: src/patterns/props-getters/useCounterContext.js ================================================ import React from "react"; const CounterContext = React.createContext(undefined); function CounterProvider({ children, value }) { return ( {children} ); } function useCounterContext() { const context = React.useContext(CounterContext); if (context === undefined) { throw new Error("useCounterContext must be used within a CounterProvider"); } return context; } export { CounterProvider, useCounterContext }; ================================================ FILE: src/patterns/state-reducer/Counter.js ================================================ import React, { useRef, useEffect } from "react"; import styled from "styled-components"; import { CounterProvider } from "./useCounterContext"; import { Count, Label, Decrement, Increment } from "./components"; function Counter({ children, value: count, onChange }) { const firstMounded = useRef(true); useEffect(() => { if (!firstMounded.current) { onChange && onChange(count); } firstMounded.current = false; }, [count, onChange]); return ( {children} ); } const StyledCounter = styled.div` display: inline-flex; border: 1px solid #17a2b8; line-height: 1.5; border-radius: 0.25rem; overflow: hidden; `; Counter.Count = Count; Counter.Label = Label; Counter.Increment = Increment; Counter.Decrement = Decrement; export { Counter }; ================================================ FILE: src/patterns/state-reducer/Usage.js ================================================ import React from "react"; import styled from "styled-components"; import { Counter } from "./Counter"; import { useCounter } from "./useCounter"; const MAX_COUNT = 10; function Usage() { const reducer = (state, action) => { switch (action.type) { case "decrement": return { count: Math.max(0, state.count - 2) //The decrement delta was changed for 2 (Default is 1) }; default: return useCounter.reducer(state, action); } }; const { count, handleDecrement, handleIncrement } = useCounter( { initial: 0, max: 10 }, reducer ); return ( <> Counter ); } export { Usage }; const StyledContainer = styled.div` margin-top: 20px; `; ================================================ FILE: src/patterns/state-reducer/components/Count.js ================================================ import React from "react"; import styled from "styled-components"; import { useCounterContext } from "../useCounterContext"; function Count({ max }) { const { count } = useCounterContext(); const hasError = max ? count >= max : false; return {count}; } const StyledCount = styled.div` background-color: ${({ hasError }) => (hasError ? "#bd2130" : "#17a2b8")}; color: white; padding: 5px 7px; `; export { Count }; ================================================ FILE: src/patterns/state-reducer/components/Decrement.js ================================================ import React from "react"; import { StyledButton } from "./styles.js"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; function Decrement({ icon = "minus", onClick, ...props }) { return ( ); } export { Decrement }; ================================================ FILE: src/patterns/state-reducer/components/Increment.js ================================================ import React from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { StyledButton } from "./styles.js"; function Increment({ icon = "plus", onClick, ...props }) { return ( ); } export { Increment }; ================================================ FILE: src/patterns/state-reducer/components/Label.js ================================================ import React from "react"; import styled from "styled-components"; function Label({ children }) { return {children}; } const StyledLabel = styled.div` background-color: #e9ecef; color: #495057; padding: 5px 7px; `; export { Label }; ================================================ FILE: src/patterns/state-reducer/components/index.js ================================================ export * from "./Count"; export * from "./Decrement"; export * from "./Increment"; export * from "./Label"; ================================================ FILE: src/patterns/state-reducer/components/styles.js ================================================ import styled from "styled-components"; const StyledButton = styled.button` background-color: white; border: none; &:hover { cursor: pointer; } &:active, &:focus { outline: none; } `; export { StyledButton }; ================================================ FILE: src/patterns/state-reducer/useCounter.js ================================================ import { useReducer } from "react"; const internalReducer = ({ count }, { type, payload }) => { switch (type) { case "increment": return { count: Math.min(count + 1, payload.max) }; case "decrement": return { count: Math.max(0, count - 1) }; default: throw new Error(`Unhandled action type: ${type}`); } }; function useCounter({ initial, max }, reducer = internalReducer) { const [{ count }, dispatch] = useReducer(reducer, { count: initial }); const handleIncrement = () => { dispatch({ type: "increment", payload: { max } }); }; const handleDecrement = () => { dispatch({ type: "decrement" }); }; return { count, handleIncrement, handleDecrement }; } useCounter.reducer = internalReducer; useCounter.types = { increment: "increment", decrement: "decrement" }; export { useCounter }; ================================================ FILE: src/patterns/state-reducer/useCounterContext.js ================================================ import React from "react"; const CounterContext = React.createContext(undefined); function CounterProvider({ children, value }) { return ( {children} ); } function useCounterContext() { const context = React.useContext(CounterContext); if (context === undefined) { throw new Error("useCounterContext must be used within a CounterProvider"); } return context; } export { CounterProvider, useCounterContext };