Do setter methods trigger a DOM rerender in React?

531 views Asked by At

I'm learning React at the minute by doing a basic calendar app, which relies heavily on using the Date() object. I'm trying to display the selected month, which is changed by clicking on a couple of font awesome arrow icons.

Using the context API and useState, i'm updating the date with the "setDateObject" callback, which uses the setter function "setMonth()" of the Date object.

Just doing some console logs shows me that the date is successfully being updated in state, but the date displayed in the < p> tag is not being rerendered. I have tried switching the date object out with a hard-typed object and manually incrementing the month within the "incrementMonth" method, which does trigger a rerender.

So either I am misunderstanding something, or using a setter function to update the date object just isn't triggering a rerender. Is this something anybody has experience with?

Thanks in advance!

Object

import React, { useContext } from "react"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"

// Styling
import style from "../styles/Calendar.css"

// Assets
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons"
import { faArrowRight } from "@fortawesome/free-solid-svg-icons"

// Contexts
import { DateContext } from "./context/DateContext.js"

const Calendar = () =>  {
    const [dateObj, setDateObj] = useContext(DateContext)
    const monthArray = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

    const incrementMonth = () => {
        setDateObj(prevDate => {
            let newDate = prevDate
            newDate.setMonth(prevDate.getMonth() + 1)
            return newDate
        })
    }
    
    const decrementMonth = () => {
        setDateObj(prevDate => {
            let newDate = prevDate
            newDate.setMonth(prevDate.getMonth() - 1)
            return newDate
        })
    }
    
    return(
        <div style={style} className="CalendarWrapper">
            <div className="MonthSelector">
                <FontAwesomeIcon icon={faArrowLeft} onClick={decrementMonth} className="hoverArrow"/>
                <p>{ `${ monthArray[dateObj.getMonth()] } ${ dateObj.getFullYear() }` }</p>
                <FontAwesomeIcon icon={faArrowRight} onClick={incrementMonth} className="hoverArrow"/>
            </div>
        </div>
    )
}

export default Calendar

Context

import React, { useState, createContext } from "react"

export const DateContext = createContext(null)

export const DateProvider = props => {
    const [dateObj, setDateObj] = useState(new Date())
    return(
        <DateContext.Provider value={[dateObj, setDateObj]}>
            {props.children}
        </DateContext.Provider>
    )
}
1

There are 1 answers

0
backtick On BEST ANSWER

React tries to be smart about re-rendering. If the new state and the old state are references to the same value, it doesn't re-render.

You're actually calling setDateObj with the same date object, even though you've changed its internal data. In other words, if newDate === prevDate, it won't re-render. You can avoid this by copying the date thusly:

const newDate = new Date(prevDate);