Repository: ctrlplusb/react-component-queries
Branch: master
Commit: a94df672f826
Files: 28
Total size: 45.0 KB
Directory structure:
gitextract_w9wdpwa2/
├── .babelrc
├── .eslintignore
├── .gitignore
├── .nvmrc
├── .travis.yml
├── LICENSE
├── README.md
├── examples/
│ ├── .eslintrc
│ └── web/
│ ├── .babelrc
│ ├── package.json
│ ├── server.js
│ ├── src/
│ │ └── index.js
│ └── tools/
│ └── webpack/
│ ├── config.js
│ └── html.js
├── package.json
├── rollup-min.config.js
├── rollup.config.js
├── src/
│ ├── __tests__/
│ │ └── componentQueries.test.js
│ ├── componentQueries.js
│ ├── index.js
│ └── utils/
│ ├── __tests__/
│ │ └── mergeWith.test.js
│ ├── getDisplayName.js
│ ├── mergeWith.js
│ └── shallowEqual.js
├── tools/
│ ├── .eslintrc
│ ├── scripts/
│ │ └── build.js
│ └── utils.js
└── wallaby.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"presets": [
[
"env",
{
"targets": {
"node": true
}
}
],
"stage-3",
"react"
],
"plugins": ["transform-class-properties"]
}
================================================
FILE: .eslintignore
================================================
node_modules/
commonjs/
coverage/
umd/
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
# Dependencies
node_modules
# Debug log from npm
npm-debug.log
# Jest
coverage
# Build output
dist
================================================
FILE: .nvmrc
================================================
8
================================================
FILE: .travis.yml
================================================
sudo: false
language: node_js
cache:
yarn: true
directories:
- node_modules
node_js:
- '8'
script:
- npm run precommit
after_success:
# Deploy code coverage report to codecov.io
- npm run test:coverage:deploy
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Sean Matheson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<p align='center'>
<img width='250' src='https://raw.githubusercontent.com/ctrlplusb/react-component-queries/master/assets/logo.png' />
<p align='center'>Provide props to your React Components based on their Width and/or Height.</p>
</p>
<p align='center'>
[](https://travis-ci.org/ctrlplusb/react-component-queries)
[](http://npm.im/react-component-queries)
[](http://opensource.org/licenses/MIT)
[](https://codecov.io/github/ctrlplusb/react-component-queries)
```javascript
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](https://github.com/ctrlplusb/react-component-queries#overview)
- [Why use this instead of `react-sizeme`?](https://github.com/ctrlplusb/react-component-queries#why-use-this-instead-of-react-sizeme)
- [Install](https://github.com/ctrlplusb/react-component-queries#install)
- [Demo](https://github.com/ctrlplusb/react-component-queries#demo)
- [API](https://github.com/ctrlplusb/react-component-queries#api)
- [Examples](https://github.com/ctrlplusb/react-component-queries#examples)
- [Prop Conflict Handling](https://github.com/ctrlplusb/react-component-queries#prop-conflict-handling)
- [Custom Prop Conflict Resolution](https://github.com/ctrlplusb/react-component-queries#custom-prop-conflict-resolution)
## Overview
`react-component-queries` is a useful abstraction of the [`react-sizeme`](https://github.com/ctrlplusb/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:
```javascript
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:
```javascript
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:
```javascript
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`](https://github.com/ctrlplusb/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!](https://react-component-queries-demo-aowygvryob.now.sh)
## 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.
```javascript
componentQueries([
function (size, props) { return { foo: 'bar' }; },
function (size, props) { return { bob: true }; }
])(MyComponent)
```
or
```javascript
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.
```javascript
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.
```javascript
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:
```javascript
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`](https://github.com/ctrlplusb/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:
```javascript
function (prevPropValue: Any, currentPropValue: Any, propName: String) : Any
```
To solve our above described case we could provide the following implementation of the `conflictResolver`:
```javascript
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:
```javascript
ReactDOM.render(<MyComponent className="baz" />, container);
```
Then the props that would be resolved would be:
```javascript
{
className: 'foo bar baz',
poop: 'plop'
}
```
---
### Credits
Rubix graphic by <a href="http://www.freepik.com/">Freepik</a> from <a href="http://www.flaticon.com/">Flaticon</a> is licensed under <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0">CC BY 3.0</a>. Made with <a href="http://logomakr.com" title="Logo Maker">Logo Maker</a>
================================================
FILE: examples/.eslintrc
================================================
{
"rules": {
"no-console": 0
}
}
================================================
FILE: examples/web/.babelrc
================================================
{
"presets": [
"latest",
"stage-3",
"react"
]
}
================================================
FILE: examples/web/package.json
================================================
{
"name": "my-library-example",
"version": "1.0.0",
"description": "An example of my-library",
"main": "index.js",
"scripts": {
"start": "babel-node server.js"
},
"author": "",
"license": "MIT",
"devDependencies": {},
"dependencies": {
"app-root-dir": "^1.0.2",
"babel-cli": "^6.18.0",
"babel-core": "^6.21.0",
"babel-loader": "^6.2.10",
"babel-preset-latest": "^6.16.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-3": "^6.17.0",
"babel-register": "^6.18.0",
"express": "^4.14.0",
"html-webpack-plugin": "^2.26.0",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"webpack": "^2.2.0-rc.3",
"webpack-dev-middleware": "^1.9.0",
"webpack-hot-middleware": "^2.15.0"
}
}
================================================
FILE: examples/web/server.js
================================================
import express from 'express';
import webpack from 'webpack';
import devMiddleware from 'webpack-dev-middleware';
import hotMiddleware from 'webpack-hot-middleware';
import config from './tools/webpack/config';
const port = process.env.PORT || 1337;
const app = express();
const compiler = webpack(config);
app.use(
devMiddleware(compiler, {
quiet: true,
noInfo: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
// Ensure that the public path is taken from the compiler webpack config
// as it will have been created as an absolute path to avoid conflicts
// with an node servers.
publicPath: compiler.options.output.publicPath,
}),
);
app.use(hotMiddleware(compiler));
app.listen(port, () => console.log(`Example running on port ${port}...`));
================================================
FILE: examples/web/src/index.js
================================================
import React from 'react';
import { render } from 'react-dom';
function App() {
return <div>poop</div>;
}
render(<App />, document.getElementById('app'));
================================================
FILE: examples/web/tools/webpack/config.js
================================================
import { resolve as resolvePath } from 'path';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import appRootDir from 'app-root-dir';
import pkg from '../../package.json';
module.exports = {
entry: {
index: resolvePath(appRootDir.get(), './src/index.js'),
},
output: {
path: resolvePath(appRootDir.get(), './build'),
filename: `${pkg.name}.js`,
publicPath: '/',
},
target: 'web',
plugins: [
new webpack.NoErrorsPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: resolvePath(__dirname, './html.js'),
inject: true,
// We can pass custom data to the template...
custom: {
name: pkg.name,
description: pkg.description,
},
}),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: [
resolvePath(appRootDir.get(), './src'),
],
},
],
},
};
================================================
FILE: examples/web/tools/webpack/html.js
================================================
module.exports = function html(templateParams) {
const { name, description } = templateParams.htmlWebpackPlugin.options.custom;
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="description" content="${description}"/>
<meta name="charset" content="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>${name} example</title>
</head>
<body>
<div id='app'></div>
</body>
</html>`;
};
================================================
FILE: package.json
================================================
{
"name": "react-component-queries",
"description":
"Provide props to your Components based on their Width and/or Height.",
"version": "2.3.0",
"license": "MIT",
"main": "dist/react-component-queries.js",
"files": ["*.js", "*.md", "dist"],
"repository": {
"type": "git",
"url": "https://github.com/ctrlplusb/react-component-queries.git"
},
"homepage": "https://github.com/ctrlplusb/react-component-queries#readme",
"author": "Sean Matheson <sean@ctrlplusb.com>",
"keywords": ["library"],
"scripts": {
"build": "node ./tools/scripts/build.js",
"clean": "rimraf ./dist && rimraf ./coverage",
"example:web":
"echo 'Make sure to `cd example/web && yarn install`' && cd example/web && yarn run start",
"lint": "eslint src",
"precommit": "lint-staged && npm run test",
"prepublish": "npm run build",
"test": "jest",
"test:coverage": "npm run test -- --coverage",
"test:coverage:deploy": "npm run test:coverage && codecov"
},
"dependencies": {
"invariant": "^2.2.2"
},
"peerDependencies": {
"prop-types": "^15.0.0",
"react": "^0.14.0 || ^15.0.0 || ^16.0.0",
"react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0",
"react-sizeme": "^2.0.0"
},
"devDependencies": {
"app-root-dir": "^1.0.2",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-eslint": "^8.0.0",
"babel-jest": "^22.4.3",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"babel-register": "^6.26.0",
"change-case": "^3.0.2",
"codecov": "^3.0.0",
"cross-env": "^5.0.5",
"enzyme": "^3.1.0",
"enzyme-adapter-react-16": "^1.0.2",
"enzyme-to-json": "^3.1.4",
"eslint": "^4.7.2",
"eslint-config-airbnb": "^16.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.3.0",
"gzip-size": "^4.0.0",
"husky": "^0.14.3",
"in-publish": "^2.0.0",
"jest": "^22.4.3",
"lint-staged": "^7.0.0",
"prettier": "^1.7.0",
"pretty-bytes": "^4.0.2",
"raf": "^3.4.0",
"ramda": "^0.25.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-sizeme": "^2.3.6",
"readline-sync": "^1.4.7",
"rimraf": "^2.6.2",
"rollup": "^0.57.1",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-uglify": "^3.0.0"
},
"jest": {
"collectCoverageFrom": ["src/**/*.{js,jsx}"],
"snapshotSerializers": ["<rootDir>/node_modules/enzyme-to-json/serializer"],
"testPathIgnorePatterns": ["<rootDir>/(coverage|dist|node_modules|tools)/"]
},
"eslintConfig": {
"root": true,
"parser": "babel-eslint",
"env": {
"browser": true,
"es6": true,
"node": true,
"jest": true
},
"extends": ["airbnb", "prettier"],
"rules": {
"camelcase": 0,
"import/prefer-default-export": 0,
"import/no-extraneous-dependencies": 0,
"no-nested-ternary": 0,
"no-underscore-dangle": 0,
"react/no-array-index-key": 0,
"react/react-in-jsx-scope": 0,
"semi": [2, "never"],
"react/forbid-prop-types": 0,
"react/jsx-filename-extension": 0,
"react/sort-comp": 0
}
},
"eslintIgnore": ["node_modules/", "dist/", "coverage/"],
"prettier": {
"semi": false,
"singleQuote": true,
"trailingComma": "all"
},
"lint-staged": {
"*.js": ["prettier --write \"src/**/*.js\"", "git add"]
}
}
================================================
FILE: rollup-min.config.js
================================================
const uglify = require('rollup-plugin-uglify')
const packageJson = require('./package.json')
const baseConfig = require('./rollup.config.js')
baseConfig.plugins.push(uglify())
baseConfig.output.file = `dist/${packageJson.name}.min.js`
module.exports = baseConfig
================================================
FILE: rollup.config.js
================================================
const babel = require('rollup-plugin-babel')
const changeCase = require('change-case')
const packageJson = require('./package.json')
process.env.BABEL_ENV = 'production'
module.exports = {
external: [
'element-resize-detector',
'invariant',
'lodash.debounce',
'lodash.throttle',
'prop-types',
'react-dom',
'react-sizeme',
'react',
],
input: 'src/index.js',
output: {
file: `dist/${packageJson.name}.js`,
format: 'cjs',
sourcemap: true,
name: changeCase
.titleCase(packageJson.name.replace(/-/g, ' '))
.replace(/ /g, ''),
},
plugins: [
babel({
babelrc: false,
exclude: 'node_modules/**',
presets: [['env', { modules: false }], 'stage-3', 'react'],
plugins: ['external-helpers', 'transform-class-properties'],
}),
],
}
================================================
FILE: src/__tests__/componentQueries.test.js
================================================
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable global-require */
/* eslint-disable no-underscore-dangle */
import React from 'react'
import enzyme, { mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
enzyme.configure({ adapter: new Adapter() })
describe('Given the ComponentQueries library', () => {
let componentQueries
let sizeMeConfig
beforeEach(() => {
jest.doMock('react-sizeme', () => config => {
sizeMeConfig = config
return x => x
})
componentQueries = require('../componentQueries').default
})
describe('When setting up the ComponentQueries HOC', () => {
describe('And no queries are provided', () => {
it('Then an error should be thrown', () => {
const simpleConfig = () => {
componentQueries()
}
expect(simpleConfig).toThrow(
/provide at least one query to ComponentQueries/,
)
const complexConfig = () => {
componentQueries({
queries: [],
})
}
expect(complexConfig).toThrow(
/provide at least one query to ComponentQueries/,
)
const complexConfig2 = () => {
componentQueries({
queries: 'foo',
})
}
expect(complexConfig2).toThrow(/"queries" must be provided as an array/)
})
})
describe('And an invalid query type is provided', () => {
it('Then an error should be thrown', () => {
const simpleConfig = () => {
componentQueries('wrong!')
}
expect(simpleConfig).toThrow(
/queries for ComponentQueries should be functions/,
)
const complexConfig = () => {
componentQueries({
queries: ['foo'],
})
}
expect(complexConfig).toThrow(
/queries for ComponentQueries should be functions/,
)
})
})
describe('And no sizeMeConfig configuration is provided', () => {
it('Then the default config should be given to SizeMe', () => {
componentQueries(() => ({}))(() => <div />)
expect(sizeMeConfig).toMatchObject({
monitorHeight: false,
monitorWidth: true,
refreshRate: 16,
})
})
})
describe('And a custom sizeMeConfig configuration is provided', () => {
it('Then the custom config should be given to SizeMe', () => {
componentQueries({
queries: [() => ({})],
sizeMeConfig: {
monitorHeight: true,
monitorWidth: false,
noPlaceholder: true,
refreshRate: 200,
},
})(() => <div />)
expect(sizeMeConfig).toMatchObject({
monitorHeight: true,
monitorWidth: false,
refreshRate: 200,
noPlaceholder: true,
})
})
})
describe('And a custom config is provided', () => {
it('Then the custom config should be given to SizeMe', () => {
const conflictResolver = () => undefined
componentQueries({
queries: [() => ({})],
config: {
monitorHeight: true,
monitorWidth: false,
refreshRate: 200,
refreshMode: 'debounce',
noPlaceholder: true,
conflictResolver,
},
})(() => <div />)
expect(sizeMeConfig).toMatchObject({
monitorHeight: true,
monitorWidth: false,
refreshRate: 200,
refreshMode: 'debounce',
noPlaceholder: true,
})
})
})
})
describe('When rendering a component queries component', () => {
it("Then it should receive the appropriate props based on it's queries", () => {
let receivedProps
const ComponentQueriedComponent = componentQueries({
queries: [
({ width }) => (width <= 100 ? { foo: 'bar' } : {}),
({ width }) => (width > 100 ? { bob: 'baz' } : {}),
({ height }) => (height <= 100 ? { zip: 'zap' } : {}),
],
// NOTE: This is the old configuration.
sizeMeConfig: {
monitorWidth: true,
monitorHeight: true,
},
})(props => {
receivedProps = props
return <div />
})
// Initial render
const mounted = mount(
<ComponentQueriedComponent size={{ width: 100, height: 100 }} />,
)
expect(receivedProps).toMatchObject({ foo: 'bar', zip: 'zap' })
// Update size, but no size change
mounted.setProps({ size: { width: 100, height: 100 } })
expect(receivedProps).toMatchObject({ foo: 'bar', zip: 'zap' })
// Update size, with change.
mounted.setProps({ size: { width: 101, height: 99 } })
expect(receivedProps).toMatchObject({ bob: 'baz', zip: 'zap' })
// Update size, with change.
mounted.setProps({ size: { width: 101, height: 101 } })
expect(receivedProps).toMatchObject({ bob: 'baz' })
})
it('Then it should only rerender if the props have changed', () => {
const ComponentQueriedComponent = componentQueries({
queries: [
({ width }) => (width <= 100 ? { foo: 'bar' } : {}),
({ width }) => (width > 100 ? { bob: 'baz' } : {}),
],
})(() => <div />)
// Initial render
const mounted = mount(
<ComponentQueriedComponent size={{ width: 50 }} foo="bar" />,
)
const instance = mounted.instance()
// Set up a spy on the render
const renderSpy = jest.spyOn(instance, 'render')
expect(renderSpy).toHaveBeenCalledTimes(0)
// Change the width so that the queries produce a new result.
mounted.setProps({ size: { width: 150 }, foo: 'bar' })
expect(renderSpy).toHaveBeenCalledTimes(1)
// Change the width so that the queries produce the same result.
mounted.setProps({ size: { width: 120 }, foo: 'bar' })
expect(renderSpy).toHaveBeenCalledTimes(1)
// Change the value of an "other" prop should cause a new render.
mounted.setProps({ size: { width: 120 }, foo: 'zip' })
expect(renderSpy).toHaveBeenCalledTimes(2)
})
it('Then an impure component should always render', () => {
const ComponentQueriedComponent = componentQueries({
queries: [({ width }) => (width <= 100 ? { foo: 'bar' } : {})],
config: {
pure: false,
},
})(() => <div />)
// Initial render
const mounted = mount(
<ComponentQueriedComponent size={{ width: 50 }} foo="bar" />,
)
const instance = mounted.instance()
// Set up a spy on the render
const renderSpy = jest.spyOn(instance, 'render')
expect(renderSpy).toHaveBeenCalledTimes(0)
// Set the props causes a rerender.
mounted.setProps({ size: { width: 150 }, foo: 'bar' })
expect(renderSpy).toHaveBeenCalledTimes(1)
// Set the same props causes a rerender.
mounted.setProps({ size: { width: 150 }, foo: 'bar' })
expect(renderSpy).toHaveBeenCalledTimes(2)
})
it('Then it should pass the "other" props to the queries', () => {
let actualProps
const ComponentQueriedComponent = componentQueries((_, props) => {
actualProps = props
return {}
})(() => <div />)
// Initial mount should call queries.
const expectedMountProps = { foo: 'bar', baz: 1 }
const mounted = mount(
<ComponentQueriedComponent
size={{ width: 50 }}
{...expectedMountProps}
/>,
)
expect(actualProps).toMatchObject(expectedMountProps)
// Update should call queries with updated props.
const expectedUpdateProps = { foo: 'bob', baz: 2 }
mounted.setProps(
Object.assign({}, { size: { width: 100 } }, expectedUpdateProps),
)
expect(actualProps).toMatchObject(expectedUpdateProps)
})
it('Then height should be undefined if we are not monitoring height', () => {
let actualHeight
const ComponentQueriedComponent = componentQueries(({ height }) => {
actualHeight = height
return {}
})(() => <div />)
// Initial render
mount(<ComponentQueriedComponent size={{ width: 100, height: 100 }} />)
expect(actualHeight).toEqual(null)
})
it('Then duplicate props should be overridden when using the default conflict resolver', () => {
let receivedProps
const ComponentQueriedComponent = componentQueries(
({ width }) => (width <= 100 ? { foo: 'bar' } : {}),
({ width }) => (width <= 100 ? { foo: 'baz' } : {}),
)(props => {
receivedProps = props
return <div />
})
// Initial render with duplicate query result.
const mounted = mount(
<ComponentQueriedComponent size={{ width: 100, height: 100 }} />,
)
expect(receivedProps).toMatchObject({ foo: 'baz' })
// Set a custom prop that conflicts with the query result.
mounted.setProps({ foo: 'bob' })
expect(receivedProps).toMatchObject({ foo: 'bob' })
})
it('Then a custom conflict resolver should behave as expected', () => {
let receivedProps
const ComponentQueriedComponent = componentQueries({
queries: [
({ width }) => (width <= 100 ? { foo: 'bar' } : {}),
({ width }) => (width <= 100 ? { foo: 'bob' } : {}),
],
conflictResolver: (x, y, key) => (key === 'foo' ? x.concat(' ', y) : y),
})(props => {
receivedProps = props
return <div />
})
// Initial render duplicate prop
const mounted = mount(
<ComponentQueriedComponent size={{ width: 100, height: 100 }} />,
)
expect(receivedProps).toMatchObject({ foo: 'bar bob' })
// Updated component duplicate prop
mounted.setProps({ foo: 'baz' })
expect(mounted.props().foo).toEqual('baz')
})
})
})
================================================
FILE: src/componentQueries.js
================================================
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import invariant from 'invariant'
import sizeMe from 'react-sizeme'
import mergeWith from './utils/mergeWith'
import getDisplayName from './utils/getDisplayName'
import shallowEqual from './utils/shallowEqual'
const defaultConfig = {
monitorHeight: false,
monitorWidth: true,
refreshRate: 16,
pure: true,
noPlaceholder: false,
}
const defaultConflictResolver = (x, y) => y
const defaultSizeMeConfig = () => ({
monitorWidth: defaultConfig.monitorWidth,
monitorHeight: defaultConfig.monitorHeight,
refreshRate: defaultConfig.refreshRate,
})
/**
* :: Queries -> Component -> Component
*
* This is a HOC that provides you with the mechanism to specify Component
* queries. A Component query is a similar concept to media queries except it
* operates on the Component's width/height rather than the entire viewport
* width/height.
*/
function componentQueries(...params) {
let queries
let sizeMeConfig
let pure
let conflictResolver
if (params.length === 1 && params[0].queries) {
queries = params[0].queries || []
if (params[0].sizeMeConfig) {
// Old school config style.
sizeMeConfig = params[0].sizeMeConfig || defaultSizeMeConfig()
pure = defaultConfig.pure // this didn't exist before, so we default it.
} else if (params[0].config) {
// New school config style.
pure = params[0].config.pure
const {
monitorHeight,
monitorWidth,
refreshRate,
refreshMode,
noPlaceholder,
} = params[0].config
sizeMeConfig = {
monitorHeight:
monitorHeight != null ? monitorHeight : defaultConfig.monitorHeight,
monitorWidth:
monitorWidth != null ? monitorWidth : defaultConfig.monitorWidth,
refreshRate:
refreshRate != null ? refreshRate : defaultConfig.refreshRate,
refreshMode:
refreshMode != null ? refreshMode : defaultConfig.refreshMode,
noPlaceholder:
noPlaceholder != null ? noPlaceholder : defaultConfig.noPlaceholder,
}
}
conflictResolver =
conflictResolver || params[0].conflictResolver || defaultConflictResolver
invariant(
typeof conflictResolver === 'function',
'The conflict resolver you provide to ComponentQueries should be a function.',
)
invariant(
Array.isArray(queries),
'"queries" must be provided as an array when using the complex configuration.',
)
} else {
queries = params
}
// TODO: Consider removing this check. Perhaps it's best to just silently
// pass through if no queries were provided? Maybe a development based
// warning would be the most useful.
invariant(
queries.length > 0,
'You must provide at least one query to ComponentQueries.',
)
invariant(
queries.filter(q => typeof q !== 'function').length === 0,
'All provided queries for ComponentQueries should be functions.',
)
// We will default out any configuration if it wasn't set.
sizeMeConfig = sizeMeConfig || defaultSizeMeConfig()
conflictResolver = conflictResolver || defaultConflictResolver
pure = pure != null ? pure : defaultConfig.pure
const mergeWithCustomizer = (x, y, key) => {
if (x === undefined) return undefined
return conflictResolver(x, y, key)
}
return function WrapComponent(WrappedComponent) {
class ComponentWithComponentQueries extends Component {
static displayName = `ComponentQueries(${getDisplayName(
WrappedComponent,
)})`
static propTypes = {
size: PropTypes.shape({
width: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
height: PropTypes.number, // eslint-disable-line react/no-unused-prop-types
}).isRequired,
}
static WrappedComponent = WrappedComponent
state = {
queryResult: {},
}
componentWillMount() {
const { size, ...otherProps } = this.props
this.runQueries(size, otherProps)
}
componentWillReceiveProps(nextProps) {
const { size } = this.props
const { size: nextSize, ...nextOtherProps } = nextProps
if (!shallowEqual(size, nextSize)) {
this.runQueries(nextSize, nextOtherProps)
}
}
shouldComponentUpdate(nextProps, nextState) {
const {
size, // eslint-disable-line no-unused-vars
...otherProps
} = this.props
const {
size: nextSize, // eslint-disable-line no-unused-vars
...nextOtherProps
} = nextProps
return (
!pure ||
!shallowEqual(otherProps, nextOtherProps) ||
!shallowEqual(this.state.queryResult, nextState.queryResult)
)
}
runQueries({ width, height }, otherProps) {
const queryResult = queries.reduce(
(acc, cur) =>
mergeWith(
acc,
cur(
{
width: sizeMeConfig.monitorWidth ? width : null,
height: sizeMeConfig.monitorHeight ? height : null,
},
otherProps,
),
mergeWithCustomizer,
),
{},
)
this.setState({ queryResult })
}
render() {
const {
size, // eslint-disable-line no-unused-vars
...otherProps
} = this.props
const allProps = mergeWith(
this.state.queryResult,
otherProps,
mergeWithCustomizer,
)
return <WrappedComponent {...allProps} />
}
}
return sizeMe(sizeMeConfig)(ComponentWithComponentQueries)
}
}
export default componentQueries
================================================
FILE: src/index.js
================================================
import componentQueries from './componentQueries'
export default componentQueries
================================================
FILE: src/utils/__tests__/mergeWith.test.js
================================================
import mergeWith from '../mergeWith'
describe('When mering props with `mergeWith`', () => {
describe('and we are using "apply left prop" as resolver', () => {
const resolver = x => x
it('it should keep all defined values on left side.', () => {
const a = {
string: 'string',
zero: 0,
negaive: -1,
float: 0.555555,
deep: {
er: 'foo',
},
array: [0, 1],
emptyArray: [],
}
expect(mergeWith(a, {}, resolver)).toMatchObject({
string: 'string',
zero: 0,
negaive: -1,
float: 0.555555,
deep: {
er: 'foo',
},
array: [0, 1],
emptyArray: [],
})
})
it('it should keep all defined values on right side.', () => {
const b = {
string: 'string',
zero: 0,
negaive: -1,
float: 0.555555,
deep: {
er: 'foo',
},
array: [0, 1],
emptyArray: [],
}
expect(mergeWith({}, b, resolver)).toMatchObject({
string: 'string',
zero: 0,
negaive: -1,
float: 0.555555,
deep: {
er: 'foo',
},
array: [0, 1],
emptyArray: [],
})
})
it('it should copy existing values and use left one on conflict.', () => {
const a = {
string: 'string',
}
const b = {
string: 'my string',
}
expect(mergeWith(a, b, resolver)).toMatchObject({
string: 'string',
})
})
})
})
================================================
FILE: src/utils/getDisplayName.js
================================================
// :: Component => String
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}
export default getDisplayName
================================================
FILE: src/utils/mergeWith.js
================================================
// :: (Object, Object, (any, any) => any) => Object
const mergeWith = (x, y, fn) => {
const result = Object.assign({}, x)
Object.keys(y).forEach(key => {
if (x[key] && y[key]) {
result[key] = fn(x[key], y[key], key)
} else {
result[key] = y[key]
}
})
return result
}
export default mergeWith
================================================
FILE: src/utils/shallowEqual.js
================================================
// Taken from react-redux. Thanks Dan!
export default function shallowEqual(objA, objB) {
if (objA === objB) {
return true
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) {
return false
}
// Test for A's keys different from B.
const hasOwn = Object.prototype.hasOwnProperty
for (let i = 0; i < keysA.length; i += 1) {
// eslint-disable-line no-plusplus
if (!hasOwn.call(objB, keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
return false
}
}
return true
}
================================================
FILE: tools/.eslintrc
================================================
{
"rules": {
"no-console": 0,
"import/no-extraneous-dependencies": 0
}
}
================================================
FILE: tools/scripts/build.js
================================================
const { readFileSync } = require('fs')
const { inInstall } = require('in-publish')
const prettyBytes = require('pretty-bytes')
const gzipSize = require('gzip-size')
const { pipe } = require('ramda')
const { exec } = require('../utils')
const packageJson = require('../../package.json')
if (inInstall()) {
process.exit(0)
}
const nodeEnv = Object.assign({}, process.env, {
NODE_ENV: 'production',
})
exec('npx rollup -c rollup-min.config.js', nodeEnv)
exec('npx rollup -c rollup.config.js', nodeEnv)
function fileGZipSize(path) {
return pipe(readFileSync, gzipSize.sync, prettyBytes)(path)
}
console.log(
`\ngzipped, the build is ${fileGZipSize(`dist/${packageJson.name}.min.js`)}`,
)
================================================
FILE: tools/utils.js
================================================
const { execSync } = require('child_process')
const appRootDir = require('app-root-dir')
function exec(command) {
execSync(command, { stdio: 'inherit', cwd: appRootDir.get() })
}
module.exports = {
exec,
}
================================================
FILE: wallaby.js
================================================
const fs = require('fs')
const path = require('path')
process.env.NODE_ENV = 'test'
const babelConfigContents = fs.readFileSync(path.join(__dirname, '.babelrc'))
const babelConfig = JSON.parse(babelConfigContents)
module.exports = wallaby => ({
files: ['src/**/*.js', { pattern: 'src/**/*.test.js', ignore: true }],
tests: ['src/**/*.test.js'],
testFramework: 'jest',
env: {
type: 'node',
runner: 'node',
},
compilers: {
'src/**/*.js': wallaby.compilers.babel(babelConfig),
},
})
gitextract_w9wdpwa2/ ├── .babelrc ├── .eslintignore ├── .gitignore ├── .nvmrc ├── .travis.yml ├── LICENSE ├── README.md ├── examples/ │ ├── .eslintrc │ └── web/ │ ├── .babelrc │ ├── package.json │ ├── server.js │ ├── src/ │ │ └── index.js │ └── tools/ │ └── webpack/ │ ├── config.js │ └── html.js ├── package.json ├── rollup-min.config.js ├── rollup.config.js ├── src/ │ ├── __tests__/ │ │ └── componentQueries.test.js │ ├── componentQueries.js │ ├── index.js │ └── utils/ │ ├── __tests__/ │ │ └── mergeWith.test.js │ ├── getDisplayName.js │ ├── mergeWith.js │ └── shallowEqual.js ├── tools/ │ ├── .eslintrc │ ├── scripts/ │ │ └── build.js │ └── utils.js └── wallaby.js
SYMBOL INDEX (6 symbols across 6 files)
FILE: examples/web/src/index.js
function App (line 4) | function App() {
FILE: src/componentQueries.js
function componentQueries (line 33) | function componentQueries(...params) {
FILE: src/utils/getDisplayName.js
function getDisplayName (line 2) | function getDisplayName(WrappedComponent) {
FILE: src/utils/shallowEqual.js
function shallowEqual (line 3) | function shallowEqual(objA, objB) {
FILE: tools/scripts/build.js
function fileGZipSize (line 20) | function fileGZipSize(path) {
FILE: tools/utils.js
function exec (line 4) | function exec(command) {
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (49K chars).
[
{
"path": ".babelrc",
"chars": 191,
"preview": "{\n \"presets\": [\n [\n \"env\",\n {\n \"targets\": {\n \"node\": true\n }\n }\n ],\n \"st"
},
{
"path": ".eslintignore",
"chars": 39,
"preview": "node_modules/\ncommonjs/\ncoverage/\numd/\n"
},
{
"path": ".gitignore",
"chars": 121,
"preview": "# Logs\nlogs\n*.log\n\n# Dependencies\nnode_modules\n\n# Debug log from npm\nnpm-debug.log\n\n# Jest\ncoverage\n\n# Build output\ndist"
},
{
"path": ".nvmrc",
"chars": 2,
"preview": "8\n"
},
{
"path": ".travis.yml",
"chars": 217,
"preview": "sudo: false\nlanguage: node_js\ncache:\n yarn: true\n directories:\n - node_modules\nnode_js:\n- '8'\nscript:\n- npm run pre"
},
{
"path": "LICENSE",
"chars": 1080,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Sean Matheson\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "README.md",
"chars": 16405,
"preview": "<p align='center'>\n <img width='250' src='https://raw.githubusercontent.com/ctrlplusb/react-component-queries/master/as"
},
{
"path": "examples/.eslintrc",
"chars": 41,
"preview": "{\n \"rules\": {\n \"no-console\": 0\n }\n}\n"
},
{
"path": "examples/web/.babelrc",
"chars": 64,
"preview": "{\n \"presets\": [\n \"latest\",\n \"stage-3\",\n \"react\"\n ]\n}\n"
},
{
"path": "examples/web/package.json",
"chars": 757,
"preview": "{\n \"name\": \"my-library-example\",\n \"version\": \"1.0.0\",\n \"description\": \"An example of my-library\",\n \"main\": \"index.js"
},
{
"path": "examples/web/server.js",
"chars": 796,
"preview": "import express from 'express';\nimport webpack from 'webpack';\nimport devMiddleware from 'webpack-dev-middleware';\nimport"
},
{
"path": "examples/web/src/index.js",
"chars": 159,
"preview": "import React from 'react';\nimport { render } from 'react-dom';\n\nfunction App() {\n return <div>poop</div>;\n}\n\nrender(<Ap"
},
{
"path": "examples/web/tools/webpack/config.js",
"chars": 965,
"preview": "import { resolve as resolvePath } from 'path';\nimport webpack from 'webpack';\nimport HtmlWebpackPlugin from 'html-webpac"
},
{
"path": "examples/web/tools/webpack/html.js",
"chars": 600,
"preview": "module.exports = function html(templateParams) {\n const { name, description } = templateParams.htmlWebpackPlugin.option"
},
{
"path": "package.json",
"chars": 3629,
"preview": "{\n \"name\": \"react-component-queries\",\n \"description\":\n \"Provide props to your Components based on their Width and/o"
},
{
"path": "rollup-min.config.js",
"chars": 266,
"preview": "const uglify = require('rollup-plugin-uglify')\nconst packageJson = require('./package.json')\n\nconst baseConfig = require"
},
{
"path": "rollup.config.js",
"chars": 825,
"preview": "const babel = require('rollup-plugin-babel')\nconst changeCase = require('change-case')\nconst packageJson = require('./pa"
},
{
"path": "src/__tests__/componentQueries.test.js",
"chars": 9946,
"preview": "/* eslint-disable import/no-extraneous-dependencies */\n/* eslint-disable global-require */\n/* eslint-disable no-undersco"
},
{
"path": "src/componentQueries.js",
"chars": 5771,
"preview": "import React, { Component } from 'react'\nimport PropTypes from 'prop-types'\nimport invariant from 'invariant'\nimport siz"
},
{
"path": "src/index.js",
"chars": 83,
"preview": "import componentQueries from './componentQueries'\n\nexport default componentQueries\n"
},
{
"path": "src/utils/__tests__/mergeWith.test.js",
"chars": 1551,
"preview": "import mergeWith from '../mergeWith'\n\ndescribe('When mering props with `mergeWith`', () => {\n describe('and we are usin"
},
{
"path": "src/utils/getDisplayName.js",
"chars": 181,
"preview": "// :: Component => String\nfunction getDisplayName(WrappedComponent) {\n return WrappedComponent.displayName || WrappedCo"
},
{
"path": "src/utils/mergeWith.js",
"chars": 327,
"preview": "// :: (Object, Object, (any, any) => any) => Object\nconst mergeWith = (x, y, fn) => {\n const result = Object.assign({},"
},
{
"path": "src/utils/shallowEqual.js",
"chars": 564,
"preview": "// Taken from react-redux. Thanks Dan!\n\nexport default function shallowEqual(objA, objB) {\n if (objA === objB) {\n r"
},
{
"path": "tools/.eslintrc",
"chars": 85,
"preview": "{\n \"rules\": {\n \"no-console\": 0,\n \"import/no-extraneous-dependencies\": 0\n }\n}\n"
},
{
"path": "tools/scripts/build.js",
"chars": 697,
"preview": "const { readFileSync } = require('fs')\nconst { inInstall } = require('in-publish')\nconst prettyBytes = require('pretty-b"
},
{
"path": "tools/utils.js",
"chars": 212,
"preview": "const { execSync } = require('child_process')\nconst appRootDir = require('app-root-dir')\n\nfunction exec(command) {\n exe"
},
{
"path": "wallaby.js",
"chars": 509,
"preview": "const fs = require('fs')\nconst path = require('path')\n\nprocess.env.NODE_ENV = 'test'\n\nconst babelConfigContents = fs.rea"
}
]
About this extraction
This page contains the full source code of the ctrlplusb/react-component-queries GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (45.0 KB), approximately 12.1k tokens, and a symbol index with 6 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.