• Stars
    star
    183
  • Rank 202,712 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 8 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Provide props to your React Components based on their Width and/or Height.

Provide props to your React Components based on their Width and/or Height.

Travis npm MIT License Codecov

import componentQueries from 'react-component-queries'

function MyComponent({ mode }) {
  return (
    <div>
      { mode === 'wide'
        ? <WideVariant />
        : <NarrowVariant /> }
    </div>
  )
}

componentQueries(
  ({ width }) => ({ mode: width < 768 ? 'narrow' : 'wide' }),
)(MyComponent);
  • Responsive Components!
  • A useful abstraction on the bare metal react-sizeme component.
  • Easy to use.
  • Extensive browser support.
  • Supports any Component type, i.e. stateless/class.
  • Works with React 0.14.x and 15.x.x.
  • 1.84KB gzipped standalone, even smaller if bundled into your own project.

TOCS

Overview

react-component-queries is a useful abstraction of the react-sizeme library. It allows you to define queries against the dimensions of your Component in order to produce custom props for your Component.

Any time the dimensions of your rendered Component changes the queries will automatically be run again.

The queries themselves are super simple functions that accept a size argument. You can implement any logic you like within the query functions but they must return an object containing the props you would like to assign to your Component.

For example:

function isSquareQuery(size) {
  return {
    isSquare: size.width === size.height
  };
}

It's great to be able to define your queries as functions as this gives you an opportunity to create and share queries across your components.

Once you have configured your queries then pass them to ComponentQueries and wrap your component, like so:

import componentQueries from 'react-component-queries';
...
componentQueries(query1, query2)(MyComponent)

You can provide as many queries as you like, their results will be merged and passed to your component.

Of course you can provide your queries inline too. Below we are using the ES2015 destructuring and anonymous function syntax:

componentQueries(
  // This query emulates a "breakpoint" type of property
  ({ width }) => {
    if (width <= 330) return { breakpoint: 'small' };
    if (width > 330 && width <=960) return { breakpoint: 'medium' };
    return { breakpoint: 'large' };
  },
  // You can have multiple queries, and the props that are returned can
  // be of any type.  Boolean's are often useful.
  ({ width }) => ({ isMassive: width > 1000000 })
)(MyComponent);

The above example will result in a breakpoint and an isMassive prop being passed to your component.

Why use this instead of react-sizeme?

react-sizeme is great, however, it suffers with a couple of problems in my opinion:

  1. It is raw in that it provides you with the actual dimensions of your component and then requires to execute logic within your component to establish the desired behaviour of your component. This can be a bit tedious and polute your component with a lot of if-else statements.
  2. It is possible that your component may gets spammed with updated size props. This is because any time your component changes in size react-sizeme will kick in.

react-component-queries was built to solve these problems. It solves problem 1 by moving the dimension based logic out of your component. It then solves problem 2 by ensuring that your component will only be called for re-render if any of the prop values change. That saves you some error prone boilerplate.

This allows you to deal with "simpler" props, for example; a boolean flag indicating if the component is square, an enum representing it's size ('small'|'medium'|'large'), a className, or a style object. Whatever you feel is most appropriate for your use case.

So, to recap, some of the benefits of using this abstraction are:

  • Simplify your components by moving the dimension logic away from them, which in turn is easier to test in isolation.
  • shouldComponentUpdate is implemented on your behalf.
  • The query functions themselves can be formed into a reusable library of queries for all your components.

I am not trying to take away from react-sizeme, but I want to highlight that it's a bit more of a low level HOC, and if you want to use it you should be aware of the problems above and consider using your own abstraction or this one.

Install

There is a peer-dependency on react-sizeme, so run the following command to install both libraries:

npm install react-sizeme react-component-queries --save

Demo

See it in action!

API

react-component-queries exports a single function to be used as an HOC around your existing components. This function supports two modes of usage: simple and configured.

Simple: componentQueries(queries)

Wraps your component with the given component queries using the default configuration options. You can provide either an array containing queries, or multiple arguments with each argument being a query function.

e.g.

componentQueries([
  function (size, props) { return { foo: 'bar' }; },
  function (size, props) { return { bob: true }; }
])(MyComponent)

or

componentQueries(
  function (size, props) { return { foo: 'bar' }; },
  function (size, props) { return { bob: true }; }
)(MyComponent)

Arguments

  • query(size, [ownProps]) : props (Function): A query function which can be provided as a set of arguments, or can be contained within an array containing one or more queries.
    • size (Object): Contains the current dimensions of your wrapped component. As the default configuration is being used, it will only contain th e width dimension.
      • width (Number): The current width of your component.
    • [ownProps] (Object): The additional props which have been provided to your wrapped component.

Configured: componentQueries(config)

Wraps your component with the given component queries and uses the provided configuration customisations. You must provide an object containing a queries property as well as a config property.

e.g.

componentQueries({
  queries: [
    function (size, props) { return { foo: 'bar' }; },
    function (size, props) { return { bob: true }; }
  ],
  config: {
    monitorWidth: true,
    monitorHeight: false,
    refreshRate: 16,
    pure: true
  }
})(MyComponent)

Arguments

  • config (Object): An object containing the queries and configuration.
    • queries (Array): An array of query functions:
      • query(size, [ownProps]) : props (Function): A query function which can be provided as a set of arguments, or can be contained within an array containing one or more queries.
        • size (Object): Contains the current dimensions of your wrapped component.
          • [width] (Number): Will only be provided if the monitorWidth configuration option is set to true. The current width of your component.
          • [height] (Number): Will only be provided if the monitorHeight configuration option is set to true. The current height of your component.
        • [ownProps] (Object): The additional props which have been provided to your wrapped component.
    • [config] (Object): Custom configuration.
      • [monitorWidth] (Boolean): If true then the width of your component will be tracked and provided within the size argument to your query functions. Defaults to true.
      • [monitorHeight] (Boolean): If true then the height of your component will be tracked and provided within the size argument to your query functions. Defaults to false.
      • [refreshRate] (Number): The maximum frequency, in milliseconds, at which size changes should be recalculated when changes in your Component's rendered size are being detected. This must not be set to lower than 16. Defaults to 16.
      • [noPlaceholder] (Boolean): By default we render a "placeholder" component initially so we can try and "prefetch" the expected size for your component. This is to avoid any unnecessary deep tree renders. If you feel this is not an issue for your component case and you would like to get an eager render of your component then disable the placeholder using this config option. Defaults to false.
      • [pure] (Boolean): Indicates if your component should be considered "pure", i.e. it should only be rerendered if the result of your query functions change, or if new props are provided to the wrapped component. If you set it to false then the wrapped component will render every time the size changes, even if it doesn't result in new query provided props. Defaults to true.
      • [conflictResolver(prev, current, key) : Any] (Function): A custom function to use in order to resolve prop conflicts when two or more query functions return a prop with the same key. This gives you an opportunity to do custom resolution for special prop types, e.g. className where you could instead concat the conflicted values. The default implementation will return the value from the last query function provided in the query array. Please read the respective section further down in the readme for more info and examples of this.
        • prev (Any): The value of the conflicted prop provided by the previously executed query function.
        • current (Any): The value of the conflicted prop provided by the most recently executed query function.
        • key (Any): The name of the prop which is in conflict.

Examples

Below are a few super simple examples highlighting the usage and capabilities of the library. They are using the ES6 syntax described above to define the queries.

Example 1: Queries on your Component's width

By default the ComponentQueries higher order component only operates on width. This is a design decision as in most cases we only wish to query against width, therefore we ignore height changes to minimize any potential DOM spamming. If you would like to operate on height too then please see Example 2.

import componentQueries from 'react-component-queries';

class MyComponent extends Component {
  render() {
    return (
      <div>
        {/* We recieve the following props from our queries */}
        I am at {this.props.scale} scale.
      </div>
    );
  }
}

export default componentQueries(
  // Provide as many query functions as you need.
  ({ width }) => {
    if (width <= 330) return { breakpoint: 'small' };
    if (width > 330 && width <=960) return { breakpoint: 'medium' };
    return { breakpoint: 'large' };
  }
)(MyComponent);

Example 2: Queries on your Component's width AND height

If you would like to operate on height also then you must use the extended configuration mode shown below to enable monitoring on the height of your component:

import componentQueries from 'react-component-queries';

class MyComponent extends Component {
  render() {
    return (
      <div>
        {/* We recieve the following props from our queries */}
        I am at {this.props.breakpoint} scale.<br />
        I am {this.props.short ? 'short' : 'long'}<br />
        I am {this.props.square ? 'square' : 'rectangular'}
      </div>
    );
  }
}

// NOTE: We are passing in a configuration object now.
export default componentQueries({
  queries: [
    // Use just the width.
    ({ width }) => {
      if (width <= 330) return { breakpoint: 'small' };
      if (width > 330 && width <=960) return { breakpoint: 'medium' };
      return { breakpoint: 'large' };
    },
    // Or use just the height.
    ({ height }) => ({ short: height > 200 }),
    // Or use both.
    ({ width, height }) => ({ square: width === height }),
  ],
  config: { monitorHeight: true }
})(MyComponent);

As you can see we expose a sizeMeConfig, please see the react-sizeme for the full list of options that you can provide.

Prop Conflict Handling

As it is possible for you to provide props from multiple queries there could be cases where prop clashing occurs. By default we have an order of preference for which prop value should be resolved in the case of conflicts.

The rule is: Custom passed in props take preference followed by the last item in the query collection.

Let's illustrate this given the following component:

const MyComponent = componentQueries(
  ({ width }) => { return { foo: 'bar' }; },
  ({ width }) => { return { foo: 'bob' }; }
)(ComponentToWrap);

If we rendered this component the value we would received for foo would be "bob".

Then say we rendered our component like so, passing in a custom prop:

ReactDOM.render(<MyComponent foo="zip" />, container);

In this case the value of foo would resolve to "zip".

It's important to remember this.

Custom Prop Conflict Resolution

There may be cases when you want to provide custom rules for how conflicts are resolved. For example, say you wanted your queries to produce className props, but desired that any conflicts simply resolved in the conflicts being concatenated. This can be especially helpful in the case where you want users to be able to pass in custom className props into your component.

To support this case we provide an extended configuration item called conflictResolver, which is specifically a function of the following structure:

function (prevPropValue: Any, currentPropValue: Any, propName: String) : Any

To solve our above described case we could provide the following implementation of the conflictResolver:

const MyComponent = componentQueries({
  queries: [
    ({ width }) => ({ className: 'foo', poop: 'splash' }),
    ({ width }) => ({ className: 'bar', poop: 'plop' })
  ],
  conflictResolver: (prev, current, key) => {
    // If the prop is "className" we will concat the new value to
    // the current value.
    if (key === 'className') {
      return prev.concat(' ', current);
    }
    // Otherwise we return the current value, overriding the prev value.
    return current;
  }
})(ComponentToWrap);

If we rendered our component like so:

ReactDOM.render(<MyComponent className="baz" />, container);

Then the props that would be resolved would be:

{
  className: 'foo bar baz',
  poop: 'plop'
}

Credits

Rubix graphic by Freepik from Flaticon is licensed under CC BY 3.0. Made with Logo Maker

More Repositories

1

easy-peasy

Vegetarian friendly state for React
JavaScript
5,022
star
2

react-sizeme

Make your React Components aware of their width and height!
JavaScript
1,932
star
3

react-universally

A starter kit for universal react applications.
JavaScript
1,699
star
4

react-async-component

Resolve components asynchronously, with support for code splitting and advanced server side rendering use cases.
JavaScript
1,447
star
5

react-tree-walker

Walk a React (or Preact) element tree, executing a "visitor" function against each element.
JavaScript
346
star
6

react-jobs

Asynchronously resolve data for your components, with support for server side rendering.
JavaScript
167
star
7

react-async-bootstrapper

Execute a bootstrap method on your React/Preact components. Useful for data prefetching and other activities.
JavaScript
118
star
8

code-split-component

Declarative code splitting for your Wepback bundled React projects, with SSR support.
JavaScript
116
star
9

prisma-pg-jest

Example showcasing how to use Prisma + Postgres + Jest, where each test has its own unique DB context
TypeScript
106
star
10

react-virtual-container

Optimise your React apps by only rendering components when in proximity to the viewport.
JavaScript
53
star
11

cra-monorepo

Example of a now 2.0 monorepo containing Create React App and Node Lambdas
TypeScript
52
star
12

vercel-node-server

An unofficial package allowing you to create http.Server instances of your Vercel Node lambdas.
TypeScript
50
star
13

react-injectables

Explicitly inject Components into any part of your React render tree.
JavaScript
43
star
14

easy-peasy-typescript

An example of using Easy Peasy with Typescript
TypeScript
33
star
15

react-universally-skinny

A "when size matters" adaptation of the react-universally boilerplate.
JavaScript
30
star
16

lerna-cola

Superpowers for your Lerna monorepos.
JavaScript
23
star
17

gun-most

Extends gunDB with the ability to chain into most.js observables.
JavaScript
14
star
18

i-theme

A minimalist theme for vscode
12
star
19

preact-compat-hmr

Example HMR with preact, preact-compat and webpack 2.
JavaScript
12
star
20

prisma2-template

A scratchpad for Prisma2
JavaScript
12
star
21

vercel-node-dev

An unofficial development CLI for Vercel applications targeting the Node.js runtime.
TypeScript
10
star
22

npm-library-starter

A starter kit for npm libraries.
JavaScript
9
star
23

vue-zod-form

A composition based API forms helper for Vue 3 projects that utilise TypeScript.
Vue
9
star
24

vue3-typescript-strategy

An example of a sound TS experience in Vue 3 via .tsx files
TypeScript
8
star
25

cinderella

[WIP] A tiny transformation library.
JavaScript
8
star
26

demo-react-async-and-jobs

Demo on how to use react-async-component and react-jobs together in an SSR project.
JavaScript
7
star
27

react-universally-opinionated

[WIP] An opinionated version of React, Universally.
JavaScript
7
star
28

simee

Ultra simple symlinking of local npm packages.
JavaScript
7
star
29

remix-aws-edge

An adapter for running Remix on AWS CloudFront Lambda@Edge
TypeScript
6
star
30

easy-peasy-hot-reload

Easy Peasy hot reloading example
HTML
6
star
31

templatiser

Apply templates to a file tree.
JavaScript
4
star
32

react-hey-fela

Alternative React bindings for Fela
JavaScript
4
star
33

react-app-rewire-modernizr

Adds the modernizr-loader to your react-app-rewired config.
JavaScript
3
star
34

reason-advent-2017

https://adventofcode.com solved using ReasonML
OCaml
2
star
35

code-split-match

A Webpack 2 code splitting Match component for React Router v4.
2
star
36

comickult.holding

RxJS powered holding page for comickult.
JavaScript
2
star
37

flowcheatsheet

An interactive cheat sheet for Facebook's Flow type system.
JavaScript
2
star
38

lerna-cola-sample

A sample application showcasing Lerna Cola
JavaScript
2
star
39

react-app-rewire-css-blocks

Adds support for CSS Blocks to your react-app-rewired config.
JavaScript
2
star
40

react-universally-archive

JavaScript
2
star
41

now-dev-issue

JavaScript
1
star
42

reason-react-gof

[WIP] Conway's Game of Life implemented using ReasonML/Bucklescript/React.
OCaml
1
star
43

sst-cors-example

An example of CORS configuration in the context of a Serverless Stack application.
TypeScript
1
star
44

react-responsive-element

A responsive element component for React web applications.
1
star
45

monilla

A CLI to manage monorepos with predictability and stability.
TypeScript
1
star
46

approachable-fp-in-scala

An Approachable Guide to Functional Programming in Scala
1
star
47

react-router-bundlesize

Debugging react-router bundle size optimisation.
JavaScript
1
star
48

blank

Blank project for fiddling with JS whilst having all the tooling I like
JavaScript
1
star
49

splice

A malleable content management system.
1
star
50

react-fractal-grid

A fractal grid Component set for React.
1
star