Giter Site home page Giter Site logo

react-sizes's Introduction

react-sizes

npm npm GitHub issues GitHub stars Twitter

Install

yarn add react-sizes
npm install react-sizes

What and why

React Sizes is a higher-order component with strong performance that transforms window sizes (width and height) into props.
You can check inside your component, for example, if user's window is less than 480 pixels of width, and add a custom content.

It can be very powerful for when you need to display different content for mobile and desktop. But it's not limited to this case. Just use that at your needs.

Usage

With class component.

import React, { Component } from 'react'
import withSizes from 'react-sizes'

class MyComponent extends Component {
  render() {
    return <div>{this.props.isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
  }
}

const mapSizesToProps = ({ width }) => ({
  isMobile: width < 480,
})

export default withSizes(mapSizesToProps)(MyComponent)

You can play with this example here.

As decorator.

import React from 'react'
import withSizes from 'react-sizes'

@withSizes(({ width }) => ({ isMobile: width < 480 }))
class MyComponent extends Component {
  render() {
    return <div>{this.props.isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
  }
}

export default MyComponent

Interoperate with other libraries.

import React from 'react'
import withSizes from 'react-sizes'
import { withState, compose } from 'recompose'

const enhancer = compose(
  withState('counter', 'setCounter', 0),
  withSizes(({ width }) => ({ isMobile: width < 480 }))
)

const MyComponent = enhancer(({ isMobile, counter, setCounter }) => (
  <div>
    <div>
      Count: {counter}{' '}
      <button onClick={() => setCounter(n => n + 1)}>Increment</button>
    </div>
    <div>{isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
  </div>
))

export default MyComponent

With functional component.

import React from 'react'
import withSizes from 'react-sizes'

const MyComponent = ({ isMobile }) => (
  <div>{isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
)

const mapSizesToProps = ({ width }) => ({
  isMobile: width < 480,
})

export default withSizes(mapSizesToProps)(MyComponent)

Mess with props.

(Added in 0.1.0)

import React from 'react'
import withSizes from 'react-sizes'

const MyComponent = ({ isMobile }) => (
  <div>{isMobile ? 'Is Mobile' : 'Is Not Mobile'}</div>
)

const mapSizesToProps = ({ width }, { mobileBreakpoint }) => ({
  isMobile: width < mobileBreakpoint,
})

export default withSizes(mapSizesToProps)(MyComponent)

then:

<MyComponent mobileBreakpoint={480} />
<MyComponent mobileBreakpoint={400} />
<MyComponent mobileBreakpoint={600} />

With presets selectors.

- const mapSizesToProps = ({ width }) => ({
-   isMobile: width < 480,
- });

+ const mapSizesToProps = sizes => ({
+  isMobile: withSizes.isMobile(sizes),
+ });

Presets Selectors

You can check all our presets selectors at our main code src/withSizes.js.

withSizes.isMobile = ({ width }) => width < 480
withSizes.isTablet = ({ width }) => width >= 480 && width < 1024
withSizes.isDesktop = ({ width }) => width >= 1024

withSizes.isGtMobile = sizes => !withSizes.isMobile(sizes)
withSizes.isGtTablet = sizes => withSizes.isDesktop(sizes)

withSizes.isStTablet = sizes => withSizes.isMobile(sizes)
withSizes.isStDesktop = sizes => !withSizes.isStDesktop(sizes)

withSizes.isTabletAndGreater = sizes => !withSizes.isMobile(sizes)
withSizes.isTabletAndSmaller = sizes => !withSizes.isStDesktop(sizes)

If it don't fit to your needs, you can create your own selectors.

// utils/sizes/selectors.js
export const isntDesktop = ({ width }) => width < 1024
export const backgroundColor = ({ width }) => (width < 480 ? 'red' : 'green')

// your component
import { isntDesktop, backgroundColor } from 'utils/sizes/selectors'

const mapSizesToProps = sizes => ({
  canDisplayMobileFeature: isntDesktop(sizes),
  backgroundColor: backgroundColor(sizes),
})

sizes argument is an object with width and height properties and represents DOM window width and height.

Guide

mapSizesToProps(sizes)

sizes argument is an object with width and height of DOM window.

const mapSizesToProps = sizes => {
  console.log(sizes) // { width: 1200, height: 720 } (example)
}

In pratice, it is a callback that return props that will injected into your Component.

const mapSizesToProps = function(sizes) {
  const props = {
    backgroundColor: sizes.width < 700 ? 'red' : 'green',
  }

  return props
}

But you can simplify this to stay practical and elegant.

const mapSizesToProps = ({ width }) => ({
  backgroundColor: width < 700 ? 'red' : 'green',
})

Server Side Rendering

Since React Sizes rely on window to computate sizes, we can't computate the values in server enviroment. To try to get around this we can guess user viewport based on your user-agent, and pass values by a Context Provider.
But be careful, user-agent based detection is not a reliable solution. It's a workaround.

// Config can be created based on user-agent. See below
const config = { fallbackWidth: 360, fallbackHeight: 640 }

return (
  <SizesProvider config={config}>
    <App />
  </SizesProvider>
)

Example:

import MobileDetect from 'mobile-detect'
import Express from 'express'
import { SizesProvider } from 'react-sizes'
// All other imports

const getSizesFallback = userAgent => {
  const md = new MobileDetect(userAgent)

  if (!!md.mobile()) {
    return {
      fallbackWidth: 360,
      fallbackHeight: 640,
    }
  } else if (!!md.tablet()) {
    return {
      fallbackWidth: 768,
      fallbackHeight: 1024,
    }
  }

  return {
    fallbackWidth: 1280,
    fallbackHeight: 700,
  }
}

// Note: you don't need to use express, this is just an example
const app = new Express()
app.use((req, res) => {
  // ...
  const sizesConfig = getSizesFallback(req.headers['user-agent'])

  const App = (
    <AnotherProvider>
      <Router location={req.url}>
        <SizesProvider config={sizesConfig}>
          <Root />
        </SizesProvider>
      </Router>
    </AnotherProvider>
  )

  res.status(200)
  res.send(`<!doctype html>\n${ReactDOM.renderToString(<App />)}`)
  res.end()
})

app.listen(/* ... */)

Performance Notes

Shallow Compare

React Sizes do a shallow compare in props generated from mapSizesToProps (called propsToPass), so it will only rerender when they really change. If you create a deep data sctructure, this can generate false positives. In these cases, we recommend using immutable for a more reliable shallow compare result. Or just don't use deep data structures, if possible.

Contribute

You can help improving this project sending PRs and helping with issues.
Also you ping me at Twitter

react-sizes's People

Contributors

compuives avatar danielr18 avatar dependabot[bot] avatar diegosanz avatar felthy avatar haritonasty avatar impressivewebs avatar johansteffner avatar ncuillery avatar psixokot avatar renatorib 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  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  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  avatar  avatar  avatar  avatar  avatar  avatar

react-sizes's Issues

window.innerWidth/innerHeight are incorrect ways to determine available document sizes

I was using react-sizes to replace some media query stuff, and got some strange behaviors when zooming on iOS. e.g. it would eventually re-render with new sizes even though my device was the same. Like, if I scrolled the page, it would re-render.

The reason: window.innerWidth / innerHeight does not give the document width / height (which is what is useful for components in doing media-query-like stuff in JS), it gives the viewport width. However, pinching / zooming does not fire any sort of event, so basically react-sizes was changing props sort of at random, to values which broke layout.

Instead, this should be using document.documentElement.clientHeight/clientWidth, so that values do not change (eventually) after zooming into the document.

Bug: throttle can't be configured due to an error in the code

Hi,

great library. I was playing around with it and found a bug. You use lodash.throttle to slow down how often react rerenders when the browser changes. You made it "possible" to configure this value using the SizesProvider component. This however does not work. Because the logic is expecting this value on this.context.throttle. The SizesProvider however stores it in a dict with the key _ReactSizesConfig_.

The line ~57 in withSizes.js should be

throttledDispatchSizes = throttle(
      this.dispatchSizes,
      (this.context[contextKey] || {}).throttle || 200
    )

and not:

throttledDispatchSizes = throttle(
     this.dispatchSizes,
     this.context.throttle || 200
)

License

I noticed that there is a "License" field in the package.json, but there is no license file within the project. Do you mind adding one?

Question: why not media query listeners?

I was reading your medium post and wondered why you were not using media queries listeners instead of listening to the resize event?

var mediaQueryList = window.matchMedia("(max-width: 480px)");
mediaQueryList.addListener(function(mq) {
  console.log(mq.matches);
});

Would print true or false to the console once the media query match changes. No event throttling or debouncing needed as you only receive an event if the match changes (matches -> does not match and vice versa).

https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Testing_media_queries#Receiving_query_notifications

Maybe I am missing a down-side of this approach so I am interested in your take.

Thanks!

Pass component props to second argument in mapSizesToProps

use case example:

const SizedComponent = ({ isMobile }) => (
  <div>{isMobile ? 'Mobile!' : 'Desktop!'}</div>
);

const mapSizesToProps = ({ width }, { mobileBreakpoint }) => ({
  isMobile: width <= mobileBreakpoint,
});

export default sizes(mapSizesToProp)(SizedComponent);
<SizedComponent mobileBreakpoint={400} />;

Version 1.0.0 install not working

Hey! Thanks for the 1.0.0 update. Unfortunately I get a not-found error for react-sizes. It seems like the es and dist directories aren't getting published to NPM. I've installed from a personal fork for now (with the compiled responses checked in [๐Ÿ˜ฑ]) but if you could publish the compete build that would be awesome!

Static properties should be copied over

Hey,

Thanks a lot for this library!
It appears that you do not copy static methods as described here: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
It seems the convention is that all HOC should do that. The behaviour is somewhat unexpected otherwise.
Are you planning on adding it to the library?

In the meantime, I'm using this workaround:

const WithHoc = compose(
  withRouter,
  withSizes(mapSizesToProps),
  withStyles(styles)
)(WrappedComponent);

hoistNonReactStatic(WithHoc, WrappedComponent);

export default WithHoc;

Thanks!

Server Side Rendering

Does this project work when using SSR? I have seen #15 but it has not been merged yet.

It would be great to know if you are going to be supporting SSR soon. Thanks

The size is not specified on initial render

I really like using react-sizes, the API is really nice to use, but it seems to me that on the initial render, the components don't have access to the size returned by the mapSizesToProps. Since you initialise the event listeners in the componentDidMount, it's logical. However, couldn't you do this initialization in the componentWillMount (while checking of course that we're not server-side) ? This would avoid a flash of styles on first render, and would also let me correctly use the prop-types (here, if I have a a mobile: PropTypes.bool.isRequired, I'll get a warning).

If you're interested, I'd be happy to submit a PR :)

Custom hook version of withSizes?

Not sure if it aligns with what you want to do with this project but since a lot of people will be moving to hooks instead of using HOCs I was wondering if it would make sense to add a custom hook version of the withSizes HOC.

That way those who prefer hooks over HOCs (like me :) ) could use it?

Size props don't update when `resize` event is used in the child

React Sizes won't update the props when a child component is also using the resize event.

Example - A child component of the component that I am using withSizes on has a resize handler defined.

window.addEventListener('resize', this.resize);
// using this.resize to re-render (child) component in case window resizes

When the above is defined, react sizes fails to update the props passed to the parent component.

When I remove the resize handler from the child component and make no other changes, react-sizes works as expected. That's why I think there could be a bug here.

Let me know if you need any more details.

Prevent server/client mismatches on hydration

When doing SSR, we want the initial render to be the same on both client and server, which is a bit tricky due to not having the size on server side, but that's what fallback is for. The issue is that if your fallback is different than the actual size in the client, it can render something different and cause mismatches.

I added a flag in the context to force usage of fallback in my fork, which can be used in the apps to force rendering with fallback size in both server and client, and can be disabled after app hydrates, at which point it would render any differences caused by the size change.

You can see the implementation here: master...LiveCoinWatch:force-fallback-client

Are you interested in a PR for this?

Infinite loop

withSizes.isStDesktop = (sizes) => !withSizes.isStDesktop(sizes);

Use with Context API

How would you use this with a functional or class component that requires the use of the context API to retrieve data from elsewhere in your application. It seems to be completely breaking my component. From your docs it seems like formerly recompose and now Hooks might be the answer but I cant seem to get anything I try to work so wondering if you had any best practices or ideas. Also noticing on components with any form of prop propagation that the examples or docs you provide do not provide a way to use this.

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.