Giter VIP home page Giter VIP logo

use-viewport-sizes's Introduction

use-viewport-sizes

npm npm GitHub issues Github Workflow Status NPM

a tiny TS-compatible React hook which allows you to track visible window viewport size in your components w/ an optional debounce, throttle or custom memo function for updates for optimal rendering.

Installation

npm install -D use-viewport-sizes

Benefits

  • extremely lightweight and zero dependencies -- adds 2kb after gzip.
  • only one window.onresize handler used to subscribe to any changes in an unlimited number of components no matter the use-cases.
  • optional debounce to delay updates until user stops dragging their window for a moment; this can make expensive components with size-dependent calculations run much faster and your app feel smoother.
  • debouncing does not create new handlers or waste re-renders in your component; the results are also pooled from only one resize result.
  • optional hash function to update component subtree only at points you would like to.
  • supports lazy loaded components and SSR out of the box.

Usage

See it in Action

CodeSandbox IDE

Basic Use-case

registers dimension changes on every resize event immediately

import useViewportSizes from 'use-viewport-sizes'

function MyComponent(props) {
    const [vpWidth, vpHeight] = useViewportSizes();

    // ...renderLogic
}

Measure/Update only on one dimension

If passed options.dimension as w or h, only the viewport width or height will be measured and observed for updates. The only dimension returned in the return array value will be the width or height, according to what was passed.

import useViewportSizes from 'use-viewport-sizes';

function MyComponent(props) {
    const [vpHeight] = useViewportSizes({ dimension: 'h' });

    // ...renderLogic
}

With Throttling

If passed options.throttleTimeout, or options are entered as a Number, dimension changes are registered on a throttled basis e.g. with a maximum frequency.

This is useful for listening to expensive components such as data grids which may be too expensive to re-render during window resize dragging.

import useViewportSizes from 'use-viewport-sizes';

function MyExpensivelyRenderedComponent(props) {
    const [vpWidth, vpHeight] = useViewportSizes({ throttleTimeout: 1000 }); // 1s throttle

    // ...renderLogic
}

With Debouncing

If passed options.debounceTimeout, dimension changes are registered only when a user stops dragging/resizing the window for a specified number of miliseconds. This is an alternative behavior to throttleTimeout where it may be less important to update viewport the entire way that a user is resizing.

import useViewportSizes from 'use-viewport-sizes';

function MyExpensivelyRenderedComponent(props) {
    const [vpWidth, vpHeight] = useViewportSizes({ debounceTimeout: 1000 }); // 1s debounce

    // ...renderLogic
}

Only update vpW/vpH passed on specific conditions

If passed an options.hasher function, this will be used to calculate a hash that only updates the viewport when the calculation changes. In the example here, we are using it to detect when we have a breakpoint change which may change how a component is rendered if this is not fully possible or inconvenient via CSS @media queries. The hash will also be available as the 3rd value returned from the hook for convenience.

import useViewportSizes from 'use-viewport-sizes';

function getBreakpointHash({ vpW, vpH }) {
    if(vpW <= 240) { return 'xs' }
    if(vpW <= 320) { return 'sm' }
    else if(vpW <= 640) { return 'md' }
    else return 'lg';
}

function MyBreakpointBehaviorComponent() {
    const [vpW, vpH] = useViewportSizes({ hasher: getBreakpointHash });

    // do-something in render and add new update for vpW, 
    // vpH in this component's subtree only when a breakpoint
    // hash updates
}

Support

If you find any issues or would like to request something changed, please feel free to post an issue on Github.

Otherwise, if this was useful and you'd like to show your support, no donations necessary, but please consider checking out the repo and giving it a star (โญ).

License

use-viewport-sizes's People

Contributors

dependabot[bot] avatar jaxonwright avatar micah-redwood avatar rob2d avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

use-viewport-sizes's Issues

useLayoutEffect warning during SSR

Hey, first off, thanks for this really nifty hook, in particular the focus on performance and avoiding unnecessary re-renders!

I've been using it in a Vite project, and noticed I'm getting a warning during SSR b/c of useLayoutEffect

Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.

This warning can be avoided by explicitly using useEffect instead of useLayoutEffect during SSR. Here's one really simple implementation:
https://usehooks-ts.com/react-hook/use-isomorphic-layout-effect

There's no change in behavior (both hooks are skipped during SSR). It's just a way of telling the framework that you know that this won't run on the server and are ok with that.

Support individual dimension-tracking and new options interface

For the next release, will be providing a new interface which will be preferred over the previous parameter overloading. Example usage:

// default behavior
const [vpW, vpH] = useViewportSizes({ dimension: 'both' });
// with only one dimension observed, and `debounceTimeout` of `20` ms applied
const vpW = useViewportSizes({ dimension: 'w', debounceTimeout: 20 });

Input-option type:

    export type VPSizesHasher = (({ vpW: number, vpH: number }) => String);
    export type VPSizesOptions ={
        debounceTimeout?: number,
        throttleTimeout?: number,
        hasher?: VPSizesHasher,
        dimension?: 'w'|'h'|'both' = 'both'
    };

The interface will be backwards-compatible, but the default behavior for numbers as input will be to throttle eventually vs debounce as a more natural resize method.

Initially will just default to debounceTimeout behavior, but as a heads up to anyone using the library where it is going and why there may be a major semver version bump.

(input is welcome for anyone interested)

No SSR Support.

I looked through the source code and noticed that the window variable was global, so I was uncertain of how this would work with SSR. I setup up a codesandbox with NextJS and my assumptions were true: this fails in SSR as window is only defined client side.

I tried calling the cmponent when the parent component is mounted, but didn't work.

Here's what I tried: https://codesandbox.io/s/wnnx49708

I don't know what would be the consecuences of declaring window inside a useEffect to ensure the window variable exists, as useEffect is only called by the Client-side of the app.

I will be open for comment and sicussion, and maybe help, to add SSR suppport to this cool hook.

Add Typescript support

Would be great to get typescript inferences -- especially with upcoming throttle interface.

Probably best to go the route of typescript declaration file.

Ability to pass a function which may prevent re-render of React Tree on viewport changes

when using viewport sizes library, the implementation itself is pretty optimal for what it does. But there are some cases where we may want to prevent re-rendering or updating of returned value unless the viewport meets some criteria. This would then let React go without passing through subsequent hooks which need to check for a diff; e.g. useMemo. While it's not necessarily expensive, the browser is already doing a lot of work internally to reflow CSS.

Another very common use case would be for emulating media viewports e.g. max width or min width. Since React would not know the difference on a media query without passing a callback, this library can also be a really nice fill in replacement with much less overhead (which in large part it is meant for). This would also decrease the stutter or jankness when resizing some screens and when it's only relevant in certain breakpoints (for example when using Material UI @ material-ui.com breakpoints).

Currently considering to do this via a passed in callback function (not a hook), with a return value that is observed (e.g. internally via useMemo) that can detect change triggers to return new values. This way from the outside calling hook fn/component, it may or may not update.

Example usage as follows:

(callback declared once above fn component)

/**
  * when callback returns 'sm' or 'md', render one time
  */
const getVpBreakpoint= ({ vpW, vpH }) => {
     return vpW < 200 ? 'sm' : 'md';
};

(within component)

/**
  * will only recompute and return sizes when the observer
  * function changes; this ensures things do not re-render,
 * and the additional memorized property is available as
 * the third param now.
 *
 * the main convenience is that the callback is ran on
 *every viewport change, but the component is not forced 
*  to re-render! So this is much less boilerplate than
 * observing via callbacks.
  */
const [vpW, vpH, vpBreakpoint] = useVpSizes(shouldUpdateVpSizes);

README.md is out-of-date or incorrect for TypeScript

You cannot call useViewportSizes with no parameters, you get a compilation error.

Further, you cannot get properly typed parameters when you want only width and height, ignoring the triggerResize value. TypeScript assumes you're expecting the other result type which is dimension, triggerResize. Note, using the following causes eslint issues when you really don't want triggerResize and don't use it in subsequent source.

const [vpW, vpH, updateVpSizes] = useViewportSizes(); doSomething(vpW, vpH); // ignore updateVpSizes return; // Error will result because updateVpSizes is unused

Improve Examples and use new interface

Interface is just about done after a bit more testing. In order to post meaningful documentation, there should be newer examples on Codebase. Would not hurt to make them look a lot nicer.

One idea particularly is to render the viewport sizes themselves on observed changes on CodeSandbox in addition to filled colorings (less-busy looking). Obviously could not do this in both directions at once, but the new dimension option allows the examples to make more sense with component widths/heights changing themselves according to when updates are registered now.

Standard Import and Variable Function Causes Elements to Disappear on Basic Frontend

To replicate this bug, please follow these steps with the code sandbox:

Note: This codesandbox is just a basic flexible layout with CSS styles, media queries and basic react components and the bug happens within the Middle.js component.

Code: https://codesandbox.io/s/young-dawn-1x6jx?file=/src/App.js

Steps:

1.) Refresh the page - (The left yellow and right purple sides will randomly disappear after page refresh)
2.) Refresh the page again - (The left yellow and right purple sides will not come back)
3.) Adjust the size of the viewport - (The left yellow and right purple sides will randomly come back)
4.) Remove lines 3,5,6 from file: Middle.js - (The bug stops and it works normally)

What's wrong?: The left yellow and right purple sides should not disappear on refresh.

I have really no idea what's going wrong technically, but I think that this library is conflicting with the media queries or material-ui.

Add Throttle Support and use as default input behavior

Hey, I love this hook, it does just the one thing it's supposed to!

I'd love to throttle the function rather than debounce, would be cool to see the changes while resizing. Now it requires the user to stop.

Would you be willing to put that in if I'd open a PR?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.