Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fancy Counter: Better way to handle event listeners and solving the button focus issue without polluting the click event handlers #3

Open
Dev-Dipesh opened this issue Mar 12, 2024 · 0 comments

Comments

@Dev-Dipesh
Copy link

Dev-Dipesh commented Mar 12, 2024

Problem 1: Solving the button focus issue conflicting with the space bar event

Solution:

We can simply use e.preventDefault() in the if conditional block.

const handleSpace = (e) => {
      if (e.code === "Space") {
        e.preventDefault();
        setCount(0);
      }

Problem 2: Performance optimization on adding and removing event handlers.

Solution:

In the course, the event listeners are added and removed every time the components re-render due to a change in the state of the count. We can gain performance by using the useRef hook. It might be a bit of an advanced concept this early in the course. But maybe it's worth it to talk about the performance issue of the current approach, so people following along know that's an issue to avoid in real-world and will be addressed later in the course.

const countRef = useRef(count);

// Handling side-effects in hooks as best practices instead of mutating directly in the function body  
// Updating countRef.current whenever count changes
  useEffect(() => {
    countRef.current = count;
  }, [count]); // This useEffect is solely for keeping countRef.current in sync with count

Full Code

import { useEffect, useRef, useState } from "react";

import { ResetIcon, PlusIcon, MinusIcon } from "@radix-ui/react-icons";

import Title from "./Title";
import Count from "./Count";
import Button from "./Button";
import Card from "./Card";

function Counter() {
  const [count, setCount] = useState(0);

  const countRef = useRef(count);

  // Updating countRef.current whenever count changes
  useEffect(() => {
    countRef.current = count;
  }, [count]); // This useEffect is solely for keeping countRef.current in sync with count

  // Setting up and tearing down event listeners
  useEffect(() => {
    const handleArrowUp = (e) => {
      if (e.key === "ArrowUp") {
        e.preventDefault();
        setCount(countRef.current + 1);
      }
    };

    const handleArrowDown = (e) => {
      if (e.key === "ArrowDown") {
        e.preventDefault();
        if (countRef.current === 0) return;
        setCount(countRef.current - 1);
      }
    };

    const handleSpace = (e) => {
      if (e.code === "Space") {
        e.preventDefault();
        setCount(0);
      }
    };

    window.addEventListener("keydown", handleArrowUp);
    window.addEventListener("keydown", handleArrowDown);
    window.addEventListener("keydown", handleSpace);

    return () => {
      window.removeEventListener("keydown", handleArrowUp);
      window.removeEventListener("keydown", handleArrowDown);
      window.removeEventListener("keydown", handleSpace);
    };
  }, []); // Note: No dependencies here, so this setup and teardown happens only once

  const handleIncrement = () => setCount(count + 1);
  const handleDecrement = () => setCount(count - 1);
  const handleReset = () => setCount(0);

  return (
    <Card>
      <Title title="fancy counter" />
      <Count count={count} />
      <Button
        theme={count === 0 ? "reset-btn-disabled" : "reset-btn"}
        onClick={handleReset}
        disabled={count === 0 ? true : false}
      >
        <ResetIcon className="reset-btn-icon" />
      </Button>
      <div className="button-container">
        <Button
          theme={count === 0 ? "count-btn-disabled" : "count-btn"}
          onClick={handleDecrement}
          disabled={count === 0 ? true : false}
        >
          <MinusIcon className="count-btn-icon" />
        </Button>
        <Button theme={"count-btn"} onClick={handleIncrement} disabled={false}>
          <PlusIcon className="count-btn-icon" />
        </Button>
      </div>
    </Card>
  );
}

export default Counter;

PS: I added arrow up and arrow down events for increment and decrement and also used a common pure Button component. I disabled the decrement and reset button when the count reaches zero. Below are the CSS styles I used -

.reset-btn-disabled {
  cursor: not-allowed;
  opacity: 0.3;
  transition: all 0.4s;
}

.reset-btn {
  cursor: pointer;
  opacity: 0.7;
  transition: all 0.4s;
}

.count-btn {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
  transition: all 0.4s;
}

.count-btn-disabled {
  flex: 1;
  opacity: 0.3;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: not-allowed;
  transition: all 0.4s;
}

Button Component

function Button({ children, theme, onClick, disabled }) {
  return (
    <button className={theme} onClick={onClick} disabled={disabled}>
      {children}
    </button>
  );
}

export default Button;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant