CSS style is not correctly being applied when conditional render in React

烈酒焚心 提交于 2021-01-29 10:34:17

问题


I need to conditionally render components based on screen size.

I use nextjs and getInitialProps for data fetching, the page is server-side rendered. I want to detect device screen size on the client-side, so I implement a customized hook to do it.

useWindowSize.js

import { useEffect, useState } from 'react';

export default function useWindowSize() {
    const [windowSize, setWindowSize] = useState({
        width: typeof window === 'undefined' ? 1200 : window.innerWidth, // default width 1200
    });

    useEffect(() => {
        // Handler to call on window resize
        function handleResize() {
            // Set window width/height to state
            setWindowSize({
                width: window.innerWidth,
                //height: window.innerHeight,
            });
        }

        // Add event listener
        window.addEventListener('resize', handleResize);

        // Call handler right away so state gets updated with initial window size
        handleResize();

        // Remove event listener on cleanup
        return () => window.removeEventListener('resize', handleResize);
    }, []); // Empty array ensures that effect is only run on mount

    return windowSize.width <= 600;
}

then I use this hook to detect window size and conditional render components

export default function IndexPage() {
  const isMobile = useWindowSize();

  if (typeof window !== "undefined") {
    // if you are running it on codesanbox, I don't know why log is not printed
    console.log("client side re-render");
  }

  return (
    <div>
      {isMobile ? (
        <div
          style={{
            color: "red",
            fontSize: 40
          }}
        >
          mobile
        </div>
      ) : (
        <div
          style={{
            color: "blue",
            fontSize: 20
          }}
        >
          desktop
        </div>
      )}
    </div>
  );
}

IndexPage.getInitialProps = () => {
  return {
    a: 1
  };
};

when I load the page on mobile browser, you will see

image

text mobile is applied wrong CSS style. video demo: https://share.getcloudapp.com/nOuk08L0

how to reproduce: https://codesandbox.io/s/thirsty-khayyam-npqpt

Can someone please help me out. Thank you in advance!


回答1:


This is an issue that is related to how React patch up DOM from SSR. When there is a mismatch between client-side and server-side rendering, React will only patch/sync the text context for the node. The DOM attribute will not be automatically updated. In your case, the SSR result has the desktop style because there is no window object, and client side has the mobile result. After the mismatch, React update the text node from 'desktop' to mobile but not the style attributes.

In my opinion, you can use two different approaches. You can use Media Query to style your component based on the screen width instead of the hook. If you are doing SSR, not SSG, you can use user agent req.headers["user-agent"] to detect the device your device is being viewed on.

For the first approach, you might need to render more DOM node you might need to. For the second approach, you won't be able to know the actual viewport size, which can cause visual issue. You might be able to combine both approach to produce a good viewing experience for your user.

Reference

https://github.com/facebook/react/issues/11128#issuecomment-334882514




回答2:


Thanks for @Andrew Zheng's detailed explanation! Today I learned.

I know that I can style the layout by using pure CSS media query, but my use case needs a variable like isMobile to

if (isMobile) {
    doSomethingOnlyOnMobileWeb();
} else {
    doSomethingOnlyForDesktopWeb();
}

So I combined two approaches you provided, and modify my hook this way:

export default function useWindowSize(userAgent) {
    let isMobile = Boolean(
        userAgent &&
            userAgent.match(
                /Android|BlackBerry|iPhone|iPod|Opera Mini|IEMobile|WPDesktop/i
            )
    );
    
    const [windowSize, setWindowSize] = useState({
        width: isServer
            ? isMobile
                ? BREAKPOINT_SMALL
                : BREAKPOINT_LARGE
            : window.innerWidth,
        
    });

    useEffect(() => {
        // Handler to call on window resize
        function handleResize() {
            // Set window width/height to state
            setWindowSize({
                width: window.innerWidth,
                //height: window.innerHeight,
            });
        }

        // Add event listener
        window.addEventListener('resize', handleResize);

        // Call handler right away so state gets updated with initial window size
        handleResize();

        // Remove event listener on cleanup
        return () => window.removeEventListener('resize', handleResize);
    }, []); // Empty array ensures that effect is only run on mount

    return windowSize.width <= BREAKPOINT_SMALL;
}

diff: passing user-agent string to useWindowSize for server-side detection and use window.innerWidth for client-side detection. There won't be a mismatch between server and client.



来源:https://stackoverflow.com/questions/63928337/css-style-is-not-correctly-being-applied-when-conditional-render-in-react

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!