This project is a hybrid slide and code sandbox for learning react.
Create JSX Elements to form virtual DOM
Virtual DOM | Actual DOM |
Compare nodes between virtual DOM and actual browser DOM
Virtual DOM | Actual DOM |
Update the actual DOM if needed
Virtual DOM | Actual DOM |
Move State Down
function ExpensiveComponent() { const renderCount = useRef(0); useEffect(() => { const t = setTimeout(() => console.log("expensive tree!"), 2000); renderCount.current += 1; return () => clearTimeout(t); }); const totalRender = renderCount.current; return <p>I am a very slow component, rendered {totalRender} times!.</p>; } export default function Problematic() { const [color, setColor] = useState("red"); return ( <div> <input value={color} onChange={(e) => setColor(e.target.value)} /> <p style={{ color }}>Hello, world!</p> <ExpensiveComponent /> </div> ); }
function ExpensiveComponent() { const renderCount = useRef(0); useEffect(() => { const t = setTimeout(() => console.log("expensive tree!"), 2000); renderCount.current += 1; return () => clearTimeout(t); }); const totalRender = renderCount.current; return <p>I am a very slow component, rendered {totalRender} times!.</p>; } export default function Problematic() { const [color, setColor] = useState("red"); return ( <div> <input value={color} onChange={(e) => setColor(e.target.value)} /> <p style={{ color }}>Hello, world!</p> <ExpensiveComponent /> </div> ); }
function MovedStateDown() { const [color, setColor] = useState("red"); return ( <> <input value={color} onChange={(e) => setColor(e.target.value)} /> <p style={{ color }}>Hello, world!</p> </> ); } export default function Better() { return ( <div> <MovedStateDown /> <ExpensiveComponent /> </div> ); }
Hello, world!
I am a very slow component, rendered 2 times.
Lift Content Up
function ExpensiveComponent() { const renderCount = useRef(0); useEffect(() => { const t = setTimeout(() => console.log("expensive tree!"), 2000); renderCount.current += 1; return () => clearTimeout(t); }); const totalRender = renderCount.current; return <p>I am a very slow component, rendered {totalRender} times!.</p>; } export default function Problematic() { const [color, setColor] = useState("red"); return ( <div style={{ color }}> <input value={color} onChange={(e) => setColor(e.target.value)} /> <p>Hello, world!</p> <ExpensiveComponent /> </div> ); }
function ExpensiveComponent() { const renderCount = useRef(0); useEffect(() => { const t = setTimeout(() => console.log("expensive tree!"), 2000); renderCount.current += 1; return () => clearTimeout(t); }); const totalRender = renderCount.current; return <p>I am a very slow component, rendered {totalRender} times!.</p>; } export default function Problematic() { const [color, setColor] = useState("red"); return ( <div style={{ color }}> <input value={color} onChange={(e) => setColor(e.target.value)} /> <p>Hello, world!</p> <ExpensiveComponent /> </div> ); }
function LiftContentUp({ children }) { const [color, setColor] = useState("red"); return ( <div style={{ color }}> <input value={color} onChange={(e) => setColor(e.target.value)} /> {children} </div> ); } export default function Better() { return ( <LiftContentUp> <p>Hello, world!</p> <ExpensiveComponent /> </LiftContentUp> ); }
export function Better2() { return ( <ExpensiveComponentWrapper expensiveComponent={<ExpensiveComponent />} /> ); } function ExpensiveComponentWrapper(props) { const [color, setColor] = useState("red"); return ( <div style={{ color }}> <input value={color} onChange={(e) => setColor(e.target.value)} /> <p>Hello, world!</p> {props.expensiveComponent} </div> ); }
Hello, world!
I am a very slow component, rendered 2 times!.
export default function TwoWayBindingDemo() { const [currentColor, setCurrentColor] = useState("blue"); return ( <div> <ColoredHeader color={currentColor}> Our current color is: {currentColor} </ColoredHeader> <BlockInput value={currentColor} onChange={(e) => setCurrentColor(e.target.value)} /> </div> ); }
export default function UseStateNoDispatchFunction() { const [count, setCount] = useState(0); const [buttonClickCount, setButtonClickCount] = useState(0); const increment = () => { setButtonClickCount(buttonClickCount + 1); setTimeout(() => setCount(count + 1), 2000); }; return ( <div> <ColoredHeader>Click Count: {buttonClickCount}</ColoredHeader> <Button onClick={increment}>Increment</Button> <ColoredHeader>{count}</ColoredHeader> </div> ); }
export default function UseStateDispatchFunction() { const [count, setCount] = useState(0); const [buttonClickCount, setButtonClickCount] = useState(0); const increment = () => { setButtonClickCount(buttonClickCount + 1); setTimeout(() => setCount((cc) => cc + 1), 2000); }; return ( <div> <ColoredHeader>Click Count: {buttonClickCount}</ColoredHeader> <Button onClick={increment}>Increment</Button> <ColoredHeader>{count}</ColoredHeader> </div> ); }
// Additive devices that are light emitting use RGB. Such as Computers, Television, Mobile Phone const rgbColors = [ { name: "red", uniqueId: 11 }, { name: "green", uniqueId: 12 }, { name: "blue", uniqueId: 13 }, ]; // Subtractive CMYK is used for paper, items that reflect light const cmykColors = [ { name: "cyan", uniqueId: 21 }, { name: "magenta", uniqueId: 22 }, { name: "yellow", uniqueId: 23 }, { name: "black", uniqueId: 24 }, ]; const SUBTRACTIVE_COLOR_TYPE = "Subtractive"; const ADDITIVE_COLOR_TYPE = "Additive"; const colorMapping = [ { name: SUBTRACTIVE_COLOR_TYPE, values: cmykColors, uniqueId: uuidv4() }, { name: ADDITIVE_COLOR_TYPE, values: rgbColors, uniqueId: uuidv4() }, ];
export default function UsingUseStateForDependentState() { const [colorSetting, setColorSetting] = useState(() => { const initialColorSetting = colorMapping.find( (cm) => cm.name === ADDITIVE_COLOR_TYPE ); return { currentColor: initialColorSetting.values[0], colorType: initialColorSetting, colors: initialColorSetting.values, }; }); const onHandleColorTypeSelection = useCallback((colorType) => { const colorSetting = colorMapping.find((cm) => cm.name === colorType.name); setColorSetting({ currentColor: colorSetting.values[0], colorType: colorType, colors: colorSetting.values, }); }, []); const onHandleColorSelection = useCallback( (color) => setColorSetting((c) => ({ ...c, currentColor: color })), [] ); const colorTypeChoices = useMemo( () => colorMapping.filter((cm) => cm.name !== colorSetting.colorType.name), [colorSetting.colorType] ); return ( <div> <ColoredHeader color={colorSetting.currentColor.name}> This header changes color (useState) </ColoredHeader> <DropDown id="color-type" dropDownLabelId="color-type" choices={colorTypeChoices} currentValue={colorSetting.colorType} setValues={onHandleColorTypeSelection} /> <DropDown id="colors" dropDownLabelId="colors" choices={colorSetting.colors} currentValue={colorSetting.currentColor} setValues={onHandleColorSelection} /> </div> ); }
const SET_COLOR_TYPE = "SET_COLOR_TYPE"; const SET_COLOR = "SET_COLOR"; function colorReducer(state, action) { const { type } = action; switch (type) { case SET_COLOR_TYPE: { const colorType = colorMapping.find( (cm) => cm.name === action.colorType.name ); const newAvailableColors = colorType.values; return { selectedColor: newAvailableColors[0], colorType: colorType, colors: newAvailableColors, }; } case SET_COLOR: { return { ...state, selectedColor: action.color }; } } }
function initialise(initialColorType) { const colorType = colorMapping.find((cm) => cm.name === initialColorType); return { selectedColor: colorType.values[0], colorType: colorType, colors: colorType.values, }; } function useColorType(initialColorType) { const [state, dispatch] = useReducer( colorReducer, initialColorType, initialise ); const setColorType = useCallback( (colorType) => dispatch({ type: SET_COLOR_TYPE, colorType }), [] ); const setColor = useCallback( (color) => dispatch({ type: SET_COLOR, color }), [] ); return [ state, { setColor, setColorType, }, ]; }
export default function UsingUseReducer() { const [colorType, { setColor, setColorType }] = useColorType(ADDITIVE_COLOR_TYPE); const onHandleColorTypeSelection = useCallback( (colorType) => setColorType(colorType), [setColorType] ); const onHandleColorSelection = useCallback( (color) => setColor(color), [setColor] ); const colorTypeChoices = useMemo( () => colorMapping.filter((cm) => cm.name !== colorType.colorType.name), [colorType.colorType] ); return ( <div> <ColoredHeader color={colorType.selectedColor.name}> This header changes color (useReducer) </ColoredHeader> <DropDown id="color-type" dropDownLabelId="color-type" choices={colorTypeChoices} currentValue={colorType.colorType} setValues={onHandleColorTypeSelection} /> <DropDown id="colors" dropDownLabelId="colors" choices={colorType.colors} currentValue={colorType.selectedColor} setValues={onHandleColorSelection} /> </div> ); }
export default function UsingUseContextPropDrillDemo() { // ... code return ( <div style={{ display: "block" }}> <ColoredHeader color={colorTypeSetting.selectedColor.name}> This header changes color (Prop Drilling) </ColoredHeader> <DropDown id="color-type" dropDownLabelId="color-type" choices={colorTypeChoices} currentValue={colorTypeSetting.colorType} setValues={onHandleColorTypeSelection} /> <DropDown id="colors" dropDownLabelId="colors" choices={colorTypeSetting.colors} currentValue={colorTypeSetting.selectedColor} setValues={onHandleColorSelection} /> <DescriptionAccordions colors={colorTypeSetting.colors} colorType={colorTypeSetting.colorType} /> </div> ); }
function ColorAccordion({ colors }) { const memoizedColors = useMemoObjCompare(colors); const panelData = useMemo(() => { const data = memoizedColors.map((fcm) => fcm.name).join(", "); return <p style={{ fontSize: "15px" }}>{data}</p>; }, [memoizedColors]); return ( <Accordion buttonLabel="Colors" panelData={panelData} maxHeight={120} /> ); } function DetailAccordion({ colorType }) { const panelData = useMemo(() => { const fontSize = "15px"; return colorType.name === SUBTRACTIVE_COLOR_TYPE ? ( <> <p style={{ fontSize }}> Subtractive colors are created by completely or partially absorbing <br /> some light wavelengths and reflecting others. </p> </> ) : ( <p style={{ fontSize }}> Additive colors are created by adding colored light to black. </p> ); }, [colorType]); return ( <Accordion buttonLabel="Detail" panelData={panelData} maxHeight={120} /> ); } function DescriptionAccordions({ colors, colorType }) { return ( <> <p>Information</p> <ColorAccordion colors={colors} /> <DetailAccordion colorType={colorType} /> </> ); }
function colorContextReducer(state, action) { switch (action.type) { case SET_SELECTED_COLOR_TYPE: { return { ...state, colorType: action.colorType, colors: action.colors, selectedColor: action.colors[0], }; } case SET_SELECTED_COLOR: { return { ...state, selectedColor: action.selectedColor, }; } default: { throw new Error("Unhandled action type"); } } }
import React, { createContext, useReducer } from "react"; const ColorContext = createContext(); let defaultInitialState = { colorType: "", selectedColor: {}, colors: [], }; function ColorProvider({ children, initialColorType, initialAvailableColors, initialColor, }) { defaultInitialState.colorType = initialColorType; defaultInitialState.colors = initialAvailableColors; defaultInitialState.selectedColor = initialColor; const [state, dispatch] = useReducer( colorContextReducer, defaultInitialState ); const value = { state, dispatch }; return ( <ColorContext.Provider value={value}>{children}</ColorContext.Provider> ); }
import React, { useContext } from "react"; function useColorContext() { const context = useContext(ColorContext); if (context === undefined) { throw new Error("useColorContext must be used within a Provider"); } return context; } // Helper functions for dispatch function setColorType(dispatch, colorType, colorChoices) { dispatch({ type: SET_SELECTED_COLOR_TYPE, colorType, colors: colorChoices, }); } function setSelectedColor(dispatch, selectedColor) { dispatch({ type: SET_SELECTED_COLOR, selectedColor, }); }
export default function UsingUseContextDemo() { const initialColorType = colorMapping.find( (cm) => cm.name === ADDITIVE_COLOR_TYPE ); const initialAvailableColors = initialColorType.values; const initialColor = initialAvailableColors[0]; const colorTypeChoices = useMemo(() => colorMapping.map((cm) => cm.name), []); return ( <ColorProvider initialColorType={initialColorType} colorTypeChoices={colorTypeChoices} initialAvailableColors={initialAvailableColors} initialColor={initialColor} > <ColorContextComponentUser /> </ColorProvider> ); } function ColorContextComponentUser() { const { state: { colors, colorType, selectedColor }, dispatch, } = useColorContext(); // ... other codes // The second dropdown calls this to set the selected color const onHandleColorSelection = useCallback( (color) => setSelectedColor(dispatch, color), [dispatch] ); return ( <div style={{ display: "block" }}> <ColoredHeader color={selectedColor.name}> This header changes color (using context) </ColoredHeader> <DropDown id="color-type" dropDownLabelId="color-type" choices={colorTypeChoices} currentValue={colorType} setValues={onHandleColorTypeSelection} /> <DropDown id="colors" dropDownLabelId="colors" choices={colors} currentValue={selectedColor} setValues={onHandleColorSelection} /> {/* We don't need to pass in the props here anymore */} <DescriptionAccordions /> </div> );
function DescriptionAccordions() { return ( <> <p>Information</p> <ColorAccordion /> <DetailAccordion /> </> ); } function ColorAccordion() { const { state: { colors }, } = useColorContext(); const panelData = useMemo(() => { const data = colors.map((c) => c.name).join(", "); return <p style={{ fontSize: "15px" }}>{data}</p>; }, [colors]); return ( <Accordion buttonLabel="Colors" panelData={panelData} maxHeight={120} /> ); } function DetailAccordion() { const { state: { colorType }, } = useColorContext(); const panelData = useMemo(() => { const fontSize = "15px"; return colorType.name === SUBTRACTIVE_COLOR_TYPE ? ( <> <p style={{ fontSize }}> Subtractive colors are created by completely or partially absorbing <br /> some light wavelengths and reflecting others. </p> </> ) : ( <p style={{ fontSize }}> Additive colors are created by adding colored light to black. </p> ); }, [colorType.name]); return ( <Accordion buttonLabel="Detail" panelData={panelData} maxHeight={120} /> ); }
Information
red, green, blue
Additive colors are created by adding colored light to black.
Basic memo
export default function CounterApp() { const [count, setCount] = useState(0); const [buttonTextColor, setButtonTextColor] = useState("blue"); return ( <CounterAppWrapper> <h2>Button Counter</h2> <Button buttonColor={buttonTextColor} onClick={() => setCount((prevCount) => prevCount + 1)} > Increment </Button> <Button onClick={() => setButtonTextColor("red")}>Red</Button> <Button onClick={() => setButtonTextColor("green")}>Green</Button> <Button onClick={() => setButtonTextColor("blue")}>Blue</Button> <Counter count={count} /> </CounterAppWrapper> ); } function Counter({ count }) { const renderCountRef = useRef(0); const renderCount = renderCountRef.current; useEffect(() => { renderCountRef.current += 1; }); return ( <div> <h4>Counter: Render count: {renderCount} time(s)</h4> <h4>Counter: Increment count: {count} time(s)</h4> </div> ); }
export default function CounterApp() { const [count, setCount] = useState(0); const [buttonTextColor, setButtonTextColor] = useState("blue"); return ( <CounterAppWrapper> <h2>Button Counter</h2> <Button buttonColor={buttonTextColor} onClick={() => setCount((prevCount) => prevCount + 1)} > Increment </Button> <Button onClick={() => setButtonTextColor("red")}>Red</Button> <Button onClick={() => setButtonTextColor("green")}>Green</Button> <Button onClick={() => setButtonTextColor("blue")}>Blue</Button> <MemoizedCounter count={count} /> </CounterAppWrapper> ); } function Counter({ count }) { const renderCountRef = useRef(0); const renderCount = renderCountRef.current; useEffect(() => { renderCountRef.current += 1; }); return ( <div> <h4>Counter: Render count: {renderCount} time(s)</h4> <h4>Counter: Increment count: {count} time(s)</h4> </div> ); } const MemoizedCounter = memo(Counter);
Using custom compare in React.memo
export default function CustomMemoApp() { const [ball, setBall] = useState({ color: "blue", weight: 0 }); const setBallToColor = (color) => setBall((pb) => ({ ...pb, color })); const incrementWeight = () => setBall((pb) => ({ ...pb, weight: pb.weight + 1 })); return ( <div className="App"> <h2>Custom Memo</h2> <Button buttonColor={ball.color} onClick={incrementWeight}> Increment Weight </Button> <Button onClick={() => setBallToColor("red")}>Red</Button> <Button onClick={() => setBallToColor("green")}>Green</Button> <Button onClick={() => setBallToColor("blue")}>Blue</Button> <BallInfo ball={ball} /> </div> ); } function BallInfoToMemo({ ball }) { const renderCountRef = useRef(0); const renderCount = renderCountRef.current; useEffect(() => (renderCountRef.current += 1)); return ( <div> <h4>Render count: {renderCount} time(s)</h4> <p> Color:{ball.color}, Weight:{ball.weight} </p> </div> ); } const BallInfo = memo(BallInfoToMemo);
export default function CustomMemoApp() { const [ball, setBall] = useState({ color: "blue", weight: 0 }); const setBallToColor = (color) => setBall((pb) => ({ ...pb, color })); const incrementWeight = () => setBall((pb) => ({ ...pb, weight: pb.weight + 1 })); return ( <div className="App"> <h2>Custom Memo</h2> <Button buttonColor={ball.color} onClick={incrementWeight}> Increment Weight </Button> <Button onClick={() => setBallToColor("red")}>Red</Button> <Button onClick={() => setBallToColor("green")}>Green</Button> <Button onClick={() => setBallToColor("blue")}>Blue</Button> <BallInfo ball={ball} /> </div> ); } function BallInfoToMemo({ ball }) { const renderCountRef = useRef(0); const renderCount = renderCountRef.current; useEffect(() => (renderCountRef.current += 1)); return ( <div> <h4>Render count: {renderCount} time(s)</h4> <p> Color:{ball.color}, Weight:{ball.weight} </p> </div> ); } const BallInfo = memo(BallInfoToMemo);
export default function CustomMemoApp() { const [ball, setBall] = useState({ color: "blue", weight: 0 }); const setBallToColor = (color) => setBall((pb) => ({ ...pb, color })); const incrementWeight = () => setBall((pb) => ({ ...pb, weight: pb.weight + 1 })); return ( <div className="App"> <h2>Custom Memo</h2> <Button buttonColor={ball.color} onClick={incrementWeight}> Increment Weight </Button> <Button onClick={() => setBallToColor("red")}>Red</Button> <Button onClick={() => setBallToColor("green")}>Green</Button> <Button onClick={() => setBallToColor("blue")}>Blue</Button> <MemoizedBallInfo ball={ball} /> </div> ); } function BallInfoToMemo({ ball }) { const renderCountRef = useRef(0); const renderCount = renderCountRef.current; useEffect(() => (renderCountRef.current += 1)); return ( <div> <h4>Render count: {renderCount} time(s)</h4> <p> Color:{ball.color}, Weight:{ball.weight} </p> </div> ); } // Using lodash's isEqual function const MemoizedBallInfo = memo(BallInfoToMemo, isEqual);
export default function CustomMemoApp() { const [ball, setBall] = useState({ color: "blue", weight: 0 }); const setBallToColor = (color) => setBall((pb) => ({ ...pb, color })); const incrementWeight = () => setBall((pb) => ({ ...pb, weight: pb.weight + 1 })); return ( <div className="App"> <h2>Custom Memo</h2> <Button buttonColor={ball.color} onClick={incrementWeight}> Increment Weight </Button> <Button onClick={() => setBallToColor("red")}>Red</Button> <Button onClick={() => setBallToColor("green")}>Green</Button> <Button onClick={() => setBallToColor("blue")}>Blue</Button> <MemoizedBallInfo ball={ball} /> </div> ); } function BallInfoToMemo({ ball }) { const renderCountRef = useRef(0); const renderCount = renderCountRef.current; useEffect(() => (renderCountRef.current += 1)); return ( <div> <h4>Render count: {renderCount} time(s)</h4> <p> Color:{ball.color}, Weight:{ball.weight} </p> </div> ); } // or const MemoizedBallInfo = memo(BallInfoToMemo, (prop1, prop2) => { return prop1.weight === prop2.weight && prop1.color === prop2.color; });
Color: blue, Weight: 0
using objects as useEffect dependencies
export default function ColorAndCountDemo() { const [colorAndCount, setColorAndCount] = useState(() => ({ count: 0, color: colorChoices.find((c) => c.name === "blue"), })); const setColor = (color) => { setColorAndCount((ccc) => ({ count: ccc.count, color: colorChoices.find((c) => c.name === color), })); }; const incrementCount = () => { setColorAndCount((ccc) => ({ count: ccc.count + 1, color: ccc.color })); }; return ( <div> <Controls> <Button onClick={incrementCount}>Increment</Button> <Button onClick={() => setColor("red")}>Red</Button> <Button onClick={() => setColor("green")}>Green</Button> <Button onClick={() => setColor("blue")}>Blue</Button> </Controls> <ColorAndCountComponent colorAndCount={colorAndCount} /> </div> ); }
export default function ColorAndCountDemo() { const [colorAndCount, setColorAndCount] = useState(() => ({ count: 0, color: colorChoices.find((c) => c.name === "blue"), })); const setColor = (color) => { setColorAndCount((ccc) => ({ count: ccc.count, color: colorChoices.find((c) => c.name === color), })); }; const incrementCount = () => { setColorAndCount((ccc) => ({ count: ccc.count + 1, color: ccc.color })); }; return ( <div> <Controls> <Button onClick={incrementCount}>Increment</Button> <Button onClick={() => setColor("red")}>Red</Button> <Button onClick={() => setColor("green")}>Green</Button> <Button onClick={() => setColor("blue")}>Blue</Button> </Controls> <ColorAndCountComponent colorAndCount={colorAndCount} /> </div> ); }
function ColorAndCountComponent({ colorAndCount }) { const [color, setColor] = useState(""); const [count, setCount] = useState(0); const [renderCount, setRenderCount] = useState(0); useEffect(() => { setRenderCount((c) => c + 1); if (colorAndCount) { const { count, color } = colorAndCount; setCount(count); setColor(color.name); } }, [colorAndCount]); return ( <> <div>UseEffect Run Count {renderCount}</div> <ColorInfo style={{ color }}> Color: {color} Count: {count} </ColorInfo> </> ); }
function ColorAndCountComponent({ colorAndCount }) { const [color, setColor] = useState(""); const [count, setCount] = useState(0); const [renderCount, setRenderCount] = useState(0); useEffect(() => { setRenderCount((c) => c + 1); if (colorAndCount) { const { count, color } = colorAndCount; setCount(count); setColor(color.name); } }, [colorAndCount]); // We cannot change to color.count and color.color.name because // we check the full object for null/undefined. This is if // we follow the exhaustive-deps react-hooks plugin eslint rule. return ( <> <div>UseEffect Run Count {renderCount}</div> <ColorInfo style={{ color }}> Color: {color} Count: {count} </ColorInfo> </> ); }
const useMemoObjCompare = (value) => { const prevRef = useRef(); const previous = prevRef.current; const isObjEqual = isEqual(previous, value); useEffect(() => { if (!isObjEqual) { prevRef.current = value; } }); return isObjEqual ? previous : value; }; function ColorAndCountComponentWithMemo({ colorAndCount }) { const [color, setColor] = useState(""); const [count, setCount] = useState(0); const [renderCount, setRenderCount] = useState(0); const colorAndCountMemo = useMemoObjCompare(colorAndCount); useEffect(() => { setRenderCount((c) => c + 1); if (colorAndCountMemo) { const { count, color } = colorAndCount; setCount(count); setColor(color.name); } }, [colorAndCountMemo]); return ( <> <div>UseEffect Run Count {renderCount}</div> <ColorInfo style={{ color }}> Color: {color} Count: {count} </ColorInfo> </> ); }
function useDeepCompareMemoize(value) { const ref = useRef(value); const changeTrigger = useRef(0); if (!isEqual(value, ref.current)) { ref.current = value; changeTrigger.current += 1; } // disable exhaustive hooks for this because we want the // memoization to trigger from the increment // eslint-disable-next-line react-hooks/exhaustive-deps return useMemo(() => ref.current, [changeTrigger.current]); } function isPrimitive(value) { return value !== Object(value); } function useDeepCompareEffect(callback, dependencies) { if (!dependencies || !dependencies.length) { throw "Invalid dependencies."; } if (dependencies.every(isPrimitive)) { throw "All dependencies are primitive. Just use useEffect."; } // eslint-disable-next-line react-hooks/exhaustive-deps return useEffect(callback, useDeepCompareMemoize(dependencies)); }
export function ColorAndCountComponentWithUseDeepEffect({ colorAndCount }) { const [color, setColor] = useState(""); const [count, setCount] = useState(0); const [renderCount, setRenderCount] = useState(0); useDeepCompareEffect(() => { setRenderCount((c) => c + 1); if (colorAndCount) { const { count, color } = colorAndCount; setCount(count); setColor(color.name); } }, [colorAndCount]); return ( <> <div>UseEffect Run Count {renderCount}</div> <ColorInfo style={{ color }}> Color: {color} Count: {count} </ColorInfo> </> ); }
export function ColorAndCountComponentWithUseDeepEffectAndUseLayoutEffect({ colorAndCount, }) { const [color, setColor] = useState(""); const [count, setCount] = useState(0); const [renderCount, setRenderCount] = useState(0); useDeepCompareEffect(() => { setRenderCount((c) => c + 1); if (colorAndCount) { const { count, color } = colorAndCount; setCount(count); setColor(color.name); } }, [colorAndCount]); return ( <> <div>UseEffect Run Count {renderCount}</div> <ColorInfo style={{ color }}> Color: {color} Count: {count} </ColorInfo> </> ); }
export function ColorAndCountComponentWithUseDeepEffectAndUseLayoutEffect({ colorAndCount, }) { const [color, setColor] = useState(""); const [count, setCount] = useState(0); const [renderCount, setRenderCount] = useState(0); useDeepCompareEffect(() => { // setRenderCount((c) => c + 1); if (colorAndCount) { const { count, color } = colorAndCount; setCount(count); setColor(color.name); } }, [colorAndCount]); useLayoutEffect(() => { setRenderCount((c) => c + 1); }, []); return ( <> <div>UseEffect Run Count {renderCount}</div> <ColorInfo style={{ color }}> Color: {color} Count: {count} </ColorInfo> </> ); }
export default function ColorAppV1() { const [currentColor, setCurrentColor] = useState("blue"); const [counter, setCounter] = useState(0); const setToGreen = () => setToOtherColor("green"); const setToRed = () => setToOtherColor("red"); const setToBlue = () => setToOtherColor("blue"); const incrementCount = () => setCounter(counter + 1); const setToOtherColor = (color) => setCurrentColor(color); const getColorType = (color) => ["red", "blue", "green"].includes(color) ? "PRIMARY" : "NON PRIMARY"; const colors = [ { name: "red", uniqueId: 1 }, { name: "green", uniqueId: 2 }, { name: "blue", uniqueId: 3 }, { name: "orange", uniqueId: 4 }, { name: "yellow", uniqueId: 5 }, { name: "violet", uniqueId: 6 }, ]; const colorChoices = colors.filter((c) => c.name !== currentColor); const favoriteColors = ["red", "green", "blue"]; return ( <div> <ColoredHeader color={currentColor}>Counter: {counter}</ColoredHeader> <h4>Change the color using the buttons or the drop down!</h4> <Button onClick={setToRed}>Red</Button> <Button onClick={setToGreen}>Green</Button> <Button onClick={setToBlue}>Blue</Button> <Button onClick={incrementCount}>Increment Counter</Button> <div style={{ color: currentColor }}> Color Type: {getColorType(currentColor)} </div> <ColorDropDown colorChoices={colorChoices} currentColor={currentColor} setToColor={setToOtherColor} /> <PrimaryColors colors={favoriteColors} /> </div> ); }
const colors = [ { name: "red", uniqueId: 1 }, { name: "green", uniqueId: 2 }, { name: "blue", uniqueId: 3 }, { name: "orange", uniqueId: 4 }, { name: "yellow", uniqueId: 5 }, { name: "violet", uniqueId: 6 }, ]; const getColorType = (color) => ["red", "blue", "green"].includes(color) ? "PRIMARY" : "NON PRIMARY"; const favoriteColors = ["red", "green", "blue"]; export default function ColorAppV2() { const [currentColor, setCurrentColor] = useState("blue"); const [counter, setCounter] = useState(0); const setToGreen = () => setToOtherColor("green"); const setToRed = () => setToOtherColor("red"); const setToBlue = () => setToOtherColor("blue"); const incrementCount = () => setCounter(counter + 1); const setToOtherColor = (color) => setCurrentColor(color); ...
export default function ColorAppV2() { const [currentColor, setCurrentColor] = useState("blue"); const [counter, setCounter] = useState(0); const setToGreen = () => setToOtherColor("green"); const setToRed = () => setToOtherColor("red"); const setToBlue = () => setToOtherColor("blue"); const incrementCount = () => setCounter(counter + 1); const setToOtherColor = (color) => setCurrentColor(color); const colorChoices = colors.filter((c) => c.name !== currentColor); return ( <div> <ColoredHeader color={currentColor}>Counter: {counter}</ColoredHeader> <h4>Change the color using the buttons or the drop down!</h4> <Button onClick={setToRed}>Red</Button> <Button onClick={setToGreen}>Green</Button> <Button onClick={setToBlue}>Blue</Button> <Button onClick={incrementCount}>Increment Counter</Button> <div style={{ color: currentColor }}> Color Type: {getColorType(currentColor)} </div> <ColorDropDown colorChoices={colorChoices} currentColor={currentColor} setToColor={setToOtherColor} /> <PrimaryColors colors={favoriteColors} /> </div> ); }
export default function ColorAppV3() { const [currentColor, setCurrentColor] = useState("blue"); const [counter, setCounter] = useState(0); const setToGreen = () => setToOtherColor("green"); const setToRed = () => setToOtherColor("red"); const setToBlue = () => setToOtherColor("blue"); const incrementCount = () => setCounter(counter + 1); const setToOtherColor = (color) => setCurrentColor(color); const colorChoices = useMemo(() => { return colors.filter((c) => c.name !== currentColor); }, [currentColor]); return ( <div> <ColoredHeader color={currentColor}>Counter: {counter}</ColoredHeader> <h4>Change the color using the buttons or the drop down!</h4> <Button onClick={setToRed}>Red</Button> <Button onClick={setToGreen}>Green</Button> <Button onClick={setToBlue}>Blue</Button> <Button onClick={incrementCount}>Increment Counter</Button> <div style={{ color: currentColor }}> Color Type: {getColorType(currentColor)} </div> <ColorDropDown colorChoices={colorChoices} currentColor={currentColor} setToColor={setToOtherColor} /> <PrimaryColors colors={favoriteColors} /> </div> ); }
export default function ColorAppV3() { const [currentColor, setCurrentColor] = useState("blue"); const [counter, setCounter] = useState(0); const setToGreen = () => setToOtherColor("green"); const setToRed = () => setToOtherColor("red"); const setToBlue = () => setToOtherColor("blue"); const incrementCount = () => setCounter(counter + 1); const setToOtherColor = (color) => setCurrentColor(color); const colorChoices = useMemo(() => { return colors.filter((c) => c.name !== currentColor); }, [currentColor]); return ( <div className="App"> <ColoredHeader color={currentColor}>I change color. Counter: {counter}</ColoredHeader> <h2>Change the color using the buttons or the drop down!</h2> <Button onClick={setToRed}>Red</Button> <Button onClick={setToGreen}>Green</Button> <Button onClick={setToBlue}>Blue</Button> <Button onClick={incrementCount}>Increment Counter</Button> <div>Color Type: {getColorType(currentColor)}</div> <ColorDropDown colorChoices={colorChoices} currentColor={currentColor} setToColor={setToOtherColor} /> <PrimaryColors colors={favoriteColors}/> </div> ); }
export default function ColorAppV4() { const [currentColor, setCurrentColor] = useState("blue"); const [counter, setCounter] = useState(0); const setToGreen = () => setToOtherColor("green"); const setToRed = () => setToOtherColor("red"); const setToBlue = () => setToOtherColor("blue"); const incrementCount = () => setCounter(counter + 1); const setToOtherColor = useCallback((color) => setCurrentColor(color), []); const colorChoices = useMemo(() => { return colors.filter((c) => c.name !== currentColor); }, [currentColor]); return ( <div> <ColoredHeader color={currentColor}>Counter: {counter}</ColoredHeader> <h4>Change the color using the buttons or the drop down!</h4> <Button onClick={setToRed}>Red</Button> <Button onClick={setToGreen}>Green</Button> <Button onClick={setToBlue}>Blue</Button> <Button onClick={incrementCount}>Increment Counter</Button> <div style={{ color: currentColor }}> Color Type: {getColorType(currentColor)} </div> <ColorDropDown colorChoices={colorChoices} currentColor={currentColor} setToColor={setToOtherColor} /> <PrimaryColors colors={favoriteColors} /> </div> ); }
Dropdown UseEffect RunCount: 3
Primary Colors (Static Table) |
---|
red |
green |
blue |
Primary Colors Table UseEffect RunCount: 3
function useWhyDidYouUpdate(name, props) { const previousProps = useRef(); const updateCount = useRef(0); useEffect(() => { if (previousProps.current) { // Get all keys from previous and current props const allKeys = Object.keys({ ...previousProps.current, ...props }); // Use this object to keep track of changed props const changesObj = {}; // Iterate through keys allKeys.forEach((key) => { // If previous is different from current if (previousProps.current[key] !== props[key]) { // Add to changesObj changesObj[key] = { from: previousProps.current[key], to: props[key], }; } }); // If changesObj is not empty then output to console if (Object.keys(changesObj).length) { console.log("[why-did-it-update]", name, changesObj); updateCount.current += 1; console.log("Update Count: ", updateCount.current); } } // Finally update previousProps with current props for next hook call previousProps.current = props; }); } // Wrap component to check const ColorDropDownUnderCheck = (props) => { useWhyDidYouUpdate("ColorDropDown", props); return <ColorDropDown {...props} />; };