Ben Honeywill
Feb 21, 2020 ⋅ 5 min read

Developing responsive layouts with React Hooks

Ben Honeywill UK-based frontend engineer building cool web software with @LushEngineering. Teacher, learner, and lover of JavaScript.

Recent posts:

Using Aws Lambda And Aws Cloudfront To Optimize Image Handling

Using AWS Lambda and CloudFront to optimize image handling

Leverage services like AWS Lambda, CloudFront, and S3 to handle images more effectively, optimizing performance and providing a better UX.

Nitish Sharma
Apr 12, 2024 ⋅ 12 min read
Building Web-Based Terminal Components With Termino.js

Building web-based terminal components with Termino.js

Explore Termino.js, an open source library for integrating web-based terminals into applications, in this introduction article.

Chibuike Nwachukwu
Apr 11, 2024 ⋅ 6 min read
How To Build A Custom Gpt: Step By Step Tutorial

How to build a custom GPT: Step-by-step tutorial

Let’s see why and how to build custom GPTs — personalized versions of ChatGPT that act as custom chatbots to serve a specific purpose.

Peter Aideloje
Apr 10, 2024 ⋅ 7 min read
Jest Adoption Guide: Overview, Examples, And Alternatives

Jest adoption guide: Overview, examples, and alternatives

Jest is feature-rich testing framework that comes with several in-built features that make it powerful and easy to use.

Ibiyemi Adewakun
Apr 9, 2024 ⋅ 12 min read
View all posts

21 Replies to "Developing responsive layouts with React Hooks"

  1. There is no clue to emulate media queries via using React Hooks. Better thing that you can do using this method is kind of container queries emulation — when you watching for width used by component, not window width.

  2. Hey Omar, I completely agree with you, CSS should almost always be the first tool you reach for to do responsive design.

    Saying that, there are reasons to use both, in the right circumstances.

    Firstly you need to ask if what you are changing on mobile vs desktop is a style change, or if it is application logic. If it is application logic and you need to do any kind of calculation or complicated conditional rendering using the viewport size then putting that logic in your JavaScript code rather than writing complex media queries in CSS could be beneficial.

    Secondly, and more importantly, hiding content with CSS doesn’t actually remove it from the virtual DOM. This means that React is still going to be re-rendering components that aren’t actually being used, so if you are showing drastically different components for different viewport sizes it could actually improve your performance to not render those components at all!

    Hope that helps.

  3. Hey Alexander, Yeah that’s a good idea. You could achieve what you are describing by using the React.useRef Hook. You could then pass the ref into a custom hook of your own design that returns the dimensions of your component based on that ref.

  4. It’s one reusable component, once that code is in place, it’s a simple 2 line solution that allows you to render completely different content. Something that you can’t do nice and cleanly with CSS media queries.

  5. Really great post! It’s a much cleaner solution than writing tons of media queries and `display: none` declarations when dealing with complex content layout shifts between mobile & desktop views 🙂

    Everything works perfectly in my development environment, but I’m running into an issue implementing this in production with Gatsby because of how Gatsby is handling SSR.

    I am obviously getting a `window` is undefined error, which is expected. So I created a simple utility function:

    `export const isSSR = typeof window === ‘undefined’;`

    And used it like:

    const [width, setWidth] = React.useState(isSSR ? 0 : window.innerWidth);
    const [height, setHeight] = React.useState(isSSR ? 0 : window.innerHeight);


    This gives us a fallback value of 0 on server side and allows me to successfully execute a `gatsby build` command. The issue, however, is that once the content is served up in production, I am not getting the correct component returned.

    It seems that SSR is still somehow returning true on the client side and preventing values from being set/correct components from being rendered until the window is resized enough to manually trigger a component swap, then everything works as intended when resizing.

    When I refresh the page, I end up with broken components again.

    Any guidance here would be much appreciated. I imagine a lot of people are dealing with when implementing with Gatsby or NextJS.

    Thank you so much!!

  6. As a follow-up here… after some digging, it seems that this approach is not well suited for any SSR solution, unless there are workarounds that I am unaware of.

    The issue is how client-side re-hydration happens. Since HTML is server-generated for Gatsby and server-rendered for NextJS, there obviously can be no access to `window`. In these cases, we are having to generate a default view of either mobile or desktop (depending on how your responsive rendering component is configured) and then have the component rehydrate client-side once `useEffect` fires. When this happens there is an obvious flash between the default server-rendered view, and the properly rendered view based on `window.innerWidth` in the client.

    We are also losing the SEO and performance benefits of SSR in this case, because the entire component is essential just rendering on the client side :/

  7. CSS media queries are fine when you have the same component for every screen size but just with minor changes to it (width, padding, etc) but what do you do when you have a component for a screen size and another component completely different for another screen size? For example, you have a full navbar with one link besides the other for desktop and an hamburger menu for mobile. In this case you need a conditional render, css media queries will not do the job.

  8. Can you explain how the cleanup happens in the useEffect? I see it returns the function, but how is it called?

  9. Hi Chris, did you find some workaround for this issue? im having the same problem i cant find more info to this day.
    Thank you!

  10. Ben, thanks for the great complete article! And Chris, thank you for the great note about possible issues with Gatsby, SSR and SEO.

    I’m happy to say that I’ve had success getting it working in my Gatsby project, including the built/rehydrated
    version! Achieved it using the browser-monads package to detect if window variable exists (https://www.npmjs.com/package/browser-monads)

    I ended up adding the ViewportProvider inside my Layout component. This comes with a caveat that useViewport() hook can’t be called directly from the Page component (since Layout is a child of Page). However ViewportContext.Consumer does work directly in the Page if it’s nested under the Layout component; and the useViewport() hook does work in any of child of the Page component.

    Our team’s use case is primarily swapping out fairly similar content, but for SEO I think will be to be careful to always render the SEO-preferable content by default (which I think is generally a good habit anyway.)

  11. I think one way to improve performance would be if instead of having the hook return the width, you could have the hook return whether or not the breakpoint was hit. With the current setup the state is being updated every time the width changes, but you don’t actually need that much granularity, you just need to know if the breakpoint was hit. The width could be stored in a ref instead that doesn’t trigger an update and the hook could be modified to be something like `useBreakpoint(width)` with a boolean breakpoint state instead.

  12. https://codesandbox.io/s/quizzical-sun-tqgvgt
    In a real application, the logic is divided among several (many) components, so in order to be able to use the technique you described, you need to separate the code into a separate component, which I did. I also rewrote for the latest version of react. please see the working code , and if you can improve. because I’m just a beginner.
    By the way, the handling of {children} has changed in React 18

    Translated with http://www.DeepL.com/Translator (free version)

Leave a Reply