useEffects may not always be suitable

Yogisha,useEffectreactstateevents

Using useEffect for state updates, event handling, and fetching APIs has become such a habit that I have often overlooked many of React's other features. To avoid such bad practices, here are some common mistakes and their better alternatives.

1. React re-renders a component on state change

Example: Suppose you need to display full name of user based on their first name and last name change.

Bad Code:

const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [displayName, setDisplayName] = useState("");
 
useEffect(() => {
  setDisplayName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

Since the state change re-renders a component the displayName will be calculated on each render. When something can be calculated from props or state, don't put it in state.

Good Code:

const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
 
// This calculates during rendering
const displayName = `${firstName} ${lastName}`;

2. Resetting state when prop changes

Bad Code:

function UserProfile({ userId }) {
  const [message, setMessage] = useState("");
 
  useEffect(() => {
    setMessage("");
  }, [userId]);
}

Instead of using useEffect to reset the state, you can use a key to force a re-mount. Using key will make each component different.

Good Code:

function UserProfile({ userId }) {
  return <UserDetails key={userId} userId={userId} />;
}
 
function UserDetails({ userId }) {
  const [message, setMessage] = useState("");
}

3. Recalculating props

When the data can be calculated directly, putting it in state and updating it through useEffect is unnecessary.

Bad Code:

function ItemsList({ items }) {
  const [availableItems, setAvailableItems] = useState([]);
 
  useEffect(() => {
    setAvailableItems(items.filter((item) => item.isAvailable));
  }, [items]);
}

Good Code:

function ItemsList({ items }) {
  const availableItems = items.filter((item) => item.isAvailable);
}

4. Sharing logic between event handlers

Use Effects only for code that should run because the component was displayed to the user.

Bad Code:

function ShoppingCart({ item, addToCart }) {
  useEffect(() => {
    if (item.inCart) {
      alert(`Added ${item.name} to the cart!`);
    }
  }, [item]);
 
  function handleAddClick() {
    addToCart(item);
  }
 
  function handleCheckoutClick() {
    addToCart(item);
    goToCheckout();
  }
}

Good Code:

function ShoppingCart({ item, addToCart }) {
  function addItem() {
    addToCart(item);
    alert(`Added ${item.name} to the cart!`);
  }
 
  function handleAddClick() {
    addItem();
  }
 
  function handleCheckoutClick() {
    addItem();
    goToCheckout();
  }
}

5. Notifying parent component about child's state changes

Bad Code:

function Switch({ onToggle }) {
  const [isActive, setIsActive] = useState(false);
 
  useEffect(() => {
    onToggle(isActive);
  }, [isActive, onToggle]);
 
  function handleSwitch() {
    setIsActive(!isActive);
  }
 
  function handleDragEnd(e) {
    setIsActive(isNearRightEdge(e));
  }
}

Lifting state up allows the parent component to control the switch state more effectively.

Good Code:

function Switch({ isActive, onToggle }) {
  function handleSwitch() {
    onToggle(!isActive);
  }
 
  function handleDragEnd(e) {
    onToggle(isNearRightEdge(e));
  }
}

6. Passing data to parent

When child components update the state of their parent components in Effects, the data flow becomes very difficult to trace. Since both the child and the parent need the same data, let the parent component fetch that data, and pass it down to the child instead.

Bad Code:

function ParentComponent() {
  const [info, setInfo] = useState(null);
  return <ChildComponent onFetch={setInfo} />;
}
 
function ChildComponent({ onFetch }) {
  const info = useApiData();
 
  useEffect(() => {
    if (info) {
      onFetch(info);
    }
  }, [onFetch, info]);
}

Good Code:

function ParentComponent() {
  const info = useApiData();
  return <ChildComponent info={info} />;
}
 
function ChildComponent({ info }) {
  // Use info directly
}

7. Data fetching

It is quite common to write a data fetching Effect like this.

Bad Code:

function DataList({ searchTerm }) {
  const [data, setData] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
 
  useEffect(() => {
    fetchData(searchTerm, currentPage).then((response) => {
      setData(response);
    });
  }, [searchTerm, currentPage]);
 
  function loadNextPage() {
    setCurrentPage(currentPage + 1);
  }
}

Adding a cleanup function avoids race conditions when fetching d

Good Code:

function DataList({ searchTerm }) {
  const [data, setData] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
 
  useEffect(() => {
    let isCancelled = false;
    fetchData(searchTerm, currentPage).then((response) => {
      if (!isCancelled) {
        setData(response);
      }
    });
    return () => {
      isCancelled = true;
    };
  }, [searchTerm, currentPage]);
 
  function loadNextPage() {
    setCurrentPage(currentPage + 1);
  }
}

Not using useEffect for everything will save me time from debugging the side effects of it.