Provider Pattern#
import React, { useState } from "react";
import "./styles.css";
import List from "./List";
import Toggle from "./Toggle";
export const themes = {
  light: {
    background: "#fff",
    color: "#000"
  },
  dark: {
    background: "#171717",
    color: "#fff"
  }
};
export const ThemeContext = React.createContext();
export default function App() {
  const [theme, setTheme] = useState("dark");
  function toggleTheme() {
    setTheme(theme === "light" ? "dark" : "light");
  }
  return (
    <div className={`App theme-${theme}`}>
      <ThemeContext.Provider value={{ theme: themes[theme], toggleTheme }}>
        <>
          <Toggle />
          <List />
        </>
      </ThemeContext.Provider>
    </div>
  );
}
import React, { useContext } from "react";
import { ThemeContext } from "./App";
export default function Toggle() {
  const theme = useContext(ThemeContext);
  return (
    <label className="switch">
      <input type="checkbox" onClick={theme.toggleTheme} />
      <span className="slider round" />
    </label>
  );
}
import React, { useContext } from "react";
import { ThemeContext } from "./App";
 
export default function TextBox() {
  const theme = useContext(ThemeContext);
 
  return <li style={theme.theme}>...</li>;
}
Observer Pattern#
class Observable {
  constructor() {
    this.observers = [];
  }
 
  subscribe(func) {
    this.observers.push(func);
  }
 
  unsubscribe(func) {
    this.observers = this.observers.filter((observer) => observer !== func);
  }
 
  notify(data) {
    this.observers.forEach((observer) => observer(data));
  }
}
import React from "react";
import { Button, Switch, FormControlLabel } from "@material-ui/core";
import { ToastContainer, toast } from "react-toastify";
import observable from "./Observable";
function handleClick() {
  observable.notify("User clicked button!");
}
function handleToggle() {
  observable.notify("User toggled switch!");
}
function logger(data) {
  console.log(`${Date.now()} ${data}`);
}
function toastify(data) {
  toast(data, {
    position: toast.POSITION.BOTTOM_RIGHT,
    closeButton: false,
    autoClose: 2000
  });
}
observable.subscribe(logger);
observable.subscribe(toastify);
export default function App() {
  return (
    <div className="App">
      <Button variant="contained" onClick={handleClick}>
        Click me!
      </Button>
      <FormControlLabel
        control={<Switch name="" onChange={handleToggle} />}
        label="Toggle me!"
      />
      <ToastContainer />
    </div>
  );
}
HOC Pattern#
Best use-cases for a HOC:
- The same, uncustomized behavior needs to be used by many components throughout the application.
- The component can work standalone, without the added custom logic.
Best use-cases for Hooks:
- The behavior has to be customized for each component that uses it.
- The behavior is not spread throughout the application, only one or a few components use the behavior.
- The behavior adds many properties to the component
import React from "react";
import withLoader from "./withLoader";
import useHover from "./useHover";
function DogImages(props) {
  const [hoverRef, hovering] = useHover();
  return (
    <div ref={hoverRef} {...props}>
      {hovering && <div id="hover">Hovering!</div>}
      <div id="list">
        {props.data.message.map((dog, index) => (
          <img src={dog} alt="Dog" key={index} />
        ))}
      </div>
    </div>
  );
}
export default withLoader(
  DogImages,
  "https://dog.ceo/api/breed/labrador/images/random/6"
);
import { useState, useRef, useEffect } from "react";
export default function useHover() {
  const [hovering, setHover] = useState(false);
  const ref = useRef(null);
  const handleMouseOver = () => setHover(true);
  const handleMouseOut = () => setHover(false);
  useEffect(() => {
    const node = ref.current;
    if (node) {
      node.addEventListener("mouseover", handleMouseOver);
      node.addEventListener("mouseout", handleMouseOut);
      return () => {
        node.removeEventListener("mouseover", handleMouseOver);
        node.removeEventListener("mouseout", handleMouseOut);
      };
    }
  }, [ref.current]);
  return [ref, hovering];
}
import React, { useEffect, useState } from "react";
export default function withLoader(Element, url) {
  return props => {
    const [data, setData] = useState(null);
    useEffect(() => {
      fetch(url)
        .then(res => res.json())
        .then(data => setData(data));
    }, []);
    if (!data) {
      return <div>Loading...</div>;
    }
    return <Element {...props} data={data} />;
  };
}
Render Props Pattern#
import React, { useState } from "react";
import "./styles.css";
function Input(props) {
  const [value, setValue] = useState(0);
  return (
    <>
      <input
        type="number"
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Temp in °C"
      />
      {props.children(value)}
    </>
  );
}
export default function App() {
  return (
    <div className="App">
      <h1>☃️ Temperature Converter 🌞</h1>
      <Input>
        {value => (
          <>
            <Kelvin value={value} />
            <Fahrenheit value={value} />
          </>
        )}
      </Input>
    </div>
  );
}
function Kelvin({ value }) {
  return <div className="temp">{parseInt(value || 0) + 273.15}K</div>;
}
function Fahrenheit({ value }) {
  return <div className="temp">{(parseInt(value || 0) * 9) / 5 + 32}°F</div>;
}
Hooks Pattern#
import React, { useState } from "react";
  export default function Input() {
    const [input, setInput] = useState("");
    return (
      <input
        onChange={e => setInput(e.target.value)}
        value={input}
        placeholder="Type something..."
      />
    );
  }
componentDidMount() { ... }
useEffect(() => { ... }, [])
 
componentWillUnmount() { ... }
useEffect(() => { return () => { ... } }, [])
 
componentDidUpdate() { ... }
useEffect(() => { ... })
import React, { useState, useEffect } from "react";
export default function Input() {
  const [input, setInput] = useState("");
  useEffect(() => {
    console.log(`The user typed ${input}`);
  }, [input]);
  return (
    <input
      onChange={e => setInput(e.target.value)}
      value={input}
      placeholder="Type something..."
    />
  );
}
import React from "react";
import useKeyPress from "./useKeyPress";
export default function Input() {
  const [input, setInput] = React.useState("");
  const pressQ = useKeyPress("q");
  const pressW = useKeyPress("w");
  const pressL = useKeyPress("l");
  React.useEffect(() => {
    console.log(`The user pressed Q!`);
  }, [pressQ]);
  React.useEffect(() => {
    console.log(`The user pressed W!`);
  }, [pressW]);
  React.useEffect(() => {
    console.log(`The user pressed L!`);
  }, [pressL]);
  return (
    <input
      onChange={e => setInput(e.target.value)}
      value={input}
      placeholder="Type something..."
    />
  );
}
import React from "react";
export default function useKeyPress(targetKey) {
  const [keyPressed, setKeyPressed] = React.useState(false);
  function handleDown({ key }) {
    if (key === targetKey) {
      setKeyPressed(true);
    }
  }
  React.useEffect(() => {
    window.addEventListener("keydown", handleDown);
    return () => {
      window.removeEventListener("keydown", handleDown);
    };
  }, []);
  return keyPressed;
}
Compound Pattern#
import React from "react";
import ReactDOM from "react-dom";
import ImagesList from "./Images";
const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <div className="App">
      <ImagesList />
    </div>
  </React.StrictMode>,
  rootElement
);
import React from "react";
import FlyOutMenu from "./FlyOutMenu";
const sources = [
  "https://images.pexels.com/photos/939478/pexels-photo-939478.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
  "https://images.pexels.com/photos/1692984/pexels-photo-1692984.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
  "https://images.pexels.com/photos/162829/squirrel-sciurus-vulgaris-major-mammal-mindfulness-162829.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260"
];
function Image({ source }) {
  return (
    <div className="image-item">
      <img src={source} alt="Squirrel" />
      <FlyOutMenu />
    </div>
  );
}
import React from "react";
import "./styles.css";
import { FlyOut } from "./FlyOut";
export default function FlyoutMenu() {
  return (
    <FlyOut>
      <FlyOut.Toggle />
      <FlyOut.List>
        <FlyOut.Item>Edit</FlyOut.Item>
        <FlyOut.Item>Delete</FlyOut.Item>
      </FlyOut.List>
    </FlyOut>
  );
}
import React from "react";
import Icon from "./Icon";
const FlyOutContext = React.createContext();
export function FlyOut(props) {
  const [open, toggle] = React.useState(false);
  return (
    <div>
      {React.Children.map(props.children, child =>
        React.cloneElement(child, { open, toggle })
      )}
    </div>
  );
}
function Toggle() {
  const { open, toggle } = React.useContext(FlyOutContext);
  return (
    <div className="flyout-btn" onClick={() => toggle(!open)}>
      <Icon />
    </div>
  );
}
function List({ children }) {
  const { open } = React.useContext(FlyOutContext);
  return open && <ul className="flyout-list">{children}</ul>;
}
function Item({ children }) {
  return <li className="flyout-item">{children}</li>;
}
FlyOut.Toggle = Toggle;
FlyOut.List = List;
FlyOut.Item = Item;
Reference: