reactjsdatereact-hooksrerender

window.innerwidth in useEffect dependency array is not calling the callback function


This component is supposed to display the day, date and month in the h2 element on the initial render. The useEffect inside of the function is intended to change the date to short form when the window.innerWidth is < 735px and back to long form when not. It is also intended to re-render the component when the date changes. The useEffect callback function gets called on first render and displays the date fully as expected depending on the date and innerWidth but not again when the date or the innerWidth change despite the fact that that the day, date, month and innerWidth are all included in the dependency array the date is checked every 2 seconds with the setInterval function.

My Code:

import { useState, useEffect } from "react"

let formattedDate
let currentDate = new Date

export default function LocalDate() {

  const [date, setDate] = useState("")
  
  useEffect(() => {
    const interval = setInterval(() => {
      currentDate = new Date
    }, 2000)
    console.log("called")
    if (window.innerWidth < 735) {
      formattedDate = currentDate.toLocaleDateString("en-US", { weekday: "short", day: "numeric", month: "short" })
    } else {
      formattedDate = currentDate.toLocaleDateString("en-US", { weekday: "long", day: "numeric", month: "long" })
    }

    let parts = formattedDate.split(" ")
    let formattedDateSwapped = `${parts[0]} ${parts[2]} ${parts[1]}`
    setDate(formattedDateSwapped)

    return clearInterval(interval)
  }, [currentDate.getDay(), currentDate.getDate(), currentDate.getMonth(), window.innerWidth])

  return (
    <h2 id="date">{date}</h2>
  )
}

I have looked over it countless times and googled similar issues and looked on many forums and can't find an answer. If anyone has any suggestions or solutions for my problem it would be very much appreciated. Please keep in mind I have only started learning react about a week ago. Thanks


Solution

  • The reason this isn't working is because the change in window width (specifically window) isn't create a change in the useEffect dependency array. When it comes to listening to the window, I found that simply adding and removing event listeners (e.g. addEventListener/removeEventListener) is good way to go...

    You're not relying on state to listen to and change a defined value of the page width.
    It's also good practice (when working with timeouts and intervals) to be able to control them (i.e. clear them) when the component changes or unmounts (that way you don't get memory leaks).
    I make a habit of defining them like this (and below) as it's quite declarative and clear as to what you're doing with a timeout. Using refs also doesn't cause a re-render when changing the timeout.current value.
    The React Docs also mention this in "When to use refs".

    const { useState, useEffect, useRef } = React
    
    const shortDate = { weekday: "short", day: "numeric", month: "short" }
    const longDate = { weekday: "long", day: "numeric", month: "long" }
    
    
    const formatDate = () => {
      const currentDate = new Date();
      return window.innerWidth < 735
        ? currentDate.toLocaleDateString("en-US", shortDate)
        : currentDate.toLocaleDateString("en-US", longDate);
    }
    
    function LocalDate () {
    
      const [formattedDate, setFormattedDate] = useState(formatDate());
      const intervalRef = useRef(null)
      
      useEffect(() => {
        const getNewDate = () => {
            setFormattedDate(formatDate());
            console.log("called");
          }
        
        const getNewDateInterval = () => {
          if (intervalRef.current) {
            console.log("cleared 1")
            clearInterval(intervalRef.current)
          }
          intervalRef.current = setInterval(getNewDate, 2000)
        }
        
        // call once to start off
        getNewDateInterval()
        // add listener
        window.addEventListener("resize", getNewDate);
    
        // clean up
        return () => {
          if (intervalRef.current) {
            console.log("cleared 2")
            clearInterval(intervalRef.current)
          }
          window.removeEventListener("resize", getNewDate);
        }
      }, [])
    
      return (
        <h2 id="date">{formattedDate}</h2>
      )
    }
    
    // Render it
    ReactDOM.createRoot(
        document.getElementById("root")
    ).render(
        <LocalDate />
    );
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>