0

Enthusiast trying to learn nextjs.

I want to be able to have a component that shows window width and height with a tailwindcss breakpoint shown on the button when clicked, as a stepping stone to a continuous display. I decided that clicking a button to show the current window width and height would be easier than setting up an event listener to continuously change the displayed values. I also wanted to show the current "breakpoint" value according to the standard TailwindCSS breakpoints.

The versions of the packages used are shown here.

  "dependencies": {
    "axios": "^1.1.2",
    "daisyui": "^2.31.0",
    "gray-matter": "^4.0.3",
    "marked": "^4.1.1",
    "next": "12.3.1",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-icons": "^4.4.0"
  },
  "devDependencies": {
    "@tailwindcss/typography": "^0.5.7",
    "autoprefixer": "^10.4.12",
    "eslint": "^8.25.0",
    "eslint-config-next": "12.3.1",
    "postcss": "^8.4.17",
    "tailwindcss": "^3.1.8"
  }

and the component WidthHeightButton.js is here:

export default function WidthHeightButton() {


const [widthParam, setWidth] = useState(55)
const [heightParam, setHeight] = useState(55)
const [tailwindBreakpoint, setTailwindBreakpoint] = useState("empty")

console.log("[WidthHeightButton] check array used to destructure setWidth is a function:", typeof setWidth)

function handleClick() {
    console.log("[WidthHeightButton] width x height:", window.innerWidth, "x", window.innerHeight)

    setWidth(window.innerWidth)
    setHeight(window.innerHeight)

    console.log("typeof widthParam:", typeof widthParam, widthParam)

    let breakpoint = ""

    if (widthParam >= 1536) {
        breakpoint = "2xl"
    } else if (widthParam >= 1280) {
        breakpoint = "xl"
    } else if (widthParam >= 1024) {
        breakpoint ="lg"
    } else if (widthParam >= 768) {
        breakpoint = "md"
    } else if (widthParam >= 640) {
        breakpoint = "sm"
    } else {
        breakpoint = "default"
    }

    setTailwindBreakpoint(breakpoint)

}

return (
    <button onClick={handleClick} className="my-6 btn btn-secondary">
        width x height: {widthParam} x {heightParam} {tailwindBreakpoint}
    </button>
)

}

I then invoke the component with It shows WIDTH x HEIGHT: 55 X 55 EMPTY (my default values)

I click the button.

It shows WIDTH x HEIGHT: 677 X 918 DEFAULT (width and height are correct, but not the breakpoint.)

I click the button again.

It shows WIDTH x HEIGHT: 677 X 918 SM (Correct)

Why does it take two clicks?

In response to all the great pointers to this gap in my understanding, I wanted to see if changing the order of operations might get a better result:

import { useState } from "react"

export default function WidthHeightButton() {

    const [widthParam, setWidth] = useState(55)
    const [heightParam, setHeight] = useState(55)
    const [tailwindBreakpoint, setTailwindBreakpoint] = useState("empty")

    console.log("[AnotherButton] check array used to destructure setWidth is a function:", typeof setWidth)

    function handleClick() {
        console.log("[AnotherButton] width x height:", window.innerWidth, "x", window.innerHeight)

        let breakpoint = ""

        if (window.innerWidth >= 1536) {
            breakpoint = "2xl"
        } else if (window.innerWidth >= 1280) {
            breakpoint = "xl"
        } else if (window.innerWidth >= 1024) {
            breakpoint ="lg"
        } else if (window.innerWidth >= 768) {
            breakpoint = "md"
        } else if (window.innerWidth >= 640) {
            breakpoint = "sm"
        } else {
            breakpoint = "default"
        }
        setWidth(window.innerWidth)
        setHeight(window.innerHeight)
        setTailwindBreakpoint(breakpoint)
    
        console.log("typeof widthParam:", typeof widthParam, widthParam)

        



    }
    
    return (
        <button onClick={handleClick} className="my-6 btn btn-secondary">
            width x height: {widthParam} x {heightParam} {tailwindBreakpoint}
        </button>
    )
}

Inspired by your answers, I wanted to see if I could stop the use of one state variable (widthParam) being used to determine another state variable (tailwindBreakpoint). This is one solution to the problem I raised, and results in the correct display with one button click. I have to focus on the great responses given here. I'm not sure if this response is a legitimate answer to my question, but maybe it is a bite sized step in the right direction.

dmr
  • 1
  • 3
  • I reviewed this answer and some others. I believe there have been some fundamental changes with React 18 which make it very difficult to relate these older answers to the current system. In learning about this, I should have made that clearer from my experience. If it does turn out to be relevant, it would be great to see the few lines of code that I need to make it work. – dmr Oct 11 '22 at 20:40
  • "I believe there have been some fundamental changes with React 18" yes indeed, but state values being reflected on the component's next render is not one of them, this remains the same. – ivanatias Oct 11 '22 at 20:51
  • In my example, three state values are changed by the clickHandler, namely widthParam, heightParam and tailwindBreakpoint, but only two of them are rendered on the first button click, and the last one on the second button click. Is this because each one is async without a definite timescale to complete....? Is there something I should do differently, or have I just made a good opportunity to really understand how this all works? – dmr Oct 11 '22 at 21:02
  • When you click your button and you set your `width` and `height` states, a re-render is triggered, what you see rendered on the screen after that is `width` and `height` values on that next render. However, on that first click, what `handleClick` and the rest of your component "sees" as state values are the default state values, on the next render the `handleClick` function is now "aware" of the new state values, therefore, setting correctly your breakpoint state. Hope this is clear enough. – ivanatias Oct 11 '22 at 21:11
  • Even more simple: on the first click, the breakpoint state is being set according to the default value of `width` which have not been updated on that render (breakpoint state depends on this state). – ivanatias Oct 11 '22 at 21:21

2 Answers2

1

When you run setWidth, width will be changed to a new value in the next render cycle. Remember that consts can't change.

function handleClick() {
    console.log("[AnotherButton] width x height:", window.innerWidth, "x", window.innerHeight)

    setWidth(window.innerWidth)
    setHeight(window.innerHeight)

    let breakpoint = ""

    if (window.innerWidth>= 1536) {
        breakpoint = "2xl"
    } else if (window.innerWidth >= 1280) {
        breakpoint = "xl"
    } else if (window.innerWidth>= 1024) {
        breakpoint ="lg"
    } else if (window.innerWidth>= 768) {
        breakpoint = "md"
    } else if (window.innerWidth>= 640) {
        breakpoint = "sm"
    } else {
        breakpoint = "default"
    }

    setTailwindBreakpoint(breakpoint)

}
Konrad
  • 21,590
  • 4
  • 28
  • 64
0

You can call the handleClick function inside useEffect with window.innerWidth and window.innerHeight as dependencies instead of calling it on Button click.

Or

Provide initial values of width and height as window.innerWidth and window.innerHeight. and use them as dependencies.