├── .eslintignore
├── .eslintrc
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bin
├── generateReadme.js
└── readme.ejs
├── dist
├── CHANGELOG.md
├── LICENSE
├── README.md
└── package.json
├── package-lock.json
├── package.json
├── src
├── _private
│ ├── utils.test.js
│ └── utils.ts
├── getChild
│ ├── README-deep.md
│ ├── README.md
│ ├── index.deep.test.js
│ ├── index.test.js
│ └── index.ts
├── getChildByType
│ ├── README-deep.md
│ ├── README.md
│ ├── index.deep.test.js
│ ├── index.test.js
│ └── index.ts
├── getChildren
│ ├── README-deep.md
│ ├── README.md
│ ├── index.deep.test.js
│ ├── index.test.js
│ └── index.ts
├── getChildrenByType
│ ├── README-deep.md
│ ├── README.md
│ ├── index.deep.test.js
│ ├── index.test.js
│ └── index.ts
├── getChildrenWithDescendant
│ ├── EXAMPLES.md
│ ├── README.md
│ ├── index.test.js
│ └── index.ts
├── getChildrenWithDescendantByType
│ ├── EXAMPLES.md
│ ├── README.md
│ ├── index.test.js
│ └── index.ts
├── getDescendantDepth
│ ├── EXAMPLES.md
│ ├── README.md
│ ├── index.test.js
│ └── index.ts
├── getDescendantDepthByType
│ ├── EXAMPLES.md
│ ├── README.md
│ ├── index.test.js
│ └── index.ts
├── index.ts
├── noEmptyChildren
│ ├── README-deep.md
│ ├── index.deep.test.js
│ └── index.ts
├── overrideProps
│ ├── README-deep.md
│ ├── README.md
│ ├── index.test.js
│ └── index.ts
├── removeChildren
│ ├── README-deep.md
│ ├── README.md
│ ├── index.deep.test.js
│ ├── index.test.js
│ └── index.ts
├── removeChildrenByType
│ ├── README-deep.md
│ ├── README.md
│ ├── index.deep.test.js
│ ├── index.test.js
│ └── index.ts
├── typeOfComponent
│ ├── README.md
│ ├── index.test.js
│ └── index.ts
└── types.ts
├── tsconfig.es5.json
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 | node_modules
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "plugins": [
5 | "@typescript-eslint"
6 | ],
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:@typescript-eslint/eslint-recommended",
10 | "plugin:@typescript-eslint/recommended"
11 | ],
12 | "rules": {
13 | "@typescript-eslint/no-explicit-any": 0,
14 | "comma-dangle": [1, "always-multiline"],
15 | "no-console": "warn",
16 | "semi": "error"
17 | }
18 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_store
2 | coverage
3 | dist/lib
4 | node_modules
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 |
5 | node_js:
6 | - 17
7 |
8 | script:
9 | - npm run compile
10 | - npm run test:coveralls
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [2.16.0] - 2025-02-19
2 | - Updated typeOfComponent() to optionally use type for React19.
3 |
4 | ## [2.15.0] - 2022-10-11
5 | - Added {skipWhenFound} to {getChildrenByType} to stop searching lower in the tree when a match is found.
6 |
7 | ## [2.14.1] - 2022-10-11
8 | - Removed toChildrenArray because it caused issues in getting nested children in an array.
9 |
10 | ## [2.14.0] - 2022-04-08
11 | - Created private toChildrenArray util to simplify code
12 |
13 | ## [2.13.0] - 2022-04-08
14 | - Added support for functions as children
15 |
16 | ## [2.12.0] - 2022-01-11
17 | - Added support for React.forwardRef
18 |
19 | ## [2.11.0] - 2021-11-08
20 | - Added example to main README.md
21 |
22 | ## [2.9.0] - 2021-07-21
23 | - Added overridePropsDeep
24 |
25 | ## [2.9.0] - 2021-05-17
26 | - Added typing generics for child items
27 |
28 | ## [2.8.0] - 2021-04-16
29 | - Added overload for 'ByType' functions to accept single type arg as well as an array of types
30 |
31 | ## [2.6.0] - 2021-01-19
32 | - Added getChildrenWithDescendant
33 | - Added getChildrenWithDescendantByType
34 | - Added getDescendantDepth
35 | - Added getDescendantDepthByType
36 | - Added type NannyNode
37 |
38 | ## [2.5.0] - 2021-01-04
39 | - Removed unused code and unreachable branches
40 |
41 | ## [2.4.0] - 2021-01-02
42 | - Integrated with Travis CI and Coveralls
43 | - Display build and coverage on README.md
44 |
45 | ## [2.3.1] - 2020-12-30
46 | - Moved TypeScript T on overrideProps util from param to generic for util
47 |
48 | ## [2.3.0] - 2020-12-30
49 | - Added overrideProps util
50 |
51 | ## [2.2.0] - 2020-12-29
52 | - Added the ability to infer type from React.ReactNode
53 |
54 | ## [2.1.0] - 2020-12-23
55 | - Added defined and exported config types
56 |
57 | ## [2.0.0] - 2020-12-22
58 | - Added ability to search for type as passed in component function/class
59 | - Moved customTypeKey into configuration object
60 |
61 | ## [1.1.0] - 2020-12-21
62 | - Documentation formatting updated
63 | - Descriptions for utils updated for clarity
64 |
65 | ## [1.0.0] - 2020-12-20
66 | - Initial release
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Michael Paravano
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS, AUTHORS' EMPLOYER(S), OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.com/TheSpicyMeatball/react-nanny)
2 | [](https://coveralls.io/github/TheSpicyMeatball/react-nanny?branch=main)
3 |
4 | # react-nanny
5 |
6 | > Utils to manage your React Children; find and filter children by type or custom function, enforce child content, and more!
7 |
8 |
Hello friend. Have you ever had the need to:
9 |
10 |
11 | - ...query a set of React Children by type or otherwise?
12 | - ...reject and remove some of your children for whatever [judgement free] reason?
13 | - ...ensure that your children return content at some level?
14 |
15 |
16 | If you answered yes to any of those questions, then it sounds like your children could use a nanny to help bring order to the chaos...
17 |
18 | Version: 2.15.0
19 |
20 | Dependencies
21 |
22 | react-nanny
doesn't have any dependencies. However, it does have a peer dependency of "react": ">=16.0.0"
which you most likely satisfy if you're the kind of person who's looking for utils for React children.
23 |
24 | Example
25 | This is simple example of how you can program defensively (your consumer can't just throw anything unexpected in children and have it render) and it shows how you can manipulate your children to place them anywhere in the rendered output.
26 |
27 | Below, we have a ToDo
list of Items
. We first get all child Items
—all other children will be ignored. We then find two lists of children that are completed and incomplete.
28 |
29 | ```
30 | import React from 'react';
31 | import { getChildrenByType, getChildren } from 'react-nanny';
32 | import Item from './Item';
33 |
34 | export const ToDoList ({ children }) => {
35 | // Get all children of type Item
36 | const items = getChildrenByType(children, [Item]);
37 |
38 | // Find all incomplete and complete Items
39 | const incomplete = getChildren(items, child => !child.props.completed);
40 | const completed = getChildren(items, child => child.props.completed);
41 |
42 | return (
43 | <>
44 |
50 |
56 | >
57 | );
58 | };
59 | ```
60 |
61 | Summary of Utils
62 |
63 | > Click on each function name for details and examples
64 |
65 |
66 |
67 |
68 | function |
69 | Description |
70 |
71 |
72 | getChild | Gets first child by specified predicate |
getChildDeep | Gets first child by specified predicate (deep search) |
getChildByType | Gets first child by specified type |
getChildByTypeDeep | Gets first child by specified type (deep search) |
getChildren | Gets all children by specified predicate |
getChildrenDeep | Gets all children by specified predicate (deep search) |
getChildrenByType | Gets all children by specified type |
getChildrenByTypeDeep | Gets all children by specified type (deep search) |
getChildrenWithDescendant | Gets all children by specified predicate or that have a descendant node in their lineage which matches the predicate |
getChildrenWithDescendantByType | Gets all children by specified type or that have a descendant node in their lineage which match the specified type |
getDescendantDepth | Gets the depth to the first descendant (or self) of each root child that match the specified predicate |
getDescendantDepthByType | Gets the depth to the first descendant (or self) of each root child that match the specified types |
noEmptyChildrenDeep | Ensure that there is some level of content and not just a bunch of empty divs, spans, etc (deep search) |
overrideProps | Immutably override props of the children of the original component and (optionally) the original component |
overridePropsDeep | Immutably override props of the children and all descendants (deep) |
removeChildren | Removes all children by specified predicate |
removeChildrenDeep | Removes all children by specified predicate (deep search) |
removeChildrenByType | Removes all children by specified type |
removeChildrenByTypeDeep | Removes all children by specified type (deep search) |
typeOfComponent | Gets the string type of the component's {customTypeKey}, string type of the core html (JSX intrinsic) element, or the function type |
73 |
74 |
75 | What can I use to derive types for a comparison?
76 | You can use an imported type, a React.ReactNode
, value from typeOfComponent
, a string type for an HTML (JSX Intrinsic) element, or a string representation of the type by using the customTypeKey
feature.
77 |
78 | Imported Type
79 |
80 | ```
81 | import { getChildByType } from 'react-nanny';
82 | import MyComponent from './MyComponent';
83 |
84 | getChildByType(children, [MyComponent]);
85 | ```
86 |
87 | React.ReactNode
88 |
89 | ```
90 | import { getChildByType, removeChildrenByType } from 'react-nanny';
91 | import MyComponent from './MyComponent';
92 |
93 | const child = getChildByType(children, [MyComponent]);
94 | ...
95 | removeChildrenByType(children, [child]);
96 | ```
97 |
98 | typeOfComponent
99 |
100 | ```
101 | import { getChildByType, removeChildrenByType, typeOfComponent } from 'react-nanny';
102 | import MyComponent from './MyComponent';
103 |
104 | const child = getChildByType(children, [MyComponent]);
105 | ...
106 | removeChildrenByType(children, [typeOfComponent(child)]);
107 | ```
108 |
109 | String type for HTML (JSX Intrinsic) Elements
110 |
111 | ```
112 | import { getChildByType } from 'react-nanny';
113 |
114 | getChildByType(children, ['div']);
115 | ```
116 |
117 | customTypeKey
118 | What the heck is a customTypeKey?
119 | One simple way to be able to define and identify a type on a component and ensure that it is the same in development builds and production builds is to add a constant prop that contains the string type. Consider the following hypothetical component:
120 |
121 | ```
122 | import React from 'react';
123 |
124 | const Hello = ({ __TYPE }) => Hello World!
;
125 |
126 | Hello.defaultProps = {
127 | __TYPE: 'Hello',
128 | };
129 | ```
130 |
131 | The Hello
has a prop __TYPE
that has a value of 'Hello'
. We can query against this value and know that it's reliable regardless of environment.
132 | The customTypeKey
in react-nanny
defines what the name of this prop is. In our example, customTypeKey
would be '__TYPE'
to query using this technique
133 |
134 | ```
135 | import { getChildByType } from 'react-nanny';
136 |
137 | getChildByType(children, ['Hello']);
138 | ```
139 |
140 | Let's say you don't like __TYPE
and what to use your own value such as: CUSTOM
. You can accomplish this by providing the name for the customTypeKey
:
141 |
142 | ```
143 | import { getChildByType } from 'react-nanny';
144 |
145 | getChildByType(children, ['Hello'], { customTypeKey: 'CUSTOM' });
146 | ```
147 |
148 | For more information on how to enforce the integrity of the customTypeKey
, check out my Medium article: Find & Filter React Children By Type
149 |
150 |
151 | forwardRef
152 |
153 | Because React.forwardRef components are higher order components, determining their type becomes tricky. The only way to reliably determine their type is to use the customTypeKey
method outlined above.
154 |
155 |
156 | Package Contents
157 |
158 | Within the module you'll find the following directories and files:
159 |
160 | ```html
161 | package.json
162 | CHANGELOG.md -- history of changes to the module
163 | README.md -- this file
164 | /lib
165 | └───/es5
166 | └───/_private
167 | └───utils.d.ts - 60 Bytes
168 | └───utils.js - 890 Bytes
169 | └───/getChild
170 | └───index.d.ts - 1.2 KB
171 | └───index.js - 1.77 KB
172 | └───/getChildByType
173 | └───index.d.ts - 4.09 KB
174 | └───index.js - 5.97 KB
175 | └───/getChildren
176 | └───index.d.ts - 1.19 KB
177 | └───index.js - 2.02 KB
178 | └───/getChildrenByType
179 | └───index.d.ts - 3.8 KB
180 | └───index.js - 5.31 KB
181 | └───/getChildrenWithDescendant
182 | └───index.d.ts - 626 Bytes
183 | └───index.js - 1.28 KB
184 | └───/getChildrenWithDescendantByType
185 | └───index.d.ts - 2.22 KB
186 | └───index.js - 2.96 KB
187 | └───/getDescendantDepth
188 | └───index.d.ts - 1.12 KB
189 | └───index.js - 2.4 KB
190 | └───/getDescendantDepthByType
191 | └───index.d.ts - 2.35 KB
192 | └───index.js - 3.86 KB
193 | └───index.d.ts - 1.08 KB
194 | └───index.js - 4.19 KB
195 | └───/noEmptyChildren
196 | └───index.d.ts - 1.72 KB
197 | └───index.js - 3.37 KB
198 | └───/overrideProps
199 | └───index.d.ts - 2.68 KB
200 | └───index.js - 4.57 KB
201 | └───/removeChildren
202 | └───index.d.ts - 1.2 KB
203 | └───index.js - 2.51 KB
204 | └───/removeChildrenByType
205 | └───index.d.ts - 3.65 KB
206 | └───index.js - 5.53 KB
207 | └───/typeOfComponent
208 | └───index.d.ts - 603 Bytes
209 | └───index.js - 1.89 KB
210 | └───types.d.ts - 240 Bytes
211 | └───types.js - 77 Bytes
212 | └───/es6
213 | └───/_private
214 | └───utils.d.ts - 60 Bytes
215 | └───utils.js - 681 Bytes
216 | └───/getChild
217 | └───index.d.ts - 1.2 KB
218 | └───index.js - 1.58 KB
219 | └───/getChildByType
220 | └───index.d.ts - 4.09 KB
221 | └───index.js - 5.68 KB
222 | └───/getChildren
223 | └───index.d.ts - 1.19 KB
224 | └───index.js - 1.82 KB
225 | └───/getChildrenByType
226 | └───index.d.ts - 3.8 KB
227 | └───index.js - 5.03 KB
228 | └───/getChildrenWithDescendant
229 | └───index.d.ts - 626 Bytes
230 | └───index.js - 1.1 KB
231 | └───/getChildrenWithDescendantByType
232 | └───index.d.ts - 2.22 KB
233 | └───index.js - 2.76 KB
234 | └───/getDescendantDepth
235 | └───index.d.ts - 1.12 KB
236 | └───index.js - 2.25 KB
237 | └───/getDescendantDepthByType
238 | └───index.d.ts - 2.35 KB
239 | └───index.js - 3.67 KB
240 | └───index.d.ts - 1.08 KB
241 | └───index.js - 892 Bytes
242 | └───/noEmptyChildren
243 | └───index.d.ts - 1.72 KB
244 | └───index.js - 3.15 KB
245 | └───/overrideProps
246 | └───index.d.ts - 2.68 KB
247 | └───index.js - 4.36 KB
248 | └───/removeChildren
249 | └───index.d.ts - 1.2 KB
250 | └───index.js - 2.28 KB
251 | └───/removeChildrenByType
252 | └───index.d.ts - 3.65 KB
253 | └───index.js - 5.23 KB
254 | └───/typeOfComponent
255 | └───index.d.ts - 603 Bytes
256 | └───index.js - 1.75 KB
257 | └───types.d.ts - 240 Bytes
258 | └───types.js - 11 Bytes
259 | ````
--------------------------------------------------------------------------------
/bin/generateReadme.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | /* eslint-disable no-undef */
4 | const ejs = require('ejs');
5 | const { copyFileSync, existsSync, readdirSync, readFileSync, writeFileSync } = require('fs');
6 | const { join, resolve } = require('path');
7 | const dirTree = require('directory-tree');
8 | const { parseTags, removeTags } = require('jsdoc-parse-plus');
9 | const htmlEncode = require('js-htmlencode').htmlEncode;
10 | const { logStatus, logStyle, logSuccess } = require('console-log-it');
11 |
12 | const first = (array, defaultValue) => array && array[0] || defaultValue;
13 | const isNotNullOrEmpty = value => {
14 | if (value === null || value === undefined || (typeof value === 'string' && value === '')) return false;
15 |
16 | if (Array.isArray(value)) {
17 | return value.length > 0;
18 | }
19 |
20 | if (typeof value === 'string' && value.length <= 0) return false;
21 |
22 | return true;
23 | };
24 |
25 | /**
26 | * Available docgen tags:
27 | * @docgen_types - code wrapped display of supported types
28 | * @docgen_description_note - note about the util (blockquote) to go under the description
29 | * @docgen_note - note about the util (blockquote)
30 | * @docgen_details - Any extra details to say about the function that you don't want in a note blockquote
31 | * @docgen_import - override for the import
32 | * @docgen_imp_note - note about the import
33 | */
34 | const customTags = [
35 | '@docgen_types',
36 | '@docgen_note',
37 | 'docgen_description_note',
38 | '@docgen_details',
39 | '@docgen_import',
40 | '@docgen_imp_note',
41 | ];
42 |
43 | const index = async () => {
44 | logStyle('bgWhite')(' Generating README.md ');
45 | console.log();
46 |
47 | const root = join(__dirname, '..');
48 | const src = join(__dirname, '..', 'src');
49 | const dist = join(__dirname, '..', 'dist');
50 | const lib = join(__dirname, '..', 'dist', 'lib');
51 | const es5 = join(__dirname, '..', 'dist', 'lib', 'es5');
52 | const es6 = join(__dirname, '..', 'dist', 'lib', 'es6');
53 |
54 | const reading = logStatus({
55 | indent: 2,
56 | tagColor: 'blue',
57 | tagMessage: 'Reading',
58 | });
59 |
60 | const writing = logStatus({
61 | indent: 2,
62 | tagColor: 'magenta',
63 | tagMessage: 'Writing',
64 | });
65 |
66 | const dirs = readdirSync(src).filter(x => !x.includes('.') && !x.startsWith('_'));
67 | let utils = [];
68 |
69 | const tags = [
70 | '@description',
71 | '@since',
72 | '@param',
73 | '@returns',
74 | '@example',
75 | '@see',
76 | '@deprecated',
77 | ...customTags,
78 | ];
79 |
80 | reading('Source directories...\n');
81 | for (const dir of dirs) {
82 | logStyle('cyan')(' ' + dir);
83 | const functions = Array.from(readFileSync(join(src, dir, 'index.ts'), 'utf8').toString().matchAll(/\/\*\*(\n|\r\n)( \*(.*)(\n|\r\n))* \*\/(\n|\r\n)(.*)/gm)).reduce((accumulator, item) => [...accumulator, item[0]] , []);
84 |
85 | for (const func of functions) {
86 | utils = [...utils, { ...getFunctionNameFromExpression(func), ...parseTags(func, tags) }];
87 | }
88 | }
89 |
90 | console.log();
91 | reading('package.json: ' + join(dist, 'package.json...'));
92 | const packageData = require(join(dist, 'package.json'));
93 | const tree = dirTree(lib);
94 |
95 | const formatBytes = function(a, b) {
96 | if (a === 0) return '0 Bytes';
97 | const c = 1024,
98 | d = b || 2,
99 | e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
100 | f = Math.floor(Math.log(a) / Math.log(c));
101 | return parseFloat((a / Math.pow(c, f)).toFixed(d)) + ' ' + e[f];
102 | };
103 |
104 | const templateData = {
105 | utils,
106 | fileTree: tree,
107 | package: packageData,
108 | formatBytes,
109 | generateTable,
110 | generateSummaryTable,
111 | };
112 |
113 | const file = resolve(__dirname, './readme.ejs');
114 |
115 | writing('README.md files...');
116 | ejs.renderFile(file, templateData, (err, output) => {
117 | if (err) {
118 | console.log(err);
119 | }
120 | writeFileSync(join(dist, 'README.md'), output);
121 | writeFileSync(join(root, 'README.md'), output);
122 | });
123 |
124 | generateIndividualReadMes(utils, packageData.name);
125 |
126 | sanitizeDTS(dirs, es5);
127 | sanitizeDTS(dirs, es6);
128 |
129 | if (existsSync(join(__dirname, '..', 'LICENSE'))) {
130 | copyFileSync(join(__dirname, '..', 'LICENSE'), join(__dirname, '..', 'dist', 'LICENSE'));
131 | }
132 |
133 | if (existsSync(join(__dirname, '..', 'CHANGELOG.md'))) {
134 | copyFileSync(join(__dirname, '..', 'CHANGELOG.md'), join(__dirname, '..', 'dist', 'CHANGELOG.md'));
135 | }
136 |
137 | console.log();
138 | logSuccess()('Compiled & README.md generated');
139 | };
140 |
141 | const generateIndividualReadMes = (utils, packageName) => utils.forEach(util => {
142 | const src = join(__dirname, '..', 'src', util.name.replace('Deep', ''), util.name.includes('Deep') ? 'README-deep.md' : 'README.md');
143 | writeFileSync(src, generateTable(util, packageName));
144 | });
145 |
146 | const generateTable = (util, packageName) => {
147 | const getValue = key => {
148 | if (util[key]?.value?.length > 0) {
149 | const startsWithTag = new RegExp(/^ *<.*?>/g);
150 | const endsWithTag = new RegExp(/<\/.*?>$/g);
151 | return startsWithTag.test(util[key].value) && endsWithTag.test(util[key].value) ? util[key].value : `${util[key].value}
\n`;
152 | }
153 |
154 | return '';
155 | };
156 |
157 | const getNotes = key => {
158 | if (util[key] && Array.isArray(util[key])) {
159 | return util[key].map(note => `${note.value}
`).join('');
160 | } else if (util[key]) {
161 | return `${util[key].value}
`;
162 | }
163 |
164 | return '';
165 | };
166 |
167 | const description = getValue('description');
168 | const since = util.since ? `Since ${util.since.value}
\n` : '';
169 | const hasDefault = util.param.some(x => x.defaultValue !== undefined);
170 | const types = util.docgen_types ? `Supporting Types
\n\n\`\`\`\n${util.docgen_types.value}\n\`\`\`` : '';
171 | const details = getValue('docgen_details');
172 |
173 | const notes = getNotes('docgen_note');
174 | const descriptionNote = getNotes('docgen_description_note');
175 | const importNote = getNotes('docgen_imp_note');
176 |
177 |
178 | let examples = existsSync(join(__dirname, '..', 'src', util.name, 'EXAMPLES.md')) ? '\n\n' + readFileSync(join(__dirname, '..', 'src', util.name, 'EXAMPLES.md'), 'utf8') + '\n\n' : '';
179 |
180 | if (isNotNullOrEmpty(util.example)) {
181 | examples = examples + '\n\n' + `
182 |
183 | \`\`\`
184 | ${util.example.map(x => x.value).join('\n')}
185 | \`\`\`
186 |
187 | `;
188 | }
189 |
190 | if (isNotNullOrEmpty(examples)) {
191 | examples = 'Examples
\n\n' + examples;
192 | }
193 |
194 | const _import = `
195 | Import
196 |
197 | \`\`\`
198 | import ${isNotNullOrEmpty(util.docgen_import) ? util.docgen_import.value : `{ ${util.name} }`} from '${packageName}';
199 | \`\`\`
200 |
201 | `;
202 |
203 | return (
204 | '\n\n' +
205 | `${util.name}${util.generic ? `<${util.generic}>` : ''}
` +
206 | '\n' +
207 | description +
208 | descriptionNote +
209 | since +
210 | `
211 |
212 |
213 | Param |
214 | Type | ` +
215 | (hasDefault ? 'Default | ' : '') +
216 | `
217 |
218 | ` +
219 | util.param.map(x => (
220 | `${x.name}${x.optional ? ' (optional)' : ''} ${x.description} | ` +
221 | `${htmlEncode(x.type)} | ` +
222 | (hasDefault ? `${x.optional && x.defaultValue !== undefined ? x.defaultValue : ''} | ` : '') +
223 | '
'
224 | )).join('') +
225 | `
226 |
` +
227 | `Returns: ${htmlEncode(util.returns.raw.replace('@returns', '').trim())}
` +
228 | notes +
229 | types +
230 | details +
231 | _import +
232 | importNote +
233 | examples
234 | );
235 | };
236 |
237 | const generateSummaryTable = utils => (
238 | `
239 |
240 |
241 | function |
242 | Description |
243 |
244 |
245 | ` +
246 | utils.map(x => (
247 | `${x.name} | ` +
248 | `${x.description ? x.description.value : ''} |
`
249 | ))
250 | .join('') +
251 | `
252 |
`
253 | );
254 |
255 | /**
256 | * Remove anything from jsdoc comments that is used for documentation generation only
257 | *
258 | * @param {string[]} dirs The directory names
259 | * @param {string} path The path to the directories
260 | */
261 | const sanitizeDTS = (dirs, path) => {
262 | logStatus({
263 | indent: 2,
264 | tagColor: 'magenta',
265 | tagMessage: 'Sanitizing',
266 | })('*.d.ts: ' + path + '...');
267 |
268 | for (const dir of dirs) {
269 | let file = readFileSync(join(path, dir, 'index.d.ts'), 'utf8');
270 | const matches = Array.from(file.matchAll(/@docgen_default +(.*)/g));
271 |
272 | for (const match of matches) {
273 | file = file.replace(match[0], ' ');
274 | }
275 |
276 | file = removeTags(file, customTags);
277 | writeFileSync(join(path, dir, 'index.d.ts'), file);
278 | }
279 | };
280 |
281 | /**
282 | * Gets the function name from a function expression string
283 | * @param {string} func - The function expression string
284 | * @returns {{name: string, generic?: string}}
285 | */
286 | const getFunctionNameFromExpression = func => {
287 | const name = first(first(func.match(/export const (.*) =/), '').split('='), '').replace('export const ', '').trim();
288 | const genericMatch = func.match(/export const (.*?) = <(.*?)>/);
289 | const generic = genericMatch && genericMatch[2] || undefined;
290 |
291 | return { name, generic };
292 | };
293 |
294 | index();
--------------------------------------------------------------------------------
/bin/readme.ejs:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.com/TheSpicyMeatball/react-nanny)
2 | [](https://coveralls.io/github/TheSpicyMeatball/react-nanny?branch=main)
3 |
4 | # <%= package.name %>
5 |
6 | > <%= package.description %>
7 |
8 | Hello friend. Have you ever had the need to:
9 |
10 |
11 | - ...query a set of React Children by type or otherwise?
12 | - ...reject and remove some of your children for whatever [judgement free] reason?
13 | - ...ensure that your children return content at some level?
14 |
15 |
16 | If you answered yes to any of those questions, then it sounds like your children could use a nanny to help bring order to the chaos...
17 |
18 | Version: <%= package.version %>
19 |
20 | Dependencies
21 |
22 | react-nanny
doesn't have any dependencies. However, it does have a peer dependency of "react": ">=16.0.0"
which you most likely satisfy if you're the kind of person who's looking for utils for React children.
23 |
24 | Example
25 | This is simple example of how you can program defensively (your consumer can't just throw anything unexpected in children and have it render) and it shows how you can manipulate your children to place them anywhere in the rendered output.
26 |
27 | Below, we have a ToDo
list of Items
. We first get all child Items
—all other children will be ignored. We then find two lists of children that are completed and incomplete.
28 |
29 | ```
30 | import React from 'react';
31 | import { getChildrenByType, getChildren } from 'react-nanny';
32 | import Item from './Item';
33 |
34 | export const ToDoList ({ children }) => {
35 | // Get all children of type Item
36 | const items = getChildrenByType(children, [Item]);
37 |
38 | // Find all incomplete and complete Items
39 | const incomplete = getChildren(items, child => !child.props.completed);
40 | const completed = getChildren(items, child => child.props.completed);
41 |
42 | return (
43 | <>
44 |
50 |
56 | >
57 | );
58 | };
59 | ```
60 |
61 | Summary of Utils
62 |
63 | > Click on each function name for details and examples
64 |
65 | <%- generateSummaryTable(utils) %>
66 |
67 | What can I use to derive types for a comparison?
68 | You can use an imported type, a React.ReactNode
, value from typeOfComponent
, a string type for an HTML (JSX Intrinsic) element, or a string representation of the type by using the customTypeKey
feature.
69 |
70 | Imported Type
71 |
72 | ```
73 | import { getChildByType } from 'react-nanny';
74 | import MyComponent from './MyComponent';
75 |
76 | getChildByType(children, [MyComponent]);
77 | ```
78 |
79 | React.ReactNode
80 |
81 | ```
82 | import { getChildByType, removeChildrenByType } from 'react-nanny';
83 | import MyComponent from './MyComponent';
84 |
85 | const child = getChildByType(children, [MyComponent]);
86 | ...
87 | removeChildrenByType(children, [child]);
88 | ```
89 |
90 | typeOfComponent
91 |
92 | ```
93 | import { getChildByType, removeChildrenByType, typeOfComponent } from 'react-nanny';
94 | import MyComponent from './MyComponent';
95 |
96 | const child = getChildByType(children, [MyComponent]);
97 | ...
98 | removeChildrenByType(children, [typeOfComponent(child)]);
99 | ```
100 |
101 | String type for HTML (JSX Intrinsic) Elements
102 |
103 | ```
104 | import { getChildByType } from 'react-nanny';
105 |
106 | getChildByType(children, ['div']);
107 | ```
108 |
109 | customTypeKey
110 | What the heck is a customTypeKey?
111 | One simple way to be able to define and identify a type on a component and ensure that it is the same in development builds and production builds is to add a constant prop that contains the string type. Consider the following hypothetical component:
112 |
113 | ```
114 | import React from 'react';
115 |
116 | const Hello = ({ __TYPE }) => Hello World!
;
117 |
118 | Hello.defaultProps = {
119 | __TYPE: 'Hello',
120 | };
121 | ```
122 |
123 | The Hello
has a prop __TYPE
that has a value of 'Hello'
. We can query against this value and know that it's reliable regardless of environment.
124 | The customTypeKey
in react-nanny
defines what the name of this prop is. In our example, customTypeKey
would be '__TYPE'
to query using this technique
125 |
126 | ```
127 | import { getChildByType } from 'react-nanny';
128 |
129 | getChildByType(children, ['Hello']);
130 | ```
131 |
132 | Let's say you don't like __TYPE
and what to use your own value such as: CUSTOM
. You can accomplish this by providing the name for the customTypeKey
:
133 |
134 | ```
135 | import { getChildByType } from 'react-nanny';
136 |
137 | getChildByType(children, ['Hello'], { customTypeKey: 'CUSTOM' });
138 | ```
139 |
140 | For more information on how to enforce the integrity of the customTypeKey
, check out my Medium article: Find & Filter React Children By Type
141 |
142 |
143 | forwardRef
144 |
145 | Because React.forwardRef components are higher order components, determining their type becomes tricky. The only way to reliably determine their type is to use the customTypeKey
method outlined above.
146 |
147 |
148 | Package Contents
149 |
150 | Within the module you'll find the following directories and files:
151 |
152 | ```html
153 | package.json
154 | CHANGELOG.md -- history of changes to the module
155 | README.md -- this file
156 | /<%= fileTree.name -%><% fileTree.children.forEach( function(child){ if (child.type == 'directory') { %>
157 | └───/<%= child.name -%><% child.children.forEach(function(grandChild){ if (grandChild.type == 'directory') { %>
158 | └───/<%= grandChild.name -%><% grandChild.children.forEach(function(greatGrand){ %>
159 | └───<%= greatGrand.name -%> - <%= formatBytes(greatGrand.size) -%><% }) } else { %>
160 | └───<%= grandChild.name -%> - <%= formatBytes(grandChild.size) -%><% } })} else { %>
161 | └───<%= child.name -%> - <%= formatBytes(child.size) -%><% }}) %>
162 | ````
--------------------------------------------------------------------------------
/dist/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [2.16.0] - 2025-02-19
2 | - Updated typeOfComponent() to optionally use type for React19.
3 |
4 | ## [2.15.0] - 2022-10-11
5 | - Added {skipWhenFound} to {getChildrenByType} to stop searching lower in the tree when a match is found.
6 |
7 | ## [2.14.1] - 2022-10-11
8 | - Removed toChildrenArray because it caused issues in getting nested children in an array.
9 |
10 | ## [2.14.0] - 2022-04-08
11 | - Created private toChildrenArray util to simplify code
12 |
13 | ## [2.13.0] - 2022-04-08
14 | - Added support for functions as children
15 |
16 | ## [2.12.0] - 2022-01-11
17 | - Added support for React.forwardRef
18 |
19 | ## [2.11.0] - 2021-11-08
20 | - Added example to main README.md
21 |
22 | ## [2.9.0] - 2021-07-21
23 | - Added overridePropsDeep
24 |
25 | ## [2.9.0] - 2021-05-17
26 | - Added typing generics for child items
27 |
28 | ## [2.8.0] - 2021-04-16
29 | - Added overload for 'ByType' functions to accept single type arg as well as an array of types
30 |
31 | ## [2.6.0] - 2021-01-19
32 | - Added getChildrenWithDescendant
33 | - Added getChildrenWithDescendantByType
34 | - Added getDescendantDepth
35 | - Added getDescendantDepthByType
36 | - Added type NannyNode
37 |
38 | ## [2.5.0] - 2021-01-04
39 | - Removed unused code and unreachable branches
40 |
41 | ## [2.4.0] - 2021-01-02
42 | - Integrated with Travis CI and Coveralls
43 | - Display build and coverage on README.md
44 |
45 | ## [2.3.1] - 2020-12-30
46 | - Moved TypeScript T on overrideProps util from param to generic for util
47 |
48 | ## [2.3.0] - 2020-12-30
49 | - Added overrideProps util
50 |
51 | ## [2.2.0] - 2020-12-29
52 | - Added the ability to infer type from React.ReactNode
53 |
54 | ## [2.1.0] - 2020-12-23
55 | - Added defined and exported config types
56 |
57 | ## [2.0.0] - 2020-12-22
58 | - Added ability to search for type as passed in component function/class
59 | - Moved customTypeKey into configuration object
60 |
61 | ## [1.1.0] - 2020-12-21
62 | - Documentation formatting updated
63 | - Descriptions for utils updated for clarity
64 |
65 | ## [1.0.0] - 2020-12-20
66 | - Initial release
--------------------------------------------------------------------------------
/dist/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Michael Paravano
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS, AUTHORS' EMPLOYER(S), OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 | DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/dist/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.com/TheSpicyMeatball/react-nanny)
2 | [](https://coveralls.io/github/TheSpicyMeatball/react-nanny?branch=main)
3 |
4 | # react-nanny
5 |
6 | > Utils to manage your React Children; find and filter children by type or custom function, enforce child content, and more!
7 |
8 | Hello friend. Have you ever had the need to:
9 |
10 |
11 | - ...query a set of React Children by type or otherwise?
12 | - ...reject and remove some of your children for whatever [judgement free] reason?
13 | - ...ensure that your children return content at some level?
14 |
15 |
16 | If you answered yes to any of those questions, then it sounds like your children could use a nanny to help bring order to the chaos...
17 |
18 | Version: 2.15.0
19 |
20 | Dependencies
21 |
22 | react-nanny
doesn't have any dependencies. However, it does have a peer dependency of "react": ">=16.0.0"
which you most likely satisfy if you're the kind of person who's looking for utils for React children.
23 |
24 | Example
25 | This is simple example of how you can program defensively (your consumer can't just throw anything unexpected in children and have it render) and it shows how you can manipulate your children to place them anywhere in the rendered output.
26 |
27 | Below, we have a ToDo
list of Items
. We first get all child Items
—all other children will be ignored. We then find two lists of children that are completed and incomplete.
28 |
29 | ```
30 | import React from 'react';
31 | import { getChildrenByType, getChildren } from 'react-nanny';
32 | import Item from './Item';
33 |
34 | export const ToDoList ({ children }) => {
35 | // Get all children of type Item
36 | const items = getChildrenByType(children, [Item]);
37 |
38 | // Find all incomplete and complete Items
39 | const incomplete = getChildren(items, child => !child.props.completed);
40 | const completed = getChildren(items, child => child.props.completed);
41 |
42 | return (
43 | <>
44 |
50 |
56 | >
57 | );
58 | };
59 | ```
60 |
61 | Summary of Utils
62 |
63 | > Click on each function name for details and examples
64 |
65 |
66 |
67 |
68 | function |
69 | Description |
70 |
71 |
72 | getChild | Gets first child by specified predicate |
getChildDeep | Gets first child by specified predicate (deep search) |
getChildByType | Gets first child by specified type |
getChildByTypeDeep | Gets first child by specified type (deep search) |
getChildren | Gets all children by specified predicate |
getChildrenDeep | Gets all children by specified predicate (deep search) |
getChildrenByType | Gets all children by specified type |
getChildrenByTypeDeep | Gets all children by specified type (deep search) |
getChildrenWithDescendant | Gets all children by specified predicate or that have a descendant node in their lineage which matches the predicate |
getChildrenWithDescendantByType | Gets all children by specified type or that have a descendant node in their lineage which match the specified type |
getDescendantDepth | Gets the depth to the first descendant (or self) of each root child that match the specified predicate |
getDescendantDepthByType | Gets the depth to the first descendant (or self) of each root child that match the specified types |
noEmptyChildrenDeep | Ensure that there is some level of content and not just a bunch of empty divs, spans, etc (deep search) |
overrideProps | Immutably override props of the children of the original component and (optionally) the original component |
overridePropsDeep | Immutably override props of the children and all descendants (deep) |
removeChildren | Removes all children by specified predicate |
removeChildrenDeep | Removes all children by specified predicate (deep search) |
removeChildrenByType | Removes all children by specified type |
removeChildrenByTypeDeep | Removes all children by specified type (deep search) |
typeOfComponent | Gets the string type of the component's {customTypeKey}, string type of the core html (JSX intrinsic) element, or the function type |
73 |
74 |
75 | What can I use to derive types for a comparison?
76 | You can use an imported type, a React.ReactNode
, value from typeOfComponent
, a string type for an HTML (JSX Intrinsic) element, or a string representation of the type by using the customTypeKey
feature.
77 |
78 | Imported Type
79 |
80 | ```
81 | import { getChildByType } from 'react-nanny';
82 | import MyComponent from './MyComponent';
83 |
84 | getChildByType(children, [MyComponent]);
85 | ```
86 |
87 | React.ReactNode
88 |
89 | ```
90 | import { getChildByType, removeChildrenByType } from 'react-nanny';
91 | import MyComponent from './MyComponent';
92 |
93 | const child = getChildByType(children, [MyComponent]);
94 | ...
95 | removeChildrenByType(children, [child]);
96 | ```
97 |
98 | typeOfComponent
99 |
100 | ```
101 | import { getChildByType, removeChildrenByType, typeOfComponent } from 'react-nanny';
102 | import MyComponent from './MyComponent';
103 |
104 | const child = getChildByType(children, [MyComponent]);
105 | ...
106 | removeChildrenByType(children, [typeOfComponent(child)]);
107 | ```
108 |
109 | String type for HTML (JSX Intrinsic) Elements
110 |
111 | ```
112 | import { getChildByType } from 'react-nanny';
113 |
114 | getChildByType(children, ['div']);
115 | ```
116 |
117 | customTypeKey
118 | What the heck is a customTypeKey?
119 | One simple way to be able to define and identify a type on a component and ensure that it is the same in development builds and production builds is to add a constant prop that contains the string type. Consider the following hypothetical component:
120 |
121 | ```
122 | import React from 'react';
123 |
124 | const Hello = ({ __TYPE }) => Hello World!
;
125 |
126 | Hello.defaultProps = {
127 | __TYPE: 'Hello',
128 | };
129 | ```
130 |
131 | The Hello
has a prop __TYPE
that has a value of 'Hello'
. We can query against this value and know that it's reliable regardless of environment.
132 | The customTypeKey
in react-nanny
defines what the name of this prop is. In our example, customTypeKey
would be '__TYPE'
to query using this technique
133 |
134 | ```
135 | import { getChildByType } from 'react-nanny';
136 |
137 | getChildByType(children, ['Hello']);
138 | ```
139 |
140 | Let's say you don't like __TYPE
and what to use your own value such as: CUSTOM
. You can accomplish this by providing the name for the customTypeKey
:
141 |
142 | ```
143 | import { getChildByType } from 'react-nanny';
144 |
145 | getChildByType(children, ['Hello'], { customTypeKey: 'CUSTOM' });
146 | ```
147 |
148 | For more information on how to enforce the integrity of the customTypeKey
, check out my Medium article: Find & Filter React Children By Type
149 |
150 |
151 | forwardRef
152 |
153 | Because React.forwardRef components are higher order components, determining their type becomes tricky. The only way to reliably determine their type is to use the customTypeKey
method outlined above.
154 |
155 |
156 | Package Contents
157 |
158 | Within the module you'll find the following directories and files:
159 |
160 | ```html
161 | package.json
162 | CHANGELOG.md -- history of changes to the module
163 | README.md -- this file
164 | /lib
165 | └───/es5
166 | └───/_private
167 | └───utils.d.ts - 60 Bytes
168 | └───utils.js - 890 Bytes
169 | └───/getChild
170 | └───index.d.ts - 1.2 KB
171 | └───index.js - 1.77 KB
172 | └───/getChildByType
173 | └───index.d.ts - 4.09 KB
174 | └───index.js - 5.97 KB
175 | └───/getChildren
176 | └───index.d.ts - 1.19 KB
177 | └───index.js - 2.02 KB
178 | └───/getChildrenByType
179 | └───index.d.ts - 3.8 KB
180 | └───index.js - 5.31 KB
181 | └───/getChildrenWithDescendant
182 | └───index.d.ts - 626 Bytes
183 | └───index.js - 1.28 KB
184 | └───/getChildrenWithDescendantByType
185 | └───index.d.ts - 2.22 KB
186 | └───index.js - 2.96 KB
187 | └───/getDescendantDepth
188 | └───index.d.ts - 1.12 KB
189 | └───index.js - 2.4 KB
190 | └───/getDescendantDepthByType
191 | └───index.d.ts - 2.35 KB
192 | └───index.js - 3.86 KB
193 | └───index.d.ts - 1.08 KB
194 | └───index.js - 4.19 KB
195 | └───/noEmptyChildren
196 | └───index.d.ts - 1.72 KB
197 | └───index.js - 3.37 KB
198 | └───/overrideProps
199 | └───index.d.ts - 2.68 KB
200 | └───index.js - 4.57 KB
201 | └───/removeChildren
202 | └───index.d.ts - 1.2 KB
203 | └───index.js - 2.51 KB
204 | └───/removeChildrenByType
205 | └───index.d.ts - 3.65 KB
206 | └───index.js - 5.53 KB
207 | └───/typeOfComponent
208 | └───index.d.ts - 603 Bytes
209 | └───index.js - 1.89 KB
210 | └───types.d.ts - 240 Bytes
211 | └───types.js - 77 Bytes
212 | └───/es6
213 | └───/_private
214 | └───utils.d.ts - 60 Bytes
215 | └───utils.js - 681 Bytes
216 | └───/getChild
217 | └───index.d.ts - 1.2 KB
218 | └───index.js - 1.58 KB
219 | └───/getChildByType
220 | └───index.d.ts - 4.09 KB
221 | └───index.js - 5.68 KB
222 | └───/getChildren
223 | └───index.d.ts - 1.19 KB
224 | └───index.js - 1.82 KB
225 | └───/getChildrenByType
226 | └───index.d.ts - 3.8 KB
227 | └───index.js - 5.03 KB
228 | └───/getChildrenWithDescendant
229 | └───index.d.ts - 626 Bytes
230 | └───index.js - 1.1 KB
231 | └───/getChildrenWithDescendantByType
232 | └───index.d.ts - 2.22 KB
233 | └───index.js - 2.76 KB
234 | └───/getDescendantDepth
235 | └───index.d.ts - 1.12 KB
236 | └───index.js - 2.25 KB
237 | └───/getDescendantDepthByType
238 | └───index.d.ts - 2.35 KB
239 | └───index.js - 3.67 KB
240 | └───index.d.ts - 1.08 KB
241 | └───index.js - 892 Bytes
242 | └───/noEmptyChildren
243 | └───index.d.ts - 1.72 KB
244 | └───index.js - 3.15 KB
245 | └───/overrideProps
246 | └───index.d.ts - 2.68 KB
247 | └───index.js - 4.36 KB
248 | └───/removeChildren
249 | └───index.d.ts - 1.2 KB
250 | └───index.js - 2.28 KB
251 | └───/removeChildrenByType
252 | └───index.d.ts - 3.65 KB
253 | └───index.js - 5.23 KB
254 | └───/typeOfComponent
255 | └───index.d.ts - 603 Bytes
256 | └───index.js - 1.75 KB
257 | └───types.d.ts - 240 Bytes
258 | └───types.js - 11 Bytes
259 | ````
--------------------------------------------------------------------------------
/dist/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-nanny",
3 | "version": "2.16.0",
4 | "description": "Utils to manage your React Children; find and filter children by type or custom function, enforce child content, and more!",
5 | "main": "lib/es5/index.js",
6 | "module": "lib/es6/index.js",
7 | "sideEffects": false,
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/TheSpicyMeatball/react-nanny.git"
11 | },
12 | "files": [
13 | "lib",
14 | "CHANGELOG.md",
15 | "LICENSE",
16 | "package.json",
17 | "README.md"
18 | ],
19 | "keywords": [
20 | "react",
21 | "children",
22 | "utils",
23 | "utilities",
24 | "type",
25 | "find"
26 | ],
27 | "author": "Michael Paravano",
28 | "license": "MIT",
29 | "bugs": {
30 | "url": "https://github.com/TheSpicyMeatball/react-nanny/issues"
31 | },
32 | "homepage": "https://github.com/TheSpicyMeatball/react-nanny#readme",
33 | "peerDependencies": {
34 | "react": ">=16.0.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-nanny",
3 | "description": "Utils to manage your React Children; find and filter children by type or custom function, enforce child content, and more!",
4 | "scripts": {
5 | "compile": "npm run lint && rm -rf dist/lib && tsc && tsc --build tsconfig.es5.json && npm run readme",
6 | "lint": "eslint . --ext .ts",
7 | "readme": "node bin/generateReadme.js",
8 | "test": "jest",
9 | "test:coverage": "jest --coverage",
10 | "test:coveralls": "jest --coverage --coverageReporters=text-lcov | coveralls"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/TheSpicyMeatball/react-nanny.git"
15 | },
16 | "keywords": [
17 | "react",
18 | "children"
19 | ],
20 | "author": "Michael Paravano",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/TheSpicyMeatball/react-nanny/issues"
24 | },
25 | "homepage": "https://github.com/TheSpicyMeatball/react-nanny#readme",
26 | "devDependencies": {
27 | "@types/jest": "^26.0.19",
28 | "@types/react": "^17.0.0",
29 | "@typescript-eslint/eslint-plugin": "^4.11.1",
30 | "@typescript-eslint/parser": "^4.11.1",
31 | "console-log-it": "^1.0.0",
32 | "copyfiles": "^2.4.1",
33 | "coveralls": "^3.1.0",
34 | "directory-tree": "^2.2.5",
35 | "ejs": "^3.1.5",
36 | "eslint": "^7.17.0",
37 | "hosted-git-info": ">=2.8.9",
38 | "jest": "^26.6.3",
39 | "js-htmlencode": "^0.3.0",
40 | "jsdoc-parse-plus": "^1.3.0",
41 | "lodash": "^4.17.21",
42 | "nyc": "^15.1.0",
43 | "typescript": "^4.1.3"
44 | },
45 | "dependencies": {
46 | "react": "^17.0.1"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/_private/utils.test.js:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | const { overrideProps } = require('../../dist/lib/es5/index');
3 | const { processTypes } = require('../../dist/lib/es5/_private/utils');
4 |
5 | describe('processTypes', () => {
6 | React.createElement = x => x;
7 |
8 | test('function', () => {
9 | const func = () => 'test';
10 | func.type = () => 'type';
11 |
12 | expect(processTypes([func])).toStrictEqual([func.type]);
13 | });
14 |
15 | test('object', () => {
16 | expect(processTypes([{}])).toStrictEqual([undefined]);
17 |
18 | const fwdRef = {
19 | type: {
20 | $$typeof: Symbol('react.forward_ref'),
21 | render: (props, ref) => (({ props: { __TYPE: 'CustomComponent' }})),
22 | },
23 | };
24 | expect(processTypes([fwdRef])).toStrictEqual(['CustomComponent']);
25 | });
26 | });
--------------------------------------------------------------------------------
/src/_private/utils.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { typeOfComponent } from '../typeOfComponent';
3 |
4 | export const processTypes = (types: any[]) : any[] => types.map(x => {
5 | switch (typeof x) {
6 | case 'string':
7 | return x;
8 |
9 | case 'function':
10 | return typeOfComponent(React.createElement(x));
11 |
12 | case 'object':
13 | default: {
14 | const component = React.createElement(x);
15 | const type = typeOfComponent(component);
16 |
17 | if (type === 'react.forward_ref') {
18 | return typeOfComponent((component.type as any).render(component.props, component.ref));
19 | }
20 |
21 | return typeOfComponent(x);
22 | }
23 | }
24 | });
--------------------------------------------------------------------------------
/src/getChild/README-deep.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChildDeep<T=React.ReactNode, TC=React.ReactNode>
4 | Gets first child by specified predicate (deep search)
5 | Since v1.0.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
predicate The predicate to determine if the given child is a match | (child: TC) => boolean |
13 |
Returns: {TChild} - The first matching child
14 | Import
15 |
16 | ```
17 | import { getChildDeep } from 'react-nanny';
18 | ```
19 |
20 | Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | // Finds the first occurrence of a child that has a prop of 'active' set to true
28 | getChildDeep(children, child => child.props.active);
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/src/getChild/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChild<T=React.ReactNode, TC=React.ReactNode>
4 | Gets first child by specified predicate
5 | Since v1.0.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
predicate The predicate to determine if the given child is a match | (child: TChild) => boolean |
13 |
Returns: {TChild} - The first matching child
14 | Import
15 |
16 | ```
17 | import { getChild } from 'react-nanny';
18 | ```
19 |
20 | Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | // Finds the first occurrence of a child that has a prop of 'active' set to true
28 | getChild(children, child => child.props.active);
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/src/getChild/index.deep.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChildDeep } = require('../../dist/lib/es5/index');
5 |
6 | let children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span' }},
22 | ],
23 | },
24 | },
25 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
26 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
27 | { type: 'span' },
28 | { type: 'div' },
29 | ];
30 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
31 |
32 | describe('getChildDeep', () => {
33 | test('Deep find', () => {
34 | expect(getChildDeep(children, child => child && child.props && child.props.active)).toStrictEqual({ props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }});
35 | expect(getChildDeep(children, child => child && child.type === 'span')).toStrictEqual({ type: 'span', props: { children: 'Deep span' }});
36 | });
37 |
38 | test('Change order', () => {
39 | children = [
40 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
41 | { type: 'span', props: { children: 'Outer span' }},
42 | {
43 | props: {
44 | __TYPE: 'div',
45 | children: [
46 | {
47 | props: {
48 | __TYPE: 'div',
49 | children: [
50 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
51 | { type: 'span', props: { children: 'Deep span' }},
52 | { type: 'div' },
53 | ],
54 | },
55 | },
56 | ],
57 | },
58 | },
59 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
60 | { type: 'div' },
61 | { type: 'span' },
62 | ];
63 |
64 | expect(getChildDeep(children, child => child && child.props && child.props.active)).toStrictEqual({ props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }});
65 | expect(getChildDeep(children, child => child && child.type === 'span')).toStrictEqual({ type: 'span', props: { children: 'Outer span' }});
66 | });
67 |
68 | test('Deep find => no props', () => {
69 | expect(getChildDeep({}, child => child && child.type === 'span')).toBe(undefined);
70 | });
71 |
72 | test('Deep find => props => no grandchildren', () => {
73 | const children = [
74 | { props: { __TYPE: 'CustomComponent', active: false }},
75 | { props: { __TYPE: 'CustomComponent', active: true }},
76 | ];
77 | expect(getChildDeep(children, child => child && child.type === 'span')).toBe(undefined);
78 | });
79 |
80 | test('undefined', () => {
81 | expect(getChildDeep(children, child => child && child.props && child.props.bogus)).toBe(undefined);
82 | });
83 | });
--------------------------------------------------------------------------------
/src/getChild/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChild } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | { props: { __TYPE: 'CustomComponent' }},
8 | { props: { __TYPE: 'CustomComponent' }},
9 | { props: { __TYPE: 'Something Else' }},
10 | { props: { TYPE: 'Some TYPE' }},
11 | { type: 'div' },
12 | { type: 'span' },
13 | { type: 'div' },
14 | ];
15 | React.Children.toArray = jest.fn().mockReturnValue(children);
16 |
17 | describe('getChild', () => {
18 | test('Basic find', () => {
19 | expect(getChild(children, child => child.type === 'div')).toStrictEqual(children[4]);
20 | expect(getChild(children, child => child.type === 'span')).toStrictEqual(children[5]);
21 | expect(getChild(children, child => child.props.TYPE === 'Some TYPE')).toStrictEqual(children[3]);
22 | });
23 |
24 | test('undefined', () => {
25 | expect(getChild(children, child => child.type === 'bogus')).toBe(undefined);
26 | });
27 | });
--------------------------------------------------------------------------------
/src/getChild/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { NannyNode } from '../types';
3 |
4 | /**
5 | * Gets first child by specified predicate
6 | *
7 | * @since v1.0.0
8 | * @template T
9 | * @template TC - Type of child
10 | * @param {T} children - JSX children
11 | * @param {(child: TChild) => boolean} predicate - The predicate to determine if the given child is a match
12 | * @returns {TChild} - The first matching child
13 | * @example
14 | * // Finds the first occurrence of a child that has a prop of 'active' set to true
15 | * getChild(children, child => child.props.active);
16 | */
17 | export const getChild = (children: T, predicate: (child: TC) => boolean) : TC =>
18 | React.Children.toArray(children).find(predicate) as TC;
19 |
20 | /**
21 | * Gets first child by specified predicate (deep search)
22 | *
23 | * @since v1.0.0
24 | * @template T
25 | * @template TC - Type of child
26 | * @param {T} children - JSX children
27 | * @param {(child: TC) => boolean} predicate - The predicate to determine if the given child is a match
28 | * @returns {TChild} - The first matching child
29 | * @example
30 | * // Finds the first occurrence of a child that has a prop of 'active' set to true
31 | * getChildDeep(children, child => child.props.active);
32 | */
33 | export const getChildDeep = (children: T, predicate: (child: TC) => boolean) : TC => {
34 | const _children = React.Children.toArray(children);
35 |
36 | for (const child of _children) {
37 | if (predicate(child as TC)) return child as TC;
38 |
39 | if ((child as any).props?.children) {
40 | const result = getChildDeep((child as NannyNode).props.children, predicate);
41 |
42 | if (result) return result;
43 | }
44 | }
45 |
46 | return;
47 | };
--------------------------------------------------------------------------------
/src/getChildByType/README-deep.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChildByTypeDeep<T=React.ReactNode, TC=unknown>
4 | Gets first child by specified type (deep search)
5 | Since v1.0.0 (modified v2.0.0)
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
types Types of children to match | TC | TC[] |
{ customTypeKey: '__TYPE', prioritized: false } (optional) The configuration params | GetChildByTypeConfig |
13 |
Returns: {T} - The first matching child
This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
Supporting Types
14 |
15 | ```
16 | // The configuration type for the util:
17 | // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
18 | // prioritized?: boolean = false - Whether or not the order of types is prioritized
19 |
20 | export type GetChildByTypeConfig = { customTypeKey?: string, prioritized?: boolean };
21 | ```
22 | Import
23 |
24 | ```
25 | import { getChildByTypeDeep, GetChildByTypeConfig } from 'react-nanny';
26 | ```
27 |
28 | GetChildByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
Examples
29 |
30 |
31 |
32 |
33 |
34 | ```
35 | // Finds the first occurrence of either a ToDo (custom component w/defined type as prop), a div, or a React Fragment
36 | getChildByTypeDeep(children, ['ToDo', 'div', 'react.fragment']);
37 |
38 | // Finds the first occurrence of either a MyComponent (custom component - full component passed in), a div, or a React Fragment
39 | import MyComponent from './MyComponent';
40 | getChildByTypeDeep(children, [MyComponent, 'div', 'react.fragment']);
41 |
42 | // Finds the first occurrence of either a ToDo, a div, or a React Fragment with a preference for that order. If ToDo exists, it will return that first. If not, then div, etc.
43 | getChildByTypeDeep(children, ['ToDo', 'div', 'react.fragment'], { prioritized: true });
44 | ```
45 |
46 |
--------------------------------------------------------------------------------
/src/getChildByType/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChildByType<T=React.ReactNode, TC=unknown>
4 | Gets first child by specified type
5 | Since v1.0.0 (modified v2.0.0)
6 |
7 |
8 |
9 | Param |
10 | Type | Default |
11 |
12 | children JSX children | T | |
types Types of children to match | TC | TC[] | |
config (optional) The configuration params | GetChildByTypeConfig | { customTypeKey: '__TYPE', prioritized: false } |
13 |
Returns: {T} - The first matching child
This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
Supporting Types
14 |
15 | ```
16 | // The configuration type for the util:
17 | // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
18 | // prioritized?: boolean = false - Whether or not the order of types is prioritized
19 |
20 | export type GetChildByTypeConfig = { customTypeKey?: string, prioritized?: boolean };
21 | ```
22 | Import
23 |
24 | ```
25 | import { getChildByType, GetChildByTypeConfig } from 'react-nanny';
26 | ```
27 |
28 | GetChildByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
Examples
29 |
30 |
31 |
32 |
33 |
34 | ```
35 | // Finds the first occurrence of either a ToDo (custom component w/defined type as prop), a div, or a React Fragment
36 | getChildByType(children, ['ToDo', 'div', 'react.fragment']);
37 |
38 | // Finds the first occurrence of either a MyComponent (custom component - full component passed in), a div, or a React Fragment
39 | import MyComponent from './MyComponent';
40 | getChildByType(children, [MyComponent, 'div', 'react.fragment']);
41 |
42 | // Finds the first occurrence of either a ToDo, a div, or a React Fragment with a preference for that order. If ToDo exists, it will return that first. If not, then div, etc.
43 | getChildByType(children, ['ToDo', 'div', 'react.fragment'], { prioritized: true });
44 | ```
45 |
46 |
--------------------------------------------------------------------------------
/src/getChildByType/index.deep.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChildByTypeDeep } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span' }},
22 | ],
23 | },
24 | },
25 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
26 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
27 | { props: { CustomKey: 'customTypeKey', children: 'Custom w/customTypeKey' }},
28 | { type: 'span' },
29 | { type: 'div' },
30 | ];
31 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
32 |
33 | describe('getChildByTypeDeep', () => {
34 | test('Deep find => single', () => {
35 | expect(getChildByTypeDeep(children, 'CustomComponent')).toStrictEqual({ props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }});
36 | expect(getChildByTypeDeep(children, 'span')).toStrictEqual({ type: 'span', props: { children: 'Deep span' }});
37 | });
38 |
39 | test('Deep find', () => {
40 | expect(getChildByTypeDeep(children, ['CustomComponent'])).toStrictEqual({ props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }});
41 | expect(getChildByTypeDeep(children, ['span'])).toStrictEqual({ type: 'span', props: { children: 'Deep span' }});
42 | });
43 |
44 | test('Change order', () => {
45 | const children = [
46 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
47 | { type: 'span', props: { children: 'Outer span' }},
48 | {
49 | props: {
50 | __TYPE: 'div',
51 | children: [
52 | {
53 | props: {
54 | __TYPE: 'div',
55 | children: [
56 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
57 | { type: 'span', props: { children: 'Deep span' }},
58 | { type: 'div' },
59 | { props: { TYPE: 'CustomKey', children: 'Child w/customTypeKey' }},
60 | ],
61 | },
62 | },
63 | ],
64 | },
65 | },
66 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
67 | { type: 'div' },
68 | { type: 'span' },
69 | ];
70 |
71 | expect(getChildByTypeDeep(children, ['CustomComponent'])).toStrictEqual({ props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }});
72 | expect(getChildByTypeDeep(children, ['span'])).toStrictEqual({ type: 'span', props: { children: 'Outer span' }});
73 | });
74 |
75 | test('undefined', () => {
76 | expect(getChildByTypeDeep(children, ['bogus'])).toBe(undefined);
77 | });
78 |
79 | test('Prioritized', () => {
80 | expect(getChildByTypeDeep(children, ['span', 'CustomComponent'])).toStrictEqual({ props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }});
81 | expect(getChildByTypeDeep(children, ['span', 'CustomComponent'], { prioritized: false })).toStrictEqual({ props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }});
82 | expect(getChildByTypeDeep(children, ['span', 'CustomComponent'], { prioritized: true })).toStrictEqual({ type: 'span', props: { children: 'Deep span' }});
83 | expect(getChildByTypeDeep(children, ['b', 'em'], { prioritized: true })).toBe(undefined);
84 | });
85 |
86 | test('customTypeKey', () => {
87 | expect(getChildByTypeDeep(children, ['customTypeKey'], { customTypeKey: 'CustomKey' })).toStrictEqual({ props: { CustomKey: 'customTypeKey', children: 'Custom w/customTypeKey' }});
88 | });
89 | });
--------------------------------------------------------------------------------
/src/getChildByType/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChildByType } = require('../../dist/lib/es5/index');
5 |
6 | describe('getChildByType', () => {
7 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
8 |
9 | test('Single', () => {
10 | let children = { props: { __TYPE: 'CustomComponent' }};
11 | expect(getChildByType(children, ['CustomComponent'])).toStrictEqual(children);
12 |
13 | const someFunction = () => 'some function';
14 |
15 | children = [
16 | { props: { __TYPE: 'CustomComponent' }},
17 | { props: { __TYPE: 'CustomComponent' }},
18 | { props: { __TYPE: 'Something Else' }},
19 | { props: { TYPE: 'Some TYPE' }},
20 | { type: 'div' },
21 | someFunction,
22 | ];
23 |
24 | expect(getChildByType(children, 'CustomComponent')).toStrictEqual(children[0]);
25 | expect(getChildByType(children, 'Some TYPE', { customTypeKey: 'TYPE' })).toStrictEqual(children[3]);
26 | expect(getChildByType(children, 'function')).toStrictEqual(someFunction);
27 | });
28 |
29 | test('Custom Components', () => {
30 | let children = { props: { __TYPE: 'CustomComponent' }};
31 | expect(getChildByType(children, ['CustomComponent'])).toStrictEqual(children);
32 |
33 | children = [
34 | { props: { __TYPE: 'CustomComponent' }},
35 | { props: { __TYPE: 'CustomComponent' }},
36 | { props: { __TYPE: 'Something Else' }},
37 | { props: { TYPE: 'Some TYPE' }},
38 | { type: 'div' },
39 | ];
40 |
41 | expect(getChildByType(children, ['CustomComponent'])).toStrictEqual(children[0]);
42 | expect(getChildByType(children, ['Some TYPE'], { customTypeKey: 'TYPE' })).toStrictEqual(children[3]);
43 | expect(getChildByType(children, ['CustomComponent', 'Something Else'])).toStrictEqual(children[0]);
44 | expect(getChildByType(children, ['Something Else', 'div'])).toStrictEqual(children[2]);
45 | });
46 |
47 | test('Standard Html (JSX) Components', () => {
48 | const children = [
49 | { props: { __TYPE: 'CustomComponent' }},
50 | { props: { __TYPE: 'CustomComponent' }},
51 | { props: { __TYPE: 'Something Else' }},
52 | { props: { TYPE: 'Some TYPE' }},
53 | { type: 'div' },
54 | { type: 'span' },
55 | { type: 'div' },
56 | ];
57 | expect(getChildByType(children, ['div'])).toStrictEqual(children[4]);
58 | expect(getChildByType(children, ['span'])).toStrictEqual(children[5]);
59 | expect(getChildByType(children, ['div', 'span'])).toStrictEqual(children[4]);
60 | });
61 |
62 | test('Mixed', () => {
63 | const children = [
64 | { props: { __TYPE: 'CustomComponent' }},
65 | { props: { __TYPE: 'CustomComponent' }},
66 | { props: { __TYPE: 'Something Else' }},
67 | { props: { TYPE: 'Some TYPE' }},
68 | { type: 'div' },
69 | { type: 'span' },
70 | { type: 'div' },
71 | ];
72 | expect(getChildByType(children, ['div', 'span', 'CustomComponent'])).toStrictEqual(children[0]);
73 | });
74 |
75 | test('undefined', () => {
76 | const children = [
77 | { props: { __TYPE: 'CustomComponent' }},
78 | { props: { __TYPE: 'CustomComponent' }},
79 | { props: { __TYPE: 'Something Else' }},
80 | { props: { TYPE: 'Some TYPE' }},
81 | { type: 'div' },
82 | { type: 'span' },
83 | { type: 'div' },
84 | ];
85 | expect(getChildByType(children, ['Doesn\'t Exist'])).toStrictEqual(undefined);
86 | });
87 |
88 | test('Prioritized', () => {
89 | const children = [
90 | { props: { __TYPE: 'CustomComponent' }},
91 | { props: { __TYPE: 'CustomComponent' }},
92 | { props: { __TYPE: 'Something Else' }},
93 | { props: { TYPE: 'Some TYPE' }},
94 | { type: 'div' },
95 | { type: 'span' },
96 | { type: 'div' },
97 | ];
98 | expect(getChildByType(children, ['div', 'span', 'CustomComponent'], { prioritized: true })).toStrictEqual(children[4]);
99 | expect(getChildByType(children, ['b', 'em', 'NotFound'], { prioritized: true })).toBe(undefined);
100 | });
101 | });
--------------------------------------------------------------------------------
/src/getChildByType/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { processTypes } from './../_private/utils';
4 | import { getChildrenByTypeDeep } from '../getChildrenByType';
5 | import { typeOfComponent } from '../typeOfComponent';
6 |
7 | /**
8 | * Gets first child by specified type
9 | *
10 | * @since v1.0.0 (modified v2.0.0)
11 | * @template T
12 | * @template TC
13 | * @param {T} children - JSX children
14 | * @param {TC | TC[]} types - Types of children to match
15 | * @param {GetChildByTypeConfig} [config={ customTypeKey: '__TYPE', prioritized: false }] - The configuration params
16 | * @returns {T} - The first matching child
17 | * @docgen_types
18 | * // The configuration type for the util:
19 | * // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
20 | * // prioritized?: boolean = false - Whether or not the order of types is prioritized
21 | *
22 | * export type GetChildByTypeConfig = { customTypeKey?: string, prioritized?: boolean };
23 | * @example
24 | * // Finds the first occurrence of either a ToDo (custom component w/defined type as prop), a div, or a React Fragment
25 | * getChildByType(children, ['ToDo', 'div', 'react.fragment']);
26 | *
27 | * // Finds the first occurrence of either a MyComponent (custom component - full component passed in), a div, or a React Fragment
28 | * import MyComponent from './MyComponent';
29 | * getChildByType(children, [MyComponent, 'div', 'react.fragment']);
30 | *
31 | * // Finds the first occurrence of either a ToDo, a div, or a React Fragment with a preference for that order. If ToDo exists, it will return that first. If not, then div, etc.
32 | * getChildByType(children, ['ToDo', 'div', 'react.fragment'], { prioritized: true });
33 | * @docgen_note
34 | * This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
35 | * @docgen_import { getChildByType, GetChildByTypeConfig }
36 | * @docgen_imp_note GetChildByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
37 | */
38 | export const getChildByType = (children: T, types: TC | Array, { customTypeKey = '__TYPE', prioritized = false }: GetChildByTypeConfig = {}) : T => {
39 | const _types = processTypes(Array.isArray(types) ? types : [types]);
40 | const matches = React.Children.toArray(children).filter(child => _types.indexOf(typeOfComponent(child, customTypeKey)) !== -1);
41 |
42 | if (prioritized) {
43 | for (const type of _types) {
44 | const match = matches.find(x => typeOfComponent(x, customTypeKey) === type);
45 |
46 | if (match) return match as T;
47 | }
48 | }
49 |
50 | return matches[0] as T;
51 | };
52 |
53 | /**
54 | * Gets first child by specified type (deep search)
55 | *
56 | * @since v1.0.0 (modified v2.0.0)
57 | * @template T
58 | * @template TC
59 | * @param {T} children - JSX children
60 | * @param {TC | TC[]} types - Types of children to match
61 | * @param {GetChildByTypeConfig} [{ customTypeKey: '__TYPE', prioritized: false }] - The configuration params
62 | * @returns {T} - The first matching child
63 | * @docgen_types
64 | * // The configuration type for the util:
65 | * // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
66 | * // prioritized?: boolean = false - Whether or not the order of types is prioritized
67 | *
68 | * export type GetChildByTypeConfig = { customTypeKey?: string, prioritized?: boolean };
69 | * @example
70 | * // Finds the first occurrence of either a ToDo (custom component w/defined type as prop), a div, or a React Fragment
71 | * getChildByTypeDeep(children, ['ToDo', 'div', 'react.fragment']);
72 | *
73 | * // Finds the first occurrence of either a MyComponent (custom component - full component passed in), a div, or a React Fragment
74 | * import MyComponent from './MyComponent';
75 | * getChildByTypeDeep(children, [MyComponent, 'div', 'react.fragment']);
76 | *
77 | * // Finds the first occurrence of either a ToDo, a div, or a React Fragment with a preference for that order. If ToDo exists, it will return that first. If not, then div, etc.
78 | * getChildByTypeDeep(children, ['ToDo', 'div', 'react.fragment'], { prioritized: true });
79 | * @docgen_note
80 | * This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
81 | * @docgen_import { getChildByTypeDeep, GetChildByTypeConfig }
82 | * @docgen_imp_note GetChildByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
83 | */
84 | export const getChildByTypeDeep = (children: T, types: TC | Array, { customTypeKey = '__TYPE', prioritized = false }: GetChildByTypeConfig = {}) : T => {
85 | const _types = processTypes(Array.isArray(types) ? types : [types]);
86 |
87 | const matches = getChildrenByTypeDeep(children, _types, { customTypeKey });
88 |
89 | if (prioritized) {
90 | for (const type of _types) {
91 | const match = matches.find(x => typeOfComponent(x, customTypeKey) === type);
92 |
93 | if (match) return match as T;
94 | }
95 | }
96 |
97 | return matches[0] as T;
98 | };
99 |
100 | export type GetChildByTypeConfig = { customTypeKey?: string, prioritized?: boolean };
--------------------------------------------------------------------------------
/src/getChildren/README-deep.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChildrenDeep<T=React.ReactNode, TC=React.ReactNode>
4 | Gets all children by specified predicate (deep search)
5 | Since v1.0.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
predicate The predicate to determine if the given child is a match | (child: TC) => boolean |
13 |
Returns: {TC[]} - All matching children
14 | Import
15 |
16 | ```
17 | import { getChildrenDeep } from 'react-nanny';
18 | ```
19 |
20 | Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | // Finds the first occurrence of a child that has a prop of 'active' set to true
28 | getChildrenDeep(children, child => child.props.active);
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/src/getChildren/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChildren<T=React.ReactNode, TC=React.ReactNode>
4 | Gets all children by specified predicate
5 | Since v1.0.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
predicate The predicate to determine if the given child is a match | (child: TC) => boolean |
13 |
Returns: {TC[]} - All matching children
14 | Import
15 |
16 | ```
17 | import { getChildren } from 'react-nanny';
18 | ```
19 |
20 | Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | // Finds all children that have an 'active' prop set to true
28 | getChildren(children, child => child.props.active);
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/src/getChildren/index.deep.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChildrenDeep } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
22 | ],
23 | },
24 | },
25 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
26 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
27 | { type: 'span' },
28 | { type: 'div' },
29 | ];
30 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
31 |
32 | describe('getChildrenDeep', () => {
33 | test('Deep find', () => {
34 | expect(getChildrenDeep(children, child => child && child.props && child.props.active)).toStrictEqual([
35 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
36 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
37 | ]);
38 | expect(getChildrenDeep(children, child => child && child.props && child.props.hello === 'world')).toStrictEqual([{ type: 'span', props: { children: 'Outer span', hello: 'world' }}]);
39 | });
40 |
41 | test('Empty', () => {
42 | expect(getChildrenDeep(children, child => child && child.props && child.props.hello === 'Newman')).toStrictEqual([]);
43 | });
44 | });
--------------------------------------------------------------------------------
/src/getChildren/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChildren } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | { props: { active: false }},
8 | { props: { hello: 'world' }},
9 | { props: { active: true }},
10 | { props: { active: true }},
11 | { props: { active: false }},
12 | { props: { hello: 'world' }},
13 | { props: { hello: 'world' }},
14 | ];
15 | React.Children.toArray = jest.fn().mockReturnValue(children);
16 |
17 | describe('getChildren', () => {
18 | test('Basic find', () => {
19 | expect(getChildren(children, child => child.props.active)).toStrictEqual(children.slice(2, 4));
20 | expect(getChildren(children, child => child.props.hello === 'world')).toStrictEqual([children[1], ...children.slice(5)]);
21 | });
22 |
23 | test('Empty', () => {
24 | expect(getChildren(children, child => child.props.hello === 'Newman')).toStrictEqual([]);
25 | });
26 | });
--------------------------------------------------------------------------------
/src/getChildren/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { NannyNode } from '../types';
3 |
4 | /**
5 | * Gets all children by specified predicate
6 | *
7 | * @since v1.0.0
8 | * @template T
9 | * @template TC - Type of child
10 | * @param {T} children - JSX children
11 | * @param {(child: TC) => boolean} predicate - The predicate to determine if the given child is a match
12 | * @returns {TC[]} - All matching children
13 | * @example
14 | * // Finds all children that have an 'active' prop set to true
15 | * getChildren(children, child => child.props.active);
16 | */
17 | export const getChildren = (children: T, predicate: (child: TC) => boolean) : TC[] =>
18 | React.Children.toArray(children).filter(predicate) as TC[];
19 |
20 | /**
21 | * Gets all children by specified predicate (deep search)
22 | *
23 | * @since v1.0.0
24 | * @template T
25 | * @template TC - Type of child
26 | * @param {T} children - JSX children
27 | * @param {(child: TC) => boolean} predicate - The predicate to determine if the given child is a match
28 | * @returns {TC[]} - All matching children
29 | * @example
30 | * // Finds the first occurrence of a child that has a prop of 'active' set to true
31 | * getChildrenDeep(children, child => child.props.active);
32 | */
33 | export const getChildrenDeep = (children: T, predicate: (child: TC) => boolean) : TC[] => {
34 | const _children = React.Children.toArray(children);
35 |
36 | let output: TC[] = [];
37 |
38 | for (const child of _children) {
39 | if (predicate(child as TC)) {
40 | output = [...output, child as TC];
41 | }
42 |
43 | if ((child as NannyNode).props?.children) {
44 | output = [...output, ...getChildrenDeep((child as NannyNode).props.children, predicate)];
45 | }
46 | }
47 |
48 | return output;
49 | };
--------------------------------------------------------------------------------
/src/getChildrenByType/README-deep.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChildrenByTypeDeep<T=React.ReactNode, TC=unknown>
4 | Gets all children by specified type (deep search)
5 | Since v1.0.0 (modified v2.0.0)
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
types Types of children to match | TC | TC[] |
{ customTypeKey: '__TYPE', skipWhenFound: false } (optional) The configuration params | GetChildrenByTypeConfig |
13 |
Returns: {T[]} - Array of matching children
This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
14 | To stop the depth search when something was found, set skipWhenFound true.
Supporting Types
15 |
16 | ```
17 | // The configuration type for the util:
18 | // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
19 | // skipWhenFound?: boolean = false - Will stop the depth search when something was found
20 |
21 | export type GetChildrenByTypeConfig = { customTypeKey?: string, skipWhenFound?: boolean };
22 | ```
23 | Import
24 |
25 | ```
26 | import { getChildrenByTypeDeep, GetChildrenByTypeConfig } from 'react-nanny';
27 | ```
28 |
29 | GetChildrenByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
Examples
30 |
31 |
32 |
33 |
34 |
35 | ```
36 | // Finds all occurrences of ToDo (custom component), div, and React Fragment
37 | getChildrenByTypeDeep(children, ['ToDo', 'div', 'react.fragment']);
38 |
39 | // Finds all occurrences of MyComponent (custom component - full component passed in), a div, and React Fragment
40 | import MyComponent from './MyComponent';
41 | getChildrenByTypeDeep(children, [MyComponent, 'div', 'react.fragment']);
42 |
43 | // Finds all occurrences of ToDo (custom component) with a customized {customTypeKey}
44 | getChildrenByTypeDeep(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
45 | ```
46 |
47 |
--------------------------------------------------------------------------------
/src/getChildrenByType/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChildrenByType<T=React.ReactNode, TC=unknown>
4 | Gets all children by specified type
5 | Since v1.0.0 (modified v2.0.0)
6 |
7 |
8 |
9 | Param |
10 | Type | Default |
11 |
12 | children JSX children | T | |
types Types of children to match | TC | TC[] | |
config (optional) The configuration params | GetChildrenByTypeConfig | { customTypeKey: '__TYPE' } |
13 |
Returns: {T[]} - Array of matching children
This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
Supporting Types
14 |
15 | ```
16 | // The configuration type for the util:
17 | // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
18 |
19 | export type GetChildrenByTypeConfig = { customTypeKey?: string };
20 | ```
21 | Import
22 |
23 | ```
24 | import { getChildrenByType, GetChildrenByTypeConfig } from 'react-nanny';
25 | ```
26 |
27 | GetChildrenByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
Examples
28 |
29 |
30 |
31 |
32 |
33 | ```
34 | // Finds all occurrences of ToDo (custom component), div, and React Fragment
35 | getChildrenByType(children, ['ToDo', 'div', 'react.fragment']);
36 |
37 | // Finds all occurrences of MyComponent (custom component - full component passed in), a div, and React Fragment
38 | import MyComponent from './MyComponent';
39 | getChildrenByType(children, [MyComponent, 'div', 'react.fragment']);
40 |
41 | // Finds all occurrences of ToDo (custom component) with a customized {customTypeKey}
42 | getChildrenByType(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
43 | ```
44 |
45 |
--------------------------------------------------------------------------------
/src/getChildrenByType/index.deep.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChildrenByTypeDeep } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
22 | ],
23 | },
24 | },
25 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
26 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
27 | {
28 | props: {
29 | __TYPE: 'CustomComponentGroup', active: true, children: [
30 | {props: {__TYPE: 'CustomComponent', active: true, children: 'Outer child active in group'}},
31 | ],
32 | },
33 | },
34 | { type: 'span' },
35 | { type: 'div' },
36 | ];
37 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
38 |
39 | describe('getChildrenByTypeDeep', () => {
40 | test('Deep Single', () => {
41 | expect(getChildrenByTypeDeep(children, 'CustomComponent')).toStrictEqual([
42 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
43 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
44 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
45 | { props: {__TYPE: 'CustomComponent', active: true, children: 'Outer child active in group'}},
46 | ]);
47 | });
48 |
49 | test('Deep Custom Component', () => {
50 | expect(getChildrenByTypeDeep(children, ['CustomComponent'])).toStrictEqual([
51 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
52 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
53 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
54 | { props: {__TYPE: 'CustomComponent', active: true, children: 'Outer child active in group' }},
55 | ]);
56 | });
57 |
58 | test('Deep Custom Component Mixed', () => {
59 | expect(getChildrenByTypeDeep(children, ['CustomComponent', 'CustomComponentGroup'])).toStrictEqual([
60 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
61 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
62 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
63 | {
64 | props: {
65 | __TYPE: 'CustomComponentGroup', active: true, children: [
66 | {props: {__TYPE: 'CustomComponent', active: true, children: 'Outer child active in group'}},
67 | ],
68 | },
69 | },
70 | { props: {__TYPE: 'CustomComponent', active: true, children: 'Outer child active in group'}},
71 | ]);
72 | });
73 |
74 | test('Deep Custom Component Mixed Skip', () => {
75 | expect(getChildrenByTypeDeep(children, ['CustomComponent', 'CustomComponentGroup'], {skipWhenFound: true})).toStrictEqual([
76 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
77 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
78 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
79 | {
80 | props: {
81 | __TYPE: 'CustomComponentGroup', active: true, children: [
82 | {props: {__TYPE: 'CustomComponent', active: true, children: 'Outer child active in group'}},
83 | ],
84 | },
85 | },
86 | ]);
87 | });
88 |
89 |
90 | test('Standard Html (JSX) Components', () => {
91 | expect(getChildrenByTypeDeep(children, ['span'])).toStrictEqual([
92 | { type: 'span', props: { children: 'Deep span' }},
93 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
94 | { type: 'span' },
95 | ]);
96 | });
97 |
98 | test('Mixed', () => {
99 | expect(getChildrenByTypeDeep(children, ['span', 'CustomComponent'])).toStrictEqual([
100 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
101 | { type: 'span', props: { children: 'Deep span' }},
102 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
103 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
104 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
105 | {props: {__TYPE: 'CustomComponent', active: true, children: 'Outer child active in group'}},
106 | { type: 'span' },
107 | ]);
108 | });
109 |
110 | test('Empty', () => {
111 | expect(getChildrenByTypeDeep(children, ['bogus'])).toStrictEqual([]);
112 | });
113 | });
--------------------------------------------------------------------------------
/src/getChildrenByType/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChildrenByType } = require('../../dist/lib/es5/index');
5 |
6 | describe('getChildrenByType', () => {
7 | test('Single', () => {
8 | let children = { props: { __TYPE: 'CustomComponent' }};
9 | let reactChildrenToArrayOutput = [children];
10 | React.Children.toArray = jest.fn().mockReturnValue(reactChildrenToArrayOutput);
11 | expect(getChildrenByType(children, ['CustomComponent'])).toStrictEqual(reactChildrenToArrayOutput);
12 |
13 | const someFunction = () => 'some function';
14 |
15 | children = [
16 | { props: { __TYPE: 'CustomComponent' }},
17 | { props: { __TYPE: 'CustomComponent' }},
18 | { props: { __TYPE: 'Something Else' }},
19 | { props: { TYPE: 'Some TYPE' }},
20 | { type: 'div' },
21 | someFunction,
22 | ];
23 | React.Children.toArray = jest.fn().mockReturnValue(children);
24 |
25 | expect(getChildrenByType(children, 'CustomComponent')).toStrictEqual(children.slice(0, 2));
26 | expect(getChildrenByType(children, 'Some TYPE', { customTypeKey: 'TYPE' })).toStrictEqual([children[3]]);
27 | expect(getChildrenByType(children, 'function')).toStrictEqual([someFunction]);
28 | });
29 |
30 | test('Custom Components', () => {
31 | let children = { props: { __TYPE: 'CustomComponent' }};
32 | let reactChildrenToArrayOutput = [children];
33 | React.Children.toArray = jest.fn().mockReturnValue(reactChildrenToArrayOutput);
34 | expect(getChildrenByType(children, ['CustomComponent'])).toStrictEqual(reactChildrenToArrayOutput);
35 |
36 | children = [
37 | { props: { __TYPE: 'CustomComponent' }},
38 | { props: { __TYPE: 'CustomComponent' }},
39 | { props: { __TYPE: 'Something Else' }},
40 | { props: { TYPE: 'Some TYPE' }},
41 | { type: 'div' },
42 | ];
43 | React.Children.toArray = jest.fn().mockReturnValue(children);
44 |
45 | expect(getChildrenByType(children, ['CustomComponent'])).toStrictEqual(children.slice(0, 2));
46 | expect(getChildrenByType(children, ['Some TYPE'], { customTypeKey: 'TYPE' })).toStrictEqual([children[3]]);
47 | expect(getChildrenByType(children, ['CustomComponent', 'Something Else'])).toStrictEqual(children.slice(0, 3));
48 | });
49 |
50 | test('Standard Html (JSX) Components', () => {
51 | const children = [
52 | { props: { __TYPE: 'CustomComponent' }},
53 | { props: { __TYPE: 'CustomComponent' }},
54 | { props: { __TYPE: 'Something Else' }},
55 | { props: { TYPE: 'Some TYPE' }},
56 | { type: 'div' },
57 | { type: 'span' },
58 | { type: 'div' },
59 | ];
60 | React.Children.toArray = jest.fn().mockReturnValue(children);
61 | expect(getChildrenByType(children, ['div'])).toStrictEqual([children[4], children[6]]);
62 | expect(getChildrenByType(children, ['div', 'span'])).toStrictEqual(children.slice(4, 7));
63 | });
64 |
65 | test('Mixed', () => {
66 | const children = [
67 | { props: { __TYPE: 'CustomComponent' }},
68 | { props: { __TYPE: 'CustomComponent' }},
69 | { props: { __TYPE: 'Something Else' }},
70 | { props: { TYPE: 'Some TYPE' }},
71 | { type: 'div' },
72 | { type: 'span' },
73 | { type: 'div' },
74 | ];
75 | React.Children.toArray = jest.fn().mockReturnValue(children);
76 | expect(getChildrenByType(children, ['div', 'span', 'CustomComponent'])).toStrictEqual([...children.slice(0, 2), ...children.slice(4, 7)]);
77 | });
78 |
79 | test('Empty', () => {
80 | const children = [
81 | { props: { __TYPE: 'CustomComponent' }},
82 | { props: { __TYPE: 'CustomComponent' }},
83 | { props: { __TYPE: 'Something Else' }},
84 | { props: { TYPE: 'Some TYPE' }},
85 | { type: 'div' },
86 | { type: 'span' },
87 | { type: 'div' },
88 | ];
89 | React.Children.toArray = jest.fn().mockReturnValue(children);
90 | expect(getChildrenByType(children, ['Doesn\'t Exist'])).toStrictEqual([]);
91 | });
92 | });
--------------------------------------------------------------------------------
/src/getChildrenByType/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { processTypes } from '../_private/utils';
4 | import { typeOfComponent } from '../typeOfComponent';
5 | import { NannyNode } from '../types';
6 |
7 | /**
8 | * Gets all children by specified type
9 | *
10 | * @since v1.0.0 (modified v2.0.0)
11 | * @template T
12 | * @template TC
13 | * @param {T} children - JSX children
14 | * @param {TC | TC[]} types - Types of children to match
15 | * @param {GetChildrenByTypeConfig} [config={ customTypeKey: '__TYPE' }] - The configuration params
16 | * @returns {T[]} - Array of matching children
17 | * @docgen_types
18 | * // The configuration type for the util:
19 | * // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
20 | *
21 | * export type GetChildrenByTypeConfig = { customTypeKey?: string };
22 | * @example
23 | * // Finds all occurrences of ToDo (custom component), div, and React Fragment
24 | * getChildrenByType(children, ['ToDo', 'div', 'react.fragment']);
25 | *
26 | * // Finds all occurrences of MyComponent (custom component - full component passed in), a div, and React Fragment
27 | * import MyComponent from './MyComponent';
28 | * getChildrenByType(children, [MyComponent, 'div', 'react.fragment']);
29 | *
30 | * // Finds all occurrences of ToDo (custom component) with a customized {customTypeKey}
31 | * getChildrenByType(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
32 | * @docgen_note
33 | * This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
34 | * @docgen_import { getChildrenByType, GetChildrenByTypeConfig }
35 | * @docgen_imp_note GetChildrenByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
36 | */
37 | export const getChildrenByType = (children: T, types: TC | Array, { customTypeKey = '__TYPE' }: GetChildrenByTypeConfig = {}) : T[] => {
38 | const _types = processTypes(Array.isArray(types) ? types : [types]);
39 | return React.Children.toArray(children).filter(child => _types.indexOf(typeOfComponent(child, customTypeKey)) !== -1) as T[];
40 | };
41 |
42 |
43 | /**
44 | * Gets all children by specified type (deep search)
45 | *
46 | * @since v1.0.0 (modified v2.0.0)
47 | * @template TC
48 | * @param {T} children - JSX children
49 | * @param {TC | TC[]} types - Types of children to match
50 | * @param {GetChildrenByTypeConfig} [{ customTypeKey: '__TYPE', skipWhenFound: false }] - The configuration params
51 | * @returns {T[]} - Array of matching children
52 | * @docgen_types
53 | * // The configuration type for the util:
54 | * // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
55 | * // skipWhenFound?: boolean = false - Will stop the depth search when something was found
56 | *
57 | * export type GetChildrenByTypeConfig = { customTypeKey?: string, skipWhenFound?: boolean };
58 | * @example
59 | * // Finds all occurrences of ToDo (custom component), div, and React Fragment
60 | * getChildrenByTypeDeep(children, ['ToDo', 'div', 'react.fragment']);
61 | *
62 | * // Finds all occurrences of MyComponent (custom component - full component passed in), a div, and React Fragment
63 | * import MyComponent from './MyComponent';
64 | * getChildrenByTypeDeep(children, [MyComponent, 'div', 'react.fragment']);
65 | *
66 | * // Finds all occurrences of ToDo (custom component) with a customized {customTypeKey}
67 | * getChildrenByTypeDeep(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
68 | * @docgen_note
69 | * This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
70 | * To stop the depth search when something was found, set skipWhenFound true.
71 | * @docgen_import { getChildrenByTypeDeep, GetChildrenByTypeConfig }
72 | * @docgen_imp_note GetChildrenByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
73 | */
74 | export const getChildrenByTypeDeep = (children: T, types: TC | Array, { customTypeKey = '__TYPE', skipWhenFound = false }: GetChildrenByTypeConfig = {}) : T[] => {
75 | const _children = React.Children.toArray(children);
76 | const _types = processTypes(Array.isArray(types) ? types : [types]);
77 |
78 | let output = [];
79 |
80 | for (const child of _children) {
81 | const found = _types.indexOf(typeOfComponent(child, customTypeKey)) !== -1;
82 | if (found) {
83 | output = [...output, child as T];
84 | }
85 |
86 | if ((child as NannyNode).props?.children && !(skipWhenFound && found)) {
87 | output = [...output, ...getChildrenByTypeDeep((child as NannyNode).props.children, _types, { customTypeKey, skipWhenFound })];
88 | }
89 | }
90 |
91 | return output;
92 | };
93 |
94 | export type GetChildrenByTypeConfig = { customTypeKey?: string, skipWhenFound?: boolean };
--------------------------------------------------------------------------------
/src/getChildrenWithDescendant/EXAMPLES.md:
--------------------------------------------------------------------------------
1 | ```
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | // Inside MyComponent...
15 | getChildrenWithDescendant(children, child => child?.props?.active)
16 |
17 | // Returns the first child because it contains a descendant that is active and
18 | // returns the last child because it is active. Doesn't return the empty divs
19 | // because they aren't active and they don't contain a descendant that is active.
20 | // =>
21 | [
22 |
23 |
24 |
25 |
26 |
,
27 |
28 | ]
29 | ```
30 |
--------------------------------------------------------------------------------
/src/getChildrenWithDescendant/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChildrenWithDescendant<T=React.ReactNode, TC=React.ReactNode>
4 | Gets all children by specified predicate or that have a descendant node in their lineage which matches the predicate
5 | Since v2.6.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
predicate The predicate to determine if the given child is a match | (child: TC) => boolean |
13 |
Returns: {TC[]} - All children that match the predicate or have a descendant which matches the predicate
14 | Import
15 |
16 | ```
17 | import { getChildrenWithDescendant } from 'react-nanny';
18 | ```
19 |
20 | Examples
21 |
22 |
23 |
24 | ```
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | // Inside MyComponent...
38 | getChildrenWithDescendant(children, child => child?.props?.active)
39 |
40 | // Returns the first child because it contains a descendant that is active and
41 | // returns the last child because it is active. Doesn't return the empty divs
42 | // because they aren't active and they don't contain a descendant that is active.
43 | // =>
44 | [
45 |
46 |
47 |
48 |
49 |
,
50 |
51 | ]
52 | ```
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/getChildrenWithDescendant/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChildrenWithDescendant } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
22 | ],
23 | },
24 | },
25 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
26 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
27 | { type: 'span' },
28 | { type: 'div' },
29 | ];
30 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
31 |
32 | describe('getChildrenWithDescendant', () => {
33 | test('Basic', () => {
34 | expect(getChildrenWithDescendant(children, child => child?.props?.active)).toStrictEqual([
35 | children[0],
36 | children[2],
37 | ]);
38 | });
39 |
40 | test('Empty', () => {
41 | expect(getChildrenWithDescendant(children, child => child?.props?.hello === 'Newman')).toStrictEqual([]);
42 | });
43 | });
--------------------------------------------------------------------------------
/src/getChildrenWithDescendant/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { getChildDeep } from '../getChild';
3 |
4 | /**
5 | * Gets all children by specified predicate or that have a descendant node in their lineage which matches the predicate
6 | *
7 | * @since v2.6.0
8 | * @template T
9 | * @template TC - Type of child
10 | * @param {T} children - JSX children
11 | * @param {(child: TC) => boolean} predicate - The predicate to determine if the given child is a match
12 | * @returns {TC[]} - All children that match the predicate or have a descendant which matches the predicate
13 | */
14 | export const getChildrenWithDescendant = (children: T, predicate: (child: TC) => boolean) : TC[] => {
15 | const _children = React.Children.toArray(children);
16 |
17 | let output: TC[] = [];
18 |
19 | for (const child of _children) {
20 | if (getChildDeep(child, predicate)) {
21 | output = [...output, child as TC];
22 | }
23 | }
24 |
25 | return output;
26 | };
--------------------------------------------------------------------------------
/src/getChildrenWithDescendantByType/EXAMPLES.md:
--------------------------------------------------------------------------------
1 | ```
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | // Inside MyComponent...
15 | getChildrenWithDescendantByType(children, ['World'])
16 |
17 | // Returns the first child because it contains a World component as a descendant and
18 | // returns the last child because it matches the type. Doesn't return the empty divs
19 | // because they aren't World components and they don't contain a World descendant.
20 | // =>
21 | [
22 |
23 |
24 |
25 |
26 |
,
27 |
28 | ]
29 | ```
30 |
31 | Other Examples
32 |
--------------------------------------------------------------------------------
/src/getChildrenWithDescendantByType/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getChildrenWithDescendantByType<T=React.ReactNode, TC=unknown>
4 | Gets all children by specified type or that have a descendant node in their lineage which match the specified type
5 | Since v2.6.0
6 |
7 |
8 |
9 | Param |
10 | Type | Default |
11 |
12 | children JSX children | T | |
types Types of children to match | TC | TC[] | |
config (optional) The configuration params | GetChildrenWithDescendantByTypeConfig | { customTypeKey: '__TYPE' } |
13 |
Returns: {T[]} - All children that match the specified type or have a descendant which matches the specified type
This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
Supporting Types
14 |
15 | ```
16 | // The configuration type for the util:
17 | // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
18 |
19 | export type GetChildrenWithDescendantByTypeConfig = { customTypeKey?: string };
20 | ```
21 | Import
22 |
23 | ```
24 | import { getChildrenWithDescendantByType, GetChildrenWithDescendantByTypeConfig } from 'react-nanny';
25 | ```
26 |
27 | GetChildrenWithDescendantByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
Examples
28 |
29 |
30 |
31 | ```
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | // Inside MyComponent...
45 | getChildrenWithDescendantByType(children, ['World'])
46 |
47 | // Returns the first child because it contains a World component as a descendant and
48 | // returns the last child because it matches the type. Doesn't return the empty divs
49 | // because they aren't World components and they don't contain a World descendant.
50 | // =>
51 | [
52 |
53 |
54 |
55 |
56 |
,
57 |
58 | ]
59 | ```
60 |
61 | Other Examples
62 |
63 |
64 |
65 |
66 |
67 |
68 | ```
69 | // Finds all root children that are of type or have a descendant of type ToDo (custom component), div, or React Fragment
70 | getChildrenWithDescendantByType(children, ['ToDo', 'div', 'react.fragment']);
71 |
72 | // Finds all root children that are of type or have a descendant of type MyComponent (custom component - full component passed in), a div, and React Fragment
73 | import MyComponent from './MyComponent';
74 | getChildrenWithDescendantByType(children, [MyComponent, 'div', 'react.fragment']);
75 |
76 | // Finds all root children that are of type or have a descendant of type ToDo (custom component) with a customized {customTypeKey}
77 | getChildrenWithDescendantByType(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
78 | ```
79 |
80 |
--------------------------------------------------------------------------------
/src/getChildrenWithDescendantByType/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getChildrenWithDescendantByType } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
22 | { props: { TYPE: 'CustomComponent', children: 'Outer span', hello: 'world' }},
23 | ],
24 | },
25 | },
26 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
27 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
28 | { type: 'span' },
29 | { type: 'div' },
30 | ];
31 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
32 |
33 | describe('getChildrenWithDescendantByType', () => {
34 | test('Single', () => {
35 | expect(getChildrenWithDescendantByType(children, 'CustomComponent')).toStrictEqual([
36 | children[0],
37 | children[1],
38 | children[2],
39 | ]);
40 | });
41 |
42 | test('Basic', () => {
43 | expect(getChildrenWithDescendantByType(children, ['CustomComponent'])).toStrictEqual([
44 | children[0],
45 | children[1],
46 | children[2],
47 | ]);
48 | });
49 |
50 | test('customTypeKey', () => {
51 | expect(getChildrenWithDescendantByType(children, ['CustomComponent'], { customTypeKey: 'TYPE' })).toStrictEqual([
52 | children[0],
53 | ]);
54 | });
55 |
56 | test('Empty', () => {
57 | expect(getChildrenWithDescendantByType(children, ['Doesn\'t Exist'])).toStrictEqual([]);
58 | });
59 | });
--------------------------------------------------------------------------------
/src/getChildrenWithDescendantByType/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { getChildByTypeDeep } from '../getChildByType';
3 |
4 | /**
5 | * Gets all children by specified type or that have a descendant node in their lineage which match the specified type
6 | *
7 | * @since v2.6.0
8 | * @template T
9 | * @template TC
10 | * @param {T} children - JSX children
11 | * @param {TC | TC[]} types - Types of children to match
12 | * @param {GetChildrenWithDescendantByTypeConfig} [config={ customTypeKey: '__TYPE' }] - The configuration params
13 | * @returns {T[]} - All children that match the specified type or have a descendant which matches the specified type
14 | * @docgen_types
15 | * // The configuration type for the util:
16 | * // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
17 | *
18 | * export type GetChildrenWithDescendantByTypeConfig = { customTypeKey?: string };
19 | * @example
20 | * // Finds all root children that are of type or have a descendant of type ToDo (custom component), div, or React Fragment
21 | * getChildrenWithDescendantByType(children, ['ToDo', 'div', 'react.fragment']);
22 | *
23 | * // Finds all root children that are of type or have a descendant of type MyComponent (custom component - full component passed in), a div, and React Fragment
24 | * import MyComponent from './MyComponent';
25 | * getChildrenWithDescendantByType(children, [MyComponent, 'div', 'react.fragment']);
26 | *
27 | * // Finds all root children that are of type or have a descendant of type ToDo (custom component) with a customized {customTypeKey}
28 | * getChildrenWithDescendantByType(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
29 | * @docgen_note
30 | * This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To find a React Fragment, search for 'react.fragment'.
31 | * @docgen_import { getChildrenWithDescendantByType, GetChildrenWithDescendantByTypeConfig }
32 | * @docgen_imp_note GetChildrenWithDescendantByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
33 | */
34 | export const getChildrenWithDescendantByType = (children: T, types: TC | Array, { customTypeKey = '__TYPE' }: GetChildrenWithDescendantByTypeConfig = {}) : T[] => {
35 | const _children = React.Children.toArray(children);
36 |
37 | let output = [];
38 |
39 | for (const child of _children) {
40 | if (getChildByTypeDeep(child, types, { customTypeKey, prioritized: false })) {
41 | output = [...output, child as T];
42 | }
43 | }
44 |
45 | return output;
46 | };
47 |
48 |
49 | export type GetChildrenWithDescendantByTypeConfig = { customTypeKey?: string };
--------------------------------------------------------------------------------
/src/getDescendantDepth/EXAMPLES.md:
--------------------------------------------------------------------------------
1 | ```
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | // Inside MyComponent...
15 | getDescendantDepth(children, child => child?.props?.active)
16 |
17 | // Returns the first child because it is the oldest ancestor which contains a
18 | // descendant that is active and returns the last child because it is active.
19 | // Doesn't return the empty divs because they aren't active and they don't
20 | // contain a descendant that is active.
21 | // =>
22 | [
23 | {
24 | ancestor: (
25 |
26 |
27 |
28 |
29 |
30 | ),
31 | depthToMatch: 2,
32 | },
33 | {
34 | ancestor: ,
35 | depthToMatch: 0,
36 | },
37 | ]
38 | ```
39 |
--------------------------------------------------------------------------------
/src/getDescendantDepth/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getDescendantDepth<T=React.ReactNode, TC=React.ReactNode>
4 | Gets the depth to the first descendant (or self) of each root child that match the specified predicate
5 | If the child does not match the predicate or have a descendant that matches, the child is not returned with the result.
Since v2.6.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
predicate The predicate to determine if the given child is a match | (child: T) => boolean |
13 |
Returns: {IDescendantDepth<T>[]} - The oldest ancestor with the depth to the matching descendant
Supporting Types
14 |
15 | ```
16 | // The item type in the returned array:
17 | // ancestor: T - The oldest ancestor of a matching descendant
18 | // depthToMatch: number - The depth to the first predicate match; 0 indicates that the oldest ancestor matches
19 |
20 | export interface IDescendantDepth{ ancestor: T, depthToMatch: number }
21 | ```
22 | Import
23 |
24 | ```
25 | import { getDescendantDepth } from 'react-nanny';
26 | ```
27 |
28 | Examples
29 |
30 |
31 |
32 | ```
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | // Inside MyComponent...
46 | getDescendantDepth(children, child => child?.props?.active)
47 |
48 | // Returns the first child because it is the oldest ancestor which contains a
49 | // descendant that is active and returns the last child because it is active.
50 | // Doesn't return the empty divs because they aren't active and they don't
51 | // contain a descendant that is active.
52 | // =>
53 | [
54 | {
55 | ancestor: (
56 |
57 |
58 |
59 |
60 |
61 | ),
62 | depthToMatch: 2,
63 | },
64 | {
65 | ancestor: ,
66 | depthToMatch: 0,
67 | },
68 | ]
69 | ```
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/getDescendantDepth/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getDescendantDepth } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
22 | ],
23 | },
24 | },
25 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
26 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
27 | { type: 'span' },
28 | { type: 'div' },
29 | ];
30 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
31 |
32 | describe('getDescendantDepth', () => {
33 | test('Basic', () => {
34 | expect(getDescendantDepth(children, child => child?.props?.active)).toStrictEqual([
35 | { ancestor: children[0], depthToMatch: 2 },
36 | { ancestor: children[2], depthToMatch: 0 },
37 | ]);
38 | });
39 |
40 | test('Empty', () => {
41 | expect(getDescendantDepth(children, child => child?.props?.hello === 'Newman')).toStrictEqual([]);
42 | });
43 | });
--------------------------------------------------------------------------------
/src/getDescendantDepth/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { IDescendantDepth, NannyNode } from '../types';
3 |
4 | /**
5 | * Gets the depth to the first descendant (or self) of each root child that match the specified predicate
6 | *
7 | * @since v2.6.0
8 | * @template T
9 | * @template TC - Type of child
10 | * @param {T} children - JSX children
11 | * @param {(child: T) => boolean} predicate - The predicate to determine if the given child is a match
12 | * @returns {IDescendantDepth[]} - The oldest ancestor with the depth to the matching descendant
13 | * @docgen_types
14 | * // The item type in the returned array:
15 | * // ancestor: T - The oldest ancestor of a matching descendant
16 | * // depthToMatch: number - The depth to the first predicate match; 0 indicates that the oldest ancestor matches
17 | *
18 | * export interface IDescendantDepth{ ancestor: T, depthToMatch: number }
19 | * @docgen_description_note
20 | * If the child does not match the predicate or have a descendant that matches, the child is not returned with the result.
21 | */
22 | export const getDescendantDepth = (children: T, predicate: (child: TC) => boolean) : IDescendantDepth[] => {
23 | const _children = React.Children.toArray(children);
24 |
25 | // recursively get the depth of the first matching child
26 | const getDepth = (children: T, predicate: (child: TC) => boolean, level: number) : number => {
27 | const _children = React.Children.toArray(children);
28 |
29 | for (const child of _children) {
30 | if (predicate(child as TC)) return level + 1;
31 |
32 | if ((child as any).props?.children) {
33 | const result = getDepth((child as NannyNode).props.children, predicate, level + 1);
34 |
35 | if (result > 0) return result;
36 | }
37 | }
38 |
39 | return -1;
40 | };
41 |
42 | let output: IDescendantDepth[] = [];
43 |
44 | for (const child of _children) {
45 | const depthToMatch = getDepth(child, predicate, -1);
46 |
47 | if (depthToMatch >= 0) {
48 | output = [...output, { ancestor: child as TC, depthToMatch }];
49 | }
50 | }
51 |
52 | return output;
53 | };
--------------------------------------------------------------------------------
/src/getDescendantDepthByType/EXAMPLES.md:
--------------------------------------------------------------------------------
1 | ```
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | // Inside MyComponent...
15 | getDescendantDepthByType(children, ['World'])
16 |
17 | // Returns the first child because it is the oldest ancestor which contains a
18 | // World descendant and returns the last child because it is of type World.
19 | // Doesn't return the empty divs because they aren't of type World and they
20 | // don't contain a World descendant.
21 | // =>
22 | [
23 | {
24 | ancestor: (
25 |
26 |
27 |
28 |
29 |
30 | ),
31 | depthToMatch: 2,
32 | },
33 | {
34 | ancestor: ,
35 | depthToMatch: 0,
36 | },
37 | ]
38 | ```
39 |
40 | Other Examples
41 |
--------------------------------------------------------------------------------
/src/getDescendantDepthByType/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | getDescendantDepthByType<T=React.ReactNode, TC=unknown>
4 | Gets the depth to the first descendant (or self) of each root child that match the specified types
5 | If the child does not match any of the specified types or have a descendant that matches, the child is not returned with the result.
Since v2.6.0
6 |
7 |
8 |
9 | Param |
10 | Type | Default |
11 |
12 | children JSX children | T | |
types Types of children to match | TC | TC[] | |
config (optional) The configuration params | GetDescendantDepthByTypeConfig | { customTypeKey: '__TYPE' } |
13 |
Returns: {IDescendantDepth<T>[]} - The oldest ancestor with the depth to the matching descendant
Supporting Types
14 |
15 | ```
16 | // The configuration type for the util:
17 | // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
18 |
19 | export type GetDescendantDepthByTypeConfig = { customTypeKey?: string };
20 |
21 | // The item type in the returned array:
22 | // ancestor: T - The oldest ancestor of a matching descendant
23 | // depthToMatch: number - The depth to the first predicate match; 0 indicates that the oldest ancestor matches
24 |
25 | export interface IDescendantDepth{ ancestor: T, depthToMatch: number }
26 | ```
27 | Import
28 |
29 | ```
30 | import { getDescendantDepthByType, GetDescendantDepthByTypeConfig } from 'react-nanny';
31 | ```
32 |
33 | GetDescendantDepthByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
Examples
34 |
35 |
36 |
37 | ```
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | // Inside MyComponent...
51 | getDescendantDepthByType(children, ['World'])
52 |
53 | // Returns the first child because it is the oldest ancestor which contains a
54 | // World descendant and returns the last child because it is of type World.
55 | // Doesn't return the empty divs because they aren't of type World and they
56 | // don't contain a World descendant.
57 | // =>
58 | [
59 | {
60 | ancestor: (
61 |
62 |
63 |
64 |
65 |
66 | ),
67 | depthToMatch: 2,
68 | },
69 | {
70 | ancestor: ,
71 | depthToMatch: 0,
72 | },
73 | ]
74 | ```
75 |
76 | Other Examples
77 |
78 |
79 |
80 |
81 |
82 |
83 | ```
84 | // Gets depth for all descendants that are of type ToDo (custom component), div, or React Fragment
85 | getDescendantDepthByType(children, ['ToDo', 'div', 'react.fragment']);
86 |
87 | // Gets depth for all descendants that are of type MyComponent (custom component - full component passed in), a div, and React Fragment
88 | import MyComponent from './MyComponent';
89 | getDescendantDepthByType(children, [MyComponent, 'div', 'react.fragment']);
90 |
91 | // Gets depth for all descendants that are of type ToDo (custom component) with a customized {customTypeKey}
92 | getDescendantDepthByType(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
93 | ```
94 |
95 |
--------------------------------------------------------------------------------
/src/getDescendantDepthByType/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { getDescendantDepthByType } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
22 | { props: { TYPE: 'CustomComponent', children: 'Outer span', hello: 'world' }},
23 | ],
24 | },
25 | },
26 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
27 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
28 | { type: 'span' },
29 | { type: 'div' },
30 | ];
31 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
32 |
33 | describe('getDescendantDepthByType', () => {
34 | test('Single', () => {
35 | expect(getDescendantDepthByType(children, 'CustomComponent')).toStrictEqual([
36 | { ancestor: children[0], depthToMatch: 2 },
37 | { ancestor: children[1], depthToMatch: 0 },
38 | { ancestor: children[2], depthToMatch: 0 },
39 | ]);
40 | });
41 |
42 | test('Basic', () => {
43 | expect(getDescendantDepthByType(children, ['CustomComponent'])).toStrictEqual([
44 | { ancestor: children[0], depthToMatch: 2 },
45 | { ancestor: children[1], depthToMatch: 0 },
46 | { ancestor: children[2], depthToMatch: 0 },
47 | ]);
48 | });
49 |
50 | test('customTypeKey', () => {
51 | expect(getDescendantDepthByType(children, ['CustomComponent'], { customTypeKey: 'TYPE' })).toStrictEqual([
52 | { ancestor: children[0], depthToMatch: 1 },
53 | ]);
54 | });
55 |
56 | test('Empty', () => {
57 | expect(getDescendantDepthByType(children, ['Doesn\'t Exist'])).toStrictEqual([]);
58 | });
59 | });
--------------------------------------------------------------------------------
/src/getDescendantDepthByType/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { IDescendantDepth, NannyNode } from '../types';
3 | import { typeOfComponent } from '../typeOfComponent';
4 | import { processTypes } from '../_private/utils';
5 |
6 | /**
7 | * Gets the depth to the first descendant (or self) of each root child that match the specified types
8 | *
9 | * @since v2.6.0
10 | * @template T
11 | * @template TC
12 | * @param {T} children - JSX children
13 | * @param {TC | TC[]} types - Types of children to match
14 | * @param {GetDescendantDepthByTypeConfig} [config={ customTypeKey: '__TYPE' }] - The configuration params
15 | * @returns {IDescendantDepth[]} - The oldest ancestor with the depth to the matching descendant
16 | * @example
17 | * // Gets depth for all descendants that are of type ToDo (custom component), div, or React Fragment
18 | * getDescendantDepthByType(children, ['ToDo', 'div', 'react.fragment']);
19 | *
20 | * // Gets depth for all descendants that are of type MyComponent (custom component - full component passed in), a div, and React Fragment
21 | * import MyComponent from './MyComponent';
22 | * getDescendantDepthByType(children, [MyComponent, 'div', 'react.fragment']);
23 | *
24 | * // Gets depth for all descendants that are of type ToDo (custom component) with a customized {customTypeKey}
25 | * getDescendantDepthByType(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
26 | * @docgen_types
27 | * // The configuration type for the util:
28 | * // customTypeKey?: string = '__TYPE' - The custom component prop key to check the type
29 | *
30 | * export type GetDescendantDepthByTypeConfig = { customTypeKey?: string };
31 | *
32 | * // The item type in the returned array:
33 | * // ancestor: T - The oldest ancestor of a matching descendant
34 | * // depthToMatch: number - The depth to the first predicate match; 0 indicates that the oldest ancestor matches
35 | *
36 | * export interface IDescendantDepth{ ancestor: T, depthToMatch: number }
37 | * @docgen_description_note
38 | * If the child does not match any of the specified types or have a descendant that matches, the child is not returned with the result.
39 | * @docgen_import { getDescendantDepthByType, GetDescendantDepthByTypeConfig }
40 | * @docgen_imp_note GetDescendantDepthByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
41 | */
42 | export const getDescendantDepthByType = (children: T, types: TC | Array, { customTypeKey = '__TYPE' }: GetDescendantDepthByTypeConfig = {}) : IDescendantDepth[] => {
43 | const _children = React.Children.toArray(children);
44 | const _types = processTypes(Array.isArray(types) ? types : [types]);
45 |
46 | // recursively get the depth of the first matching child
47 | const getDepth = (children: T, level: number) : number => {
48 | const _children = React.Children.toArray(children);
49 |
50 | for (const child of _children) {
51 | if (_types.indexOf(typeOfComponent(child, customTypeKey)) !== -1) {
52 | return level + 1;
53 | }
54 |
55 | if ((child as NannyNode).props?.children) {
56 | const result = getDepth((child as NannyNode).props.children, level + 1);
57 |
58 | if (result > 0) return result;
59 | }
60 | }
61 |
62 | return -1;
63 | };
64 |
65 | let output: IDescendantDepth[] = [];
66 |
67 | for (const child of _children) {
68 | const depthToMatch = getDepth(child, -1);
69 |
70 | if (depthToMatch >= 0) {
71 | output = [...output, { ancestor: child as T, depthToMatch }];
72 | }
73 | }
74 |
75 | return output;
76 | };
77 |
78 | export type GetDescendantDepthByTypeConfig = { customTypeKey?: string };
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { getChild, getChildDeep } from './getChild';
2 | export { getChildByType, getChildByTypeDeep, GetChildByTypeConfig } from './getChildByType';
3 |
4 | export { getChildren, getChildrenDeep } from './getChildren';
5 | export { getChildrenByType, getChildrenByTypeDeep, GetChildrenByTypeConfig } from './getChildrenByType';
6 |
7 | export { getChildrenWithDescendant } from './getChildrenWithDescendant';
8 | export { getChildrenWithDescendantByType, GetChildrenWithDescendantByTypeConfig } from './getChildrenWithDescendantByType';
9 |
10 | export { getDescendantDepth } from './getDescendantDepth';
11 | export { getDescendantDepthByType, GetDescendantDepthByTypeConfig } from './getDescendantDepthByType';
12 |
13 | export { overrideProps, overridePropsDeep } from './overrideProps';
14 |
15 | export { noEmptyChildrenDeep, NoEmptyConfig } from './noEmptyChildren';
16 |
17 | export { removeChildren, removeChildrenDeep } from './removeChildren';
18 | export { removeChildrenByType, removeChildrenByTypeDeep, RemoveChildrenByTypeConfig } from './removeChildrenByType';
19 |
20 | export { typeOfComponent } from './typeOfComponent';
21 |
22 | export { IDescendantDepth, NannyNode } from './types';
--------------------------------------------------------------------------------
/src/noEmptyChildren/README-deep.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | noEmptyChildrenDeep
4 | Ensure that there is some level of content and not just a bunch of empty divs, spans, etc (deep search)
5 | Since v1.0.0
6 |
7 |
8 |
9 | Param |
10 | Type | Default |
11 |
12 | component A component, array of components, or content of a component | any | |
config (optional) Configuration options for custom components | NoEmptyConfig | { ignore: [], rejectCustom: true, rejectEmptyCustom: false } |
13 |
Returns: {boolean} - Whether or not there is content provided. true = content is provided as children at some depth; false = no content is provided as children at any depth
Supporting Types
14 |
15 | ```
16 | // The configuration type for the util:
17 | // ignore?: string[] = [] - A list of components to ignore; Components in this list will be considered as valid content
18 | // rejectCustom?: boolean = true - Whether or not custom components should be rejected as content
19 | // rejectEmptyCustom?: boolean = false - Whether or not custom components require children to be considered valid content; Note: {rejectCustom} must be set to false in order for this setting to be considered
20 |
21 | export type NoEmptyConfig = { ignore?: string[], rejectCustom?: boolean, rejectEmptyCustom?: boolean };
22 | ```
23 | Import
24 |
25 | ```
26 | import { noEmptyChildrenDeep } from 'react-nanny';
27 | ```
28 |
29 | Examples
30 |
31 |
32 |
33 |
34 |
35 | ```
36 | // Ensure that one of the following is true at some level of depth for the children:
37 | // * There is markup with content
38 | // * A 'CustomComponent' is provided
39 | // * A different custom component that has children
40 |
41 | noEmptyChildrenDeep(component, { ignore: ['CustomComponent'], rejectCustom: false, rejectEmptyCustom: true })
42 | ```
43 |
44 |
--------------------------------------------------------------------------------
/src/noEmptyChildren/index.deep.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const { noEmptyChildrenDeep } = require('../../dist/lib/es5/index');
4 |
5 | describe('noEmptyChildrenDeep', () => {
6 | test('Deep => not empty', () => {
7 | const component = {
8 | type: 'div',
9 | props: {
10 | children: 'Content',
11 | },
12 | };
13 | expect(noEmptyChildrenDeep(component)).toStrictEqual(true);
14 | });
15 | test('Deep => not empty => fragment', () => {
16 | const component = {
17 | type: 'Symbol(react.fragment)',
18 | props: {
19 | children: 'Content',
20 | },
21 | };
22 | expect(noEmptyChildrenDeep(component)).toStrictEqual(true);
23 | });
24 | test('Deep => not empty => complex', () => {
25 | const component = {
26 | type: 'div',
27 | props: {
28 | children: [
29 | {
30 | type: 'div',
31 | props: {
32 | children: [
33 | {
34 | type: 'div',
35 | children: 'Content',
36 | },
37 | ],
38 | },
39 | },
40 | {
41 | type: 'div',
42 | props: {
43 | children: [
44 | {
45 | type: 'div',
46 | props: {
47 | children: [
48 | {
49 | type: 'div',
50 | props: {
51 | children: 'Content',
52 | },
53 | },
54 | {
55 | type: 'div',
56 | props: {
57 | children: 'Content',
58 | },
59 | },
60 | ],
61 | },
62 | },
63 | {
64 | type: 'div',
65 | props: {
66 | children: 'Content',
67 | },
68 | },
69 | ],
70 | },
71 | },
72 | ],
73 | },
74 | };
75 | expect(noEmptyChildrenDeep(component)).toStrictEqual(true);
76 | });
77 | test('Deep => not empty => one thing', () => {
78 | const component = {
79 | type: 'div',
80 | props: {
81 | children: [
82 | {
83 | type: 'div',
84 | props: {
85 | children: [
86 | {
87 | type: 'div',
88 | props: {},
89 | },
90 | ],
91 | },
92 | },
93 | {
94 | type: 'div',
95 | props: {
96 | children: [
97 | {
98 | type: 'div',
99 | props: {
100 | children: [
101 | {
102 | type: 'div',
103 | props: {
104 | children: 'content',
105 | },
106 | },
107 | {
108 | type: 'div',
109 | props: {},
110 | },
111 | ],
112 | },
113 | },
114 | {
115 | type: 'div',
116 | props: {},
117 | },
118 | ],
119 | },
120 | },
121 | ],
122 | },
123 | };
124 | expect(noEmptyChildrenDeep(component)).toStrictEqual(true);
125 | });
126 |
127 | test('Deep => empty', () => {
128 | const component = {
129 | type: 'div',
130 | props: {},
131 | };
132 | expect(noEmptyChildrenDeep(component)).toStrictEqual(false);
133 | });
134 | test('Deep => empty => complex', () => {
135 | const component = {
136 | type: 'div',
137 | props: {
138 | children: [
139 | {
140 | type: 'div',
141 | props: {
142 | children: [
143 | {
144 | type: 'div',
145 | props: {},
146 | },
147 | ],
148 | },
149 | },
150 | {
151 | type: 'div',
152 | props: {
153 | children: [
154 | {
155 | type: 'div',
156 | props: {
157 | children: [
158 | {
159 | type: 'div',
160 | props: {},
161 | },
162 | {
163 | type: 'div',
164 | props: {},
165 | },
166 | ],
167 | },
168 | },
169 | {
170 | type: 'div',
171 | props: {},
172 | },
173 | ],
174 | },
175 | },
176 | ],
177 | },
178 | };
179 | expect(noEmptyChildrenDeep(component)).toStrictEqual(false);
180 | });
181 |
182 | test('Deep => rejectCustom => empty', () => {
183 | const component = {
184 | props: {
185 | __TYPE: 'CustomComponent',
186 | },
187 | };
188 | expect(noEmptyChildrenDeep(component, { rejectCustom: true })).toStrictEqual(false);
189 | });
190 | test('Deep => rejectEmptyCustom => empty', () => {
191 | const component = {
192 | props: {
193 | __TYPE: 'CustomComponent',
194 | },
195 | };
196 | expect(noEmptyChildrenDeep(component, { rejectCustom: false, rejectEmptyCustom: true })).toStrictEqual(false);
197 | });
198 | test('Deep => rejectEmptyCustom => empty => no children', () => {
199 | const component = {
200 | type: () => true,
201 | };
202 | expect(noEmptyChildrenDeep(component, { rejectCustom: false, rejectEmptyCustom: true })).toStrictEqual(false);
203 | });
204 | test('Deep => rejectEmptyCustom => empty', () => {
205 | const component = {
206 | props: {
207 | __TYPE: 'CustomComponent',
208 | },
209 | };
210 | expect(noEmptyChildrenDeep(component, { rejectCustom: false })).toStrictEqual(true);
211 | });
212 |
213 | test('Deep => rejectEmptyCustom => not empty', () => {
214 | const component = {
215 | props: {
216 | __TYPE: 'CustomComponent',
217 | children: 'content',
218 | },
219 | };
220 | expect(noEmptyChildrenDeep(component, { rejectCustom: false, rejectEmptyCustom: true })).toStrictEqual(true);
221 | });
222 | test('Deep => Accept empty custom => empty', () => {
223 | const component = {
224 | props: {
225 | __TYPE: 'CustomComponent',
226 | },
227 | };
228 | expect(noEmptyChildrenDeep(component, { rejectCustom: false })).toStrictEqual(true);
229 | });
230 |
231 | test('Deep => ignore => not empty', () => {
232 | const component = {
233 | props: {
234 | __TYPE: 'CustomComponent',
235 | children: 'content',
236 | },
237 | };
238 | expect(noEmptyChildrenDeep(component, { ignore: ['CustomComponent'] })).toStrictEqual(true);
239 | });
240 | test('Deep => ignore => not empty => complex', () => {
241 | const component = {
242 | type: 'div',
243 | props: {
244 | children: [
245 | {
246 | type: 'div',
247 | props: {
248 | children: {
249 | props: {
250 | __TYPE: 'CustomComponent',
251 | },
252 | },
253 | },
254 | },
255 | {
256 | type: 'div',
257 | props: {
258 | children: 'content',
259 | },
260 | },
261 | ],
262 | },
263 | };
264 | expect(noEmptyChildrenDeep(component, { ignore: ['CustomComponent'], rejectEmptyCustom: true })).toStrictEqual(true);
265 | });
266 | test('Deep => ignore => empty => complex', () => {
267 | const component = {
268 | type: 'div',
269 | props: {
270 | children: [
271 | {
272 | type: 'div',
273 | props: {
274 | children: {
275 | props: {
276 | __TYPE: 'SomeOtherComponent',
277 | },
278 | },
279 | },
280 | },
281 | {
282 | type: 'div',
283 | props: {},
284 | },
285 | ],
286 | },
287 | };
288 | expect(noEmptyChildrenDeep(component, { ignore: ['CustomComponent'], rejectEmptyCustom: true })).toStrictEqual(false);
289 | });
290 | });
--------------------------------------------------------------------------------
/src/noEmptyChildren/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2 | import { typeOfComponent } from '../typeOfComponent';
3 |
4 | /**
5 | * Ensure that there is some level of content and not just a bunch of empty divs, spans, etc (deep search)
6 | *
7 | * @since v1.0.0
8 | * @param {any} component - A component, array of components, or content of a component
9 | * @param {NoEmptyConfig} [config={ ignore: [], rejectCustom: true, rejectEmptyCustom: false }] - Configuration options for custom components
10 | * @returns {boolean} - Whether or not there is content provided. true = content is provided as children at some depth; false = no content is provided as children at any depth
11 | * @docgen_types
12 | * // The configuration type for the util:
13 | * // ignore?: string[] = [] - A list of components to ignore; Components in this list will be considered as valid content
14 | * // rejectCustom?: boolean = true - Whether or not custom components should be rejected as content
15 | * // rejectEmptyCustom?: boolean = false - Whether or not custom components require children to be considered valid content; Note: {rejectCustom} must be set to false in order for this setting to be considered
16 | *
17 | * export type NoEmptyConfig = { ignore?: string[], rejectCustom?: boolean, rejectEmptyCustom?: boolean };
18 | * @example
19 | * // Ensure that one of the following is true at some level of depth for the children:
20 | * // * There is markup with content
21 | * // * A 'CustomComponent' is provided
22 | * // * A different custom component that has children
23 | *
24 | * noEmptyChildrenDeep(component, { ignore: ['CustomComponent'], rejectCustom: false, rejectEmptyCustom: true })
25 | */
26 | export const noEmptyChildrenDeep = (component: any, { ignore = [], rejectCustom = true, rejectEmptyCustom = false }: NoEmptyConfig = {}) : boolean => {
27 | if (ignore.indexOf(typeOfComponent(component)) >= 0) return true;
28 |
29 | if (Array.isArray(component)) {
30 | for (const item of component) {
31 | if (noEmptyChildrenDeep(item, { ignore, rejectCustom, rejectEmptyCustom })) {
32 | return true;
33 | }
34 | }
35 |
36 | return false;
37 | }
38 |
39 | if (typeof component.type === 'string' && !component.props) {
40 | return false;
41 | }
42 |
43 | if ((typeof component.type === 'string' || (typeOfComponent(component) === 'react.fragment')) && component.props.children) {
44 | return noEmptyChildrenDeep(component.props.children, { ignore, rejectCustom, rejectEmptyCustom });
45 | } else if (typeof component.type === 'string' || typeOfComponent(component) === 'react.fragment') {
46 | return false;
47 | }
48 |
49 | if ((rejectCustom && isCustom(component)) || (rejectEmptyCustom && isCustom(component) && !component.props?.children)) {
50 | return false;
51 | }
52 |
53 | return true;
54 | };
55 |
56 | const isCustom = (component: any) => typeof component !== 'string' && typeof component.type !== 'string';
57 |
58 | export type NoEmptyConfig = { ignore?: string[], rejectCustom?: boolean, rejectEmptyCustom?: boolean };
--------------------------------------------------------------------------------
/src/overrideProps/README-deep.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | overridePropsDeep<T=React.ReactNode, TC=React.ReactNode>
4 | Immutably override props of the children and all descendants (deep)
5 | This function is a handy shortcut for when you may need to override the props of your deeply nested child components and is an alternative for writing your own looped React.cloneElement calls.
Since v2.10.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
getChildOverrides Callback function that returns an object containing the props you wish to override for each child | (child: T) => object |
13 |
Returns: {TC[]} - All children with modified prop values
14 | Import
15 |
16 | ```
17 | import { overridePropsDeep } from 'react-nanny';
18 | ```
19 |
20 | Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | *
28 | // This will override the active prop for each child component to {true}
29 | overridePropsDeep(children, () => ({ active: true }));
30 |
31 | // This will override the active prop for each child component to {true} where child has a title prop = 'Supervisor'
32 | overridePropsDeep(children, child => child.props.title === 'Supervisor' ? ({ active: true }) : {});
33 | ```
34 |
35 |
--------------------------------------------------------------------------------
/src/overrideProps/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | overrideProps<T=any>
4 | Immutably override props of the children of the original component and (optionally) the original component
5 | This function is a handy shortcut for when you may need to override the props of your child components and is an alternative for writing your own looped React.cloneElement calls.
Since v2.3.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | component The component whose children you want to modify | React.ReactElement |
getChildOverrides Callback function that returns an object containing the props you wish to override for each child | (child: T, index?: number) => object |
overrides (optional) Any other props to override on the original component | object |
13 |
Returns: {React.ReactElement} The original component with the children with modified prop values
14 | Import
15 |
16 | ```
17 | import { overrideProps } from 'react-nanny';
18 | ```
19 |
20 | Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | *
28 | // This will override the active prop for each child component to {true}
29 | overrideProps(component, () => ({ active: true }));
30 |
31 | // This will override the active prop for each child component to {true} where child has a title prop = 'Supervisor'
32 | overrideProps(component, child => child.props.title === 'Supervisor' ? ({ active: true }) : {});
33 |
34 | // This will override the active prop for each child component to {true} and override the hello prop on the root component
35 | overrideProps(component, () => ({ active: true }), { hello: 'Hola mundo' });
36 | ```
37 |
38 |
--------------------------------------------------------------------------------
/src/overrideProps/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { overrideProps, overridePropsDeep } = require('../../dist/lib/es5/index');
5 |
6 | describe('overrideProps', () => {
7 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
8 | React.cloneElement = (x, props) => ({ ...x, props: { ...x.props, ...props }});
9 |
10 | const component = {
11 | props: {
12 | children: [
13 | { props: { active: false, title: 'Supervisor' }},
14 | { props: { active: false, title: 'Employee' }},
15 | { props: { active: false, title: 'Employee' }},
16 | { props: { active: false, title: 'Supervisor' }},
17 | ],
18 | hello: 'Hello world',
19 | },
20 | };
21 |
22 | test('Basic', () => {
23 | expect(overrideProps(component, () => ({ active: true }))).toStrictEqual({
24 | props: {
25 | children: [
26 | { props: { active: true, title: 'Supervisor' }},
27 | { props: { active: true, title: 'Employee' }},
28 | { props: { active: true, title: 'Employee' }},
29 | { props: { active: true, title: 'Supervisor' }},
30 | ],
31 | hello: 'Hello world',
32 | },
33 | });
34 | });
35 |
36 | test('Basic => single', () => {
37 | const component = {
38 | props: {
39 | children: { props: { active: false, title: 'Supervisor' }},
40 | hello: 'Hello world',
41 | },
42 | };
43 |
44 | expect(overrideProps(component, () => ({ active: true }))).toStrictEqual({
45 | props: {
46 | children: { props: { active: true, title: 'Supervisor' }},
47 | hello: 'Hello world',
48 | },
49 | });
50 | });
51 |
52 | test('Basic => updating self prop', () => {
53 | expect(overrideProps(component, () => ({ active: true }), { hello: 'Hola mundo' })).toStrictEqual({
54 | props: {
55 | children: [
56 | { props: { active: true, title: 'Supervisor' }},
57 | { props: { active: true, title: 'Employee' }},
58 | { props: { active: true, title: 'Employee' }},
59 | { props: { active: true, title: 'Supervisor' }},
60 | ],
61 | hello: 'Hola mundo',
62 | },
63 | });
64 | });
65 |
66 | test('Basic => updating self prop => null self', () => {
67 | expect(overrideProps(component, () => ({ active: true }), null)).toStrictEqual({
68 | props: {
69 | children: [
70 | { props: { active: true, title: 'Supervisor' }},
71 | { props: { active: true, title: 'Employee' }},
72 | { props: { active: true, title: 'Employee' }},
73 | { props: { active: true, title: 'Supervisor' }},
74 | ],
75 | hello: 'Hello world',
76 | },
77 | });
78 | });
79 |
80 | test('conditional on child prop value', () => {
81 | expect(overrideProps(component, child => child.props.title === 'Supervisor' ? ({ active: true }) : {})).toStrictEqual({
82 | props: {
83 | children: [
84 | { props: { active: true, title: 'Supervisor' }},
85 | { props: { active: false, title: 'Employee' }},
86 | { props: { active: false, title: 'Employee' }},
87 | { props: { active: true, title: 'Supervisor' }},
88 | ],
89 | hello: 'Hello world',
90 | },
91 | });
92 | });
93 |
94 | test('nothing', () => {
95 | expect(overrideProps(null, () => ({ active: true }))).toBe(null);
96 | expect(overrideProps(null, () => ({ active: true }), { hello: 'Hola mundo' })).toBe(null);
97 | expect(overrideProps({ props: {}}, () => ({ active: true }))).toStrictEqual({ props: {}});
98 | expect(overrideProps({}, () => ({ active: true }))).toStrictEqual({});
99 | });
100 | });
101 |
102 | describe('overridePropsDeep', () => {
103 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
104 | React.cloneElement = (x, props) => ({ ...x, props: { ...x.props, ...props }});
105 |
106 | const children = {
107 | props: {
108 | children: [
109 | { props: { active: false, title: 'Supervisor' }},
110 | { props: {
111 | active: false, title: 'Employee',
112 | children: [
113 | { props: { active: false, title: 'Supervisor' }},
114 | { props: { active: false, title: 'Employee' }},
115 | { props: { active: false, title: 'Employee' }},
116 | { props: { active: false, title: 'Supervisor' }},
117 | ],
118 | }},
119 | { props: { active: false, title: 'Employee' }},
120 | { props: { active: false, title: 'Supervisor' }},
121 | ],
122 | hello: 'Hello world',
123 | },
124 | };
125 |
126 | test('Basic', () => {
127 | expect(overridePropsDeep(children, () => ({ active: true }))).toStrictEqual([{
128 | props: {
129 | active: true,
130 | children: [
131 | { props: { active: true, title: 'Supervisor' }},
132 | { props: {
133 | active: true, title: 'Employee',
134 | children: [
135 | { props: { active: true, title: 'Supervisor' }},
136 | { props: { active: true, title: 'Employee' }},
137 | { props: { active: true, title: 'Employee' }},
138 | { props: { active: true, title: 'Supervisor' }},
139 | ],
140 | }},
141 | { props: { active: true, title: 'Employee' }},
142 | { props: { active: true, title: 'Supervisor' }},
143 | ],
144 | hello: 'Hello world',
145 | },
146 | }]);
147 | });
148 |
149 | test('Basic => single', () => {
150 | const children = {
151 | props: {
152 | children: { props: { active: false, title: 'Supervisor' }},
153 | hello: 'Hello world',
154 | },
155 | };
156 |
157 | expect(overridePropsDeep(children, () => ({ active: true }))).toStrictEqual([{
158 | props: {
159 | active: true,
160 | children: [{ props: { active: true, title: 'Supervisor' }}],
161 | hello: 'Hello world',
162 | },
163 | }]);
164 | });
165 |
166 | test('conditional on child prop value', () => {
167 | expect(overridePropsDeep(children, child => child.props.title === 'Supervisor' ? ({ active: true }) : {})).toStrictEqual([{
168 | props: {
169 | children: [
170 | { props: { active: true, title: 'Supervisor' }},
171 | { props: {
172 | active: false, title: 'Employee',
173 | children: [
174 | { props: { active: true, title: 'Supervisor' }},
175 | { props: { active: false, title: 'Employee' }},
176 | { props: { active: false, title: 'Employee' }},
177 | { props: { active: true, title: 'Supervisor' }},
178 | ],
179 | }},
180 | { props: { active: false, title: 'Employee' }},
181 | { props: { active: true, title: 'Supervisor' }},
182 | ],
183 | hello: 'Hello world',
184 | },
185 | }]);
186 | });
187 |
188 | test('nothing', () => {
189 | expect(overridePropsDeep(null, () => ({ active: true }))).toStrictEqual([]);
190 | expect(overridePropsDeep({ props: {}}, () => ({ active: true }))).toStrictEqual([{ props: { active: true } }]);
191 | expect(overridePropsDeep({}, () => ({ active: true }))).toStrictEqual([{}]);
192 | });
193 | });
--------------------------------------------------------------------------------
/src/overrideProps/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { NannyNode } from '../types';
3 |
4 | /**
5 | * Immutably override props of the children of the original component and (optionally) the original component
6 | *
7 | * @since v2.3.0
8 | * @param {React.ReactElement} component - The component whose children you want to modify
9 | * @param {(child: T, index?: number) => object} getChildOverrides - Callback function that returns an object containing the props you wish to override for each child
10 | * @param {object} [overrides] - Any other props to override on the original component
11 | * @returns {React.ReactElement} The original component with the children with modified prop values
12 | * @example *
13 | * // This will override the active prop for each child component to {true}
14 | * overrideProps(component, () => ({ active: true }));
15 | *
16 | * // This will override the active prop for each child component to {true} where child has a title prop = 'Supervisor'
17 | * overrideProps(component, child => child.props.title === 'Supervisor' ? ({ active: true }) : {});
18 | *
19 | * // This will override the active prop for each child component to {true} and override the hello prop on the root component
20 | * overrideProps(component, () => ({ active: true }), { hello: 'Hola mundo' });
21 | * @docgen_description_note
22 | * This function is a handy shortcut for when you may need to override the props of your child components and is an alternative for writing your own looped React.cloneElement calls.
23 | */
24 | export const overrideProps = (component: React.ReactElement, getChildOverrides: (child: T, index?: number) => Record, overrides: Record = {}) : React.ReactElement => {
25 | if (!component) return component;
26 |
27 | const _overrides = overrides ?? {};
28 | if (!component.props && Object.keys(_overrides).length <= 0) return component;
29 | if (!component.props.children) return React.cloneElement(component, _overrides);
30 |
31 | if (Array.isArray(component.props.children)) {
32 | return React.cloneElement(component, Object.assign(_overrides, {
33 | children: React.Children.toArray(component.props.children).map((child: any, index?: number) => React.cloneElement(child, getChildOverrides(child, index))),
34 | }));
35 | }
36 |
37 | return React.cloneElement(component, Object.assign(_overrides, {
38 | children: React.cloneElement(component.props.children, getChildOverrides(component.props.children, 0)),
39 | }));
40 | };
41 |
42 | /**
43 | * Immutably override props of the children and all descendants (deep)
44 | *
45 | * @since v2.10.0
46 | * @param {T} children - JSX children
47 | * @param {(child: T) => object} getChildOverrides - Callback function that returns an object containing the props you wish to override for each child
48 | * @returns {TC[]} - All children with modified prop values
49 | * @example *
50 | * // This will override the active prop for each child component to {true}
51 | * overridePropsDeep(children, () => ({ active: true }));
52 | *
53 | * // This will override the active prop for each child component to {true} where child has a title prop = 'Supervisor'
54 | * overridePropsDeep(children, child => child.props.title === 'Supervisor' ? ({ active: true }) : {});
55 | * @docgen_description_note
56 | * This function is a handy shortcut for when you may need to override the props of your deeply nested child components and is an alternative for writing your own looped React.cloneElement calls.
57 | */
58 | export const overridePropsDeep = (children: T, getChildOverrides: (child: TC) => Record) : TC[] => {
59 | if (!children) return [];
60 |
61 | const _children = React.Children.toArray(children);
62 |
63 | let output: TC[] = [];
64 |
65 | for (const child of _children) {
66 | if ((child as NannyNode).props?.children) {
67 | const _child = React.cloneElement(child as React.ReactElement, Object.assign(
68 | getChildOverrides(child as TC),
69 | { children: overridePropsDeep((child as NannyNode).props.children, getChildOverrides) },
70 | ));
71 |
72 | output = [...output, _child as unknown as TC];
73 | } else if ((child as NannyNode).props) {
74 | const _child = React.cloneElement(child as React.ReactElement, getChildOverrides(child as TC));
75 |
76 | output = [...output, _child as unknown as TC];
77 | } else {
78 | output = [...output, child as unknown as TC];
79 | }
80 | }
81 |
82 | return output;
83 | };
--------------------------------------------------------------------------------
/src/removeChildren/README-deep.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | removeChildrenDeep<T=React.ReactNode, TC=React.ReactNode>
4 | Removes all children by specified predicate (deep search)
5 | Since v1.0.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
predicate The predicate to determine if the given child is a match | (child: TC) => boolean |
13 |
Returns: {T[]} - All non-matching children
14 | Import
15 |
16 | ```
17 | import { removeChildrenDeep } from 'react-nanny';
18 | ```
19 |
20 | Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | // Removes all children that have an 'active' prop set to false
28 | removeChildrenDeep(children, child => !child.props.active);
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/src/removeChildren/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | removeChildren<T=React.ReactNode, TC=React.ReactNode>
4 | Removes all children by specified predicate
5 | Since v1.0.0
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
predicate The predicate to determine if the given child is a match | (child: T) => boolean |
13 |
Returns: {T[]} - All non-matching children
14 | Import
15 |
16 | ```
17 | import { removeChildren } from 'react-nanny';
18 | ```
19 |
20 | Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | // Removes all children that have an 'active' prop set to false
28 | removeChildren(children, child => !child.props.active);
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/src/removeChildren/index.deep.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { removeChildrenDeep } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
22 | ],
23 | },
24 | },
25 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
26 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
27 | { type: 'span' },
28 | { type: 'div' },
29 | ];
30 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
31 |
32 | describe('removeChildrenDeep', () => {
33 | test('Deep remove', () => {
34 | expect(removeChildrenDeep(children, child => child && child.props && child.props.active === true)).toStrictEqual([
35 | {
36 | props: {
37 | __TYPE: 'div',
38 | children: [
39 | {
40 | props: {
41 | __TYPE: 'div',
42 | children: [
43 | { type: 'span', props: { children: 'Deep span' }},
44 | { type: 'div' },
45 | ],
46 | },
47 | },
48 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
49 | ],
50 | },
51 | },
52 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
53 | { type: 'span' },
54 | { type: 'div' },
55 | ]);
56 | });
57 | });
--------------------------------------------------------------------------------
/src/removeChildren/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { removeChildren } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | { props: { active: false }},
8 | { props: { hello: 'world' }},
9 | { props: { active: true }},
10 | { props: { active: true }},
11 | { props: { active: false }},
12 | { props: { hello: 'world' }},
13 | { props: { hello: 'world' }},
14 | ];
15 | React.Children.toArray = jest.fn().mockReturnValue(children);
16 |
17 | describe('removeChildren', () => {
18 | test('Basic remove', () => {
19 | expect(removeChildren(children, child => !child.props.active)).toStrictEqual(children.slice(2, 4));
20 | expect(removeChildren(children, child => child.props.hello === 'world')).toStrictEqual([children[0], ...children.slice(2, 5)]);
21 | });
22 |
23 | test('Empty', () => {
24 | expect(removeChildren(children, child => 'active' in child.props || child.props.hello === 'world')).toStrictEqual([]);
25 | });
26 | });
--------------------------------------------------------------------------------
/src/removeChildren/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { NannyNode } from '../types';
3 |
4 | /**
5 | * Removes all children by specified predicate
6 | *
7 | * @since v1.0.0
8 | * @template T
9 | * @template TC - Type of child
10 | * @param {T} children - JSX children
11 | * @param {(child: T) => boolean} predicate - The predicate to determine if the given child is a match
12 | * @returns {T[]} - All non-matching children
13 | * @example
14 | * // Removes all children that have an 'active' prop set to false
15 | * removeChildren(children, child => !child.props.active);
16 | */
17 | export const removeChildren = (children: T, predicate: (child: TC) => boolean) : TC[] =>
18 | React.Children.toArray(children).filter((child: TC) => !predicate(child)) as TC[];
19 |
20 | /**
21 | * Removes all children by specified predicate (deep search)
22 | *
23 | * @since v1.0.0
24 | * @template T
25 | * @template TC - Type of child
26 | * @param {T} children - JSX children
27 | * @param {(child: TC) => boolean} predicate - The predicate to determine if the given child is a match
28 | * @returns {T[]} - All non-matching children
29 | * @example
30 | * // Removes all children that have an 'active' prop set to false
31 | * removeChildrenDeep(children, child => !child.props.active);
32 | */
33 | export const removeChildrenDeep = (children: T, predicate: (child: TC) => boolean) : TC[] => {
34 | const _children = React.Children.toArray(children);
35 |
36 | let output = [];
37 |
38 | for (const child of _children) {
39 | if (!predicate(child as TC)) {
40 | if ((child as NannyNode).props?.children) {
41 | output = [
42 | ...output,
43 | Object.assign((child as NannyNode), {
44 | props: Object.assign((child as NannyNode).props, {
45 | children: Array.isArray((child as NannyNode).props.children)
46 | ? removeChildrenDeep((child as NannyNode).props.children, predicate)
47 | : removeChildrenDeep((child as NannyNode).props.children, predicate)[0],
48 | }),
49 | }),
50 | ];
51 | } else {
52 | output = [...output, child as TC];
53 | }
54 | }
55 | }
56 |
57 | return output;
58 | };
--------------------------------------------------------------------------------
/src/removeChildrenByType/README-deep.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | removeChildrenByTypeDeep<T=React.ReactNode, TC=unknown>
4 | Removes all children by specified type (deep search)
5 | Since v1.0.0 (modified v2.0.0)
6 |
7 |
8 |
9 | Param |
10 | Type |
11 |
12 | children JSX children | T |
types Types of children to match | TC | TC[] |
{ customTypeKey: '__TYPE' } (optional) The configuration params | RemoveChildrenByTypeConfig |
13 |
Returns: {T[]} - All non-matching children
This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To remove a React Fragment, search for 'react.fragment'.
14 | Import
15 |
16 | ```
17 | import { removeChildrenByTypeDeep, RemoveChildrenByTypeConfig } from 'react-nanny';
18 | ```
19 |
20 | RemoveChildrenByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | // Removes all occurrences of ToDo (custom component), div, and React Fragment
28 | removeChildrenByTypeDeep(children, ['ToDo', 'div', 'react.fragment']);
29 |
30 | // Removes all occurrences of MyComponent (custom component - full component passed in), a div, and React Fragment
31 | import MyComponent from './MyComponent';
32 | removeChildrenByTypeDeep(children, [MyComponent, 'div', 'react.fragment']);
33 |
34 | // Removes all occurrences of MyComponent (custom component - as React.ReactNode), a div, and React Fragment
35 | const component = getChildByType(['MyComponent']);
36 | removeChildrenByTypeDeep(children, [component, 'div', 'react.fragment']);
37 |
38 | // Removes all occurrences of ToDo (custom component) with a customized {customTypeKey}
39 | removeChildrenByTypeDeep(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
40 | ```
41 |
42 |
--------------------------------------------------------------------------------
/src/removeChildrenByType/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | removeChildrenByType<T=React.ReactNode, TC=unknown>
4 | Removes all children by specified type
5 | Since v1.0.0 (modified v2.0.0)
6 |
7 |
8 |
9 | Param |
10 | Type | Default |
11 |
12 | children JSX children | T | |
types Types of children to match | TC | TC[] | |
config (optional) The configuration params | RemoveChildrenByTypeConfig | { customTypeKey: '__TYPE' } |
13 |
Returns: {T[]} - All non-matching children
This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To remove a React Fragment, search for 'react.fragment'.
14 | Import
15 |
16 | ```
17 | import { removeChildrenByType, RemoveChildrenByTypeConfig } from 'react-nanny';
18 | ```
19 |
20 | RemoveChildrenByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
Examples
21 |
22 |
23 |
24 |
25 |
26 | ```
27 | // Removes all occurrences of ToDo (custom component), div, and React Fragment
28 | removeChildrenByType(children, ['ToDo', 'div', 'react.fragment']);
29 |
30 | // Removes all occurrences of MyComponent (custom component - from import), a div, and React Fragment
31 | import MyComponent from './MyComponent';
32 | removeChildrenByType(children, [MyComponent, 'div', 'react.fragment']);
33 |
34 | // Removes all occurrences of MyComponent (custom component - as React.ReactNode), a div, and React Fragment
35 | const component = getChildByType(['MyComponent']);
36 | removeChildrenByType(children, [component, 'div', 'react.fragment']);
37 |
38 | // Removes all occurrences of ToDo (custom component) with a customized {customTypeKey}
39 | removeChildrenByType(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
40 | ```
41 |
42 |
--------------------------------------------------------------------------------
/src/removeChildrenByType/index.deep.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { removeChildrenByTypeDeep } = require('../../dist/lib/es5/index');
5 |
6 | const children = [
7 | {
8 | props: {
9 | __TYPE: 'div',
10 | children: [
11 | {
12 | props: {
13 | __TYPE: 'div',
14 | children: [
15 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
16 | { type: 'span', props: { children: 'Deep span' }},
17 | { type: 'div' },
18 | ],
19 | },
20 | },
21 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
22 | ],
23 | },
24 | },
25 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
26 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
27 | { type: 'span' },
28 | { type: 'div' },
29 | ];
30 | React.Children.toArray = x => (x && Array.isArray(x) ? x : [x]).filter(z => z != undefined);
31 |
32 | describe('removeChildrenByTypeDeep', () => {
33 | test('Single', () => {
34 | expect(removeChildrenByTypeDeep(children, 'CustomComponent')).toStrictEqual([
35 | {
36 | props: {
37 | __TYPE: 'div',
38 | children: [
39 | {
40 | props: {
41 | __TYPE: 'div',
42 | children: [
43 | { type: 'span', props: { children: 'Deep span' }},
44 | { type: 'div' },
45 | ],
46 | },
47 | },
48 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
49 | ],
50 | },
51 | },
52 | { type: 'span' },
53 | { type: 'div' },
54 | ]);
55 | });
56 |
57 | test('Deep remove', () => {
58 | expect(removeChildrenByTypeDeep(children, ['CustomComponent'])).toStrictEqual([
59 | {
60 | props: {
61 | __TYPE: 'div',
62 | children: [
63 | {
64 | props: {
65 | __TYPE: 'div',
66 | children: [
67 | { type: 'span', props: { children: 'Deep span' }},
68 | { type: 'div' },
69 | ],
70 | },
71 | },
72 | { type: 'span', props: { children: 'Outer span', hello: 'world' }},
73 | ],
74 | },
75 | },
76 | { type: 'span' },
77 | { type: 'div' },
78 | ]);
79 | });
80 |
81 | test('Standard Html (JSX) Components', () => {
82 | expect(removeChildrenByTypeDeep(children, ['span'])).toStrictEqual([
83 | {
84 | props: {
85 | __TYPE: 'div',
86 | children: [
87 | {
88 | props: {
89 | __TYPE: 'div',
90 | children: [
91 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Deep child active' }},
92 | { type: 'div' },
93 | ],
94 | },
95 | },
96 | ],
97 | },
98 | },
99 | { props: { __TYPE: 'CustomComponent', active: false, children: 'Outer child' }},
100 | { props: { __TYPE: 'CustomComponent', active: true, children: 'Outer child active' }},
101 | { type: 'div' },
102 | ]);
103 | });
104 |
105 | test('Mixed', () => {
106 | expect(removeChildrenByTypeDeep(children, ['span', 'CustomComponent'])).toStrictEqual([
107 | {
108 | props: {
109 | __TYPE: 'div',
110 | children: [
111 | {
112 | props: {
113 | __TYPE: 'div',
114 | children: [
115 | { type: 'div' },
116 | ],
117 | },
118 | },
119 | ],
120 | },
121 | },
122 | { type: 'div' },
123 | ]);
124 | });
125 | });
--------------------------------------------------------------------------------
/src/removeChildrenByType/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const React = require('react');
4 | const { removeChildrenByType } = require('../../dist/lib/es5/index');
5 |
6 | describe('removeChildrenByType', () => {
7 | test('Single', () => {
8 | let children = { props: { __TYPE: 'CustomComponent' }};
9 | let reactChildrenToArrayOutput = [children];
10 | React.Children.toArray = jest.fn().mockReturnValue(reactChildrenToArrayOutput);
11 | expect(removeChildrenByType(children, 'CustomComponent')).toStrictEqual([]);
12 |
13 | children = [
14 | { props: { __TYPE: 'CustomComponent' }},
15 | { props: { __TYPE: 'CustomComponent' }},
16 | { props: { __TYPE: 'Something Else' }},
17 | { props: { TYPE: 'Some TYPE' }},
18 | { type: 'div' },
19 | ];
20 | React.Children.toArray = jest.fn().mockReturnValue(children);
21 |
22 | expect(removeChildrenByType(children, 'CustomComponent')).toStrictEqual(children.slice(2));
23 | expect(removeChildrenByType(children, 'Some TYPE', { customTypeKey: 'TYPE' })).toStrictEqual([...children.slice(0, 3), children[4]]);
24 | });
25 |
26 | test('Custom Components', () => {
27 | let children = { props: { __TYPE: 'CustomComponent' }};
28 | let reactChildrenToArrayOutput = [children];
29 | React.Children.toArray = jest.fn().mockReturnValue(reactChildrenToArrayOutput);
30 | expect(removeChildrenByType(children, ['CustomComponent'])).toStrictEqual([]);
31 |
32 | children = [
33 | { props: { __TYPE: 'CustomComponent' }},
34 | { props: { __TYPE: 'CustomComponent' }},
35 | { props: { __TYPE: 'Something Else' }},
36 | { props: { TYPE: 'Some TYPE' }},
37 | { type: 'div' },
38 | ];
39 | React.Children.toArray = jest.fn().mockReturnValue(children);
40 |
41 | expect(removeChildrenByType(children, ['CustomComponent'])).toStrictEqual(children.slice(2));
42 | expect(removeChildrenByType(children, ['Some TYPE'], { customTypeKey: 'TYPE' })).toStrictEqual([...children.slice(0, 3), children[4]]);
43 | expect(removeChildrenByType(children, ['CustomComponent', 'Something Else'])).toStrictEqual(children.slice(3));
44 | });
45 |
46 | test('Standard Html (JSX) Components', () => {
47 | const children = [
48 | { props: { __TYPE: 'CustomComponent' }},
49 | { props: { __TYPE: 'CustomComponent' }},
50 | { props: { __TYPE: 'Something Else' }},
51 | { props: { TYPE: 'Some TYPE' }},
52 | { type: 'div' },
53 | { type: 'span' },
54 | { type: 'div' },
55 | ];
56 | React.Children.toArray = jest.fn().mockReturnValue(children);
57 | expect(removeChildrenByType(children, ['div'])).toStrictEqual([...children.slice(0, 4), children[5]]);
58 | expect(removeChildrenByType(children, ['div', 'span'])).toStrictEqual(children.slice(0, 4));
59 | });
60 |
61 | test('Mixed', () => {
62 | const children = [
63 | { props: { __TYPE: 'CustomComponent' }},
64 | { props: { __TYPE: 'CustomComponent' }},
65 | { props: { __TYPE: 'Something Else' }},
66 | { props: { TYPE: 'Some TYPE' }},
67 | { type: 'div' },
68 | { type: 'span' },
69 | { type: 'div' },
70 | ];
71 | React.Children.toArray = jest.fn().mockReturnValue(children);
72 | expect(removeChildrenByType(children, ['div', 'span', 'CustomComponent'])).toStrictEqual(children.slice(2, 4));
73 | });
74 | });
--------------------------------------------------------------------------------
/src/removeChildrenByType/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { processTypes } from './../_private/utils';
4 | import { typeOfComponent } from '../typeOfComponent';
5 | import { NannyNode } from '../types';
6 |
7 | /**
8 | * Removes all children by specified type
9 | *
10 | * @since v1.0.0 (modified v2.0.0)
11 | * @template T
12 | * @template TC
13 | * @param {T} children - JSX children
14 | * @param {TC | TC[]} types - Types of children to match
15 | * @param {RemoveChildrenByTypeConfig} [config={ customTypeKey: '__TYPE' }] - The configuration params
16 | * @returns {T[]} - All non-matching children
17 | * @example
18 | * // Removes all occurrences of ToDo (custom component), div, and React Fragment
19 | * removeChildrenByType(children, ['ToDo', 'div', 'react.fragment']);
20 | *
21 | * // Removes all occurrences of MyComponent (custom component - from import), a div, and React Fragment
22 | * import MyComponent from './MyComponent';
23 | * removeChildrenByType(children, [MyComponent, 'div', 'react.fragment']);
24 | *
25 | * // Removes all occurrences of MyComponent (custom component - as React.ReactNode), a div, and React Fragment
26 | * const component = getChildByType(['MyComponent']);
27 | * removeChildrenByType(children, [component, 'div', 'react.fragment']);
28 | *
29 | * // Removes all occurrences of ToDo (custom component) with a customized {customTypeKey}
30 | * removeChildrenByType(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
31 | * @docgen_note
32 | * This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To remove a React Fragment, search for 'react.fragment'.
33 | * @docgen_import { removeChildrenByType, RemoveChildrenByTypeConfig }
34 | * @docgen_imp_note RemoveChildrenByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
35 | */
36 | export const removeChildrenByType = (children: T, types: TC | Array, { customTypeKey = '__TYPE' }: RemoveChildrenByTypeConfig = {}) : T[] => {
37 | const _types = processTypes(Array.isArray(types) ? types : [types]);
38 | return React.Children.toArray(children).filter(child => _types.indexOf(typeOfComponent(child, customTypeKey)) === -1) as T[];
39 | };
40 |
41 | /**
42 | * Removes all children by specified type (deep search)
43 | *
44 | * @since v1.0.0 (modified v2.0.0)
45 | * @template T
46 | * @template TC
47 | * @param {T} children - JSX children
48 | * @param {TC | TC[]} types - Types of children to match
49 | * @param {RemoveChildrenByTypeConfig} [{ customTypeKey: '__TYPE' }] - The configuration params
50 | * @returns {T[]} - All non-matching children
51 | * @example
52 | * // Removes all occurrences of ToDo (custom component), div, and React Fragment
53 | * removeChildrenByTypeDeep(children, ['ToDo', 'div', 'react.fragment']);
54 | *
55 | * // Removes all occurrences of MyComponent (custom component - full component passed in), a div, and React Fragment
56 | * import MyComponent from './MyComponent';
57 | * removeChildrenByTypeDeep(children, [MyComponent, 'div', 'react.fragment']);
58 | *
59 | * // Removes all occurrences of MyComponent (custom component - as React.ReactNode), a div, and React Fragment
60 | * const component = getChildByType(['MyComponent']);
61 | * removeChildrenByTypeDeep(children, [component, 'div', 'react.fragment']);
62 | *
63 | * // Removes all occurrences of ToDo (custom component) with a customized {customTypeKey}
64 | * removeChildrenByTypeDeep(children, ['ToDo'], { customTypeKey: 'myTypeKey' });
65 | * @docgen_note
66 | * This function will check the prop {customTypeKey} first and then component.type to match core html (JSX intrinsic) elements or component functions. To remove a React Fragment, search for 'react.fragment'.
67 | * @docgen_import { removeChildrenByTypeDeep, RemoveChildrenByTypeConfig }
68 | * @docgen_imp_note RemoveChildrenByTypeConfig is a TypeScript type and is only for (optional) use with TypeScript projects
69 | */
70 | export const removeChildrenByTypeDeep = (children: T, types: TC | Array, { customTypeKey = '__TYPE' }: RemoveChildrenByTypeConfig = {}) : T[] => {
71 | const _children = React.Children.toArray(children);
72 | const _types = processTypes(Array.isArray(types) ? types : [types]);
73 | let output = [];
74 |
75 | for (const child of _children) {
76 | if (_types.indexOf(typeOfComponent(child, customTypeKey)) === -1) {
77 |
78 | if ((child as NannyNode).props?.children) {
79 | output = [
80 | ...output,
81 | Object.assign({}, (child as NannyNode), {
82 | props: Object.assign({}, (child as NannyNode).props, {
83 | children: Array.isArray((child as NannyNode).props.children)
84 | ? removeChildrenByTypeDeep((child as NannyNode).props.children, _types, { customTypeKey })
85 | : removeChildrenByTypeDeep((child as NannyNode).props.children, _types, { customTypeKey })[0],
86 | }),
87 | }),
88 | ];
89 | } else {
90 | output = [...output, child as T];
91 | }
92 | }
93 | }
94 |
95 | return output;
96 | };
97 |
98 | export type RemoveChildrenByTypeConfig = { customTypeKey?: string };
--------------------------------------------------------------------------------
/src/typeOfComponent/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | typeOfComponent
4 | Gets the string type of the component's {customTypeKey}, string type of the core html (JSX intrinsic) element, or the function type
5 | Since v1.0.0
6 |
7 |
8 |
9 | Param |
10 | Type | Default |
11 |
12 | component The component to type check | any | |
customTypeKey (optional) The custom component prop key to check the type | string | '__TYPE' |
13 |
Returns: {string} - The string representation of the type
React Fragments will return type 'react.fragment'. Priority will be given to the {customTypeKey} if one exists
14 | Import
15 |
16 | ```
17 | import { typeOfComponent } from 'react-nanny';
18 | ```
19 |
20 |
--------------------------------------------------------------------------------
/src/typeOfComponent/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | const { typeOfComponent } = require('../../dist/lib/es5/index');
4 |
5 | describe('typeOfComponent', () => {
6 | test('Custom Components', () => {
7 | expect(typeOfComponent({ props: { __TYPE: 'CustomComponent' }})).toBe('CustomComponent');
8 | expect(typeOfComponent({ props: { __TYPE: 'CustomComponent' }, type: 'div' })).toBe('CustomComponent');
9 | expect(typeOfComponent({ props: { TYPE: 'CustomComponent' }}, 'TYPE')).toBe('CustomComponent');
10 | expect(typeOfComponent({ props: { TYPE: 'CustomComponent' }, type: 'div' }, 'TYPE')).toBe('CustomComponent');
11 | });
12 |
13 | test('Standard Html (JSX) Components', () => {
14 | expect(typeOfComponent({ type: 'div' })).toBe('div');
15 | });
16 |
17 | test('string', () => {
18 | expect(typeOfComponent('my string')).toBe('string');
19 | });
20 |
21 | test('function', () => {
22 | expect(typeOfComponent(() => undefined)).toBe('function');
23 | });
24 |
25 | test('react.fragment', () => {
26 | expect(typeOfComponent({ type: Symbol('react.fragment') })).toBe('react.fragment');
27 | });
28 |
29 | test('react.forward_ref', () => {
30 | expect(typeOfComponent({ type: {
31 | $$typeof: Symbol('react.forward_ref'),
32 | render: () => ({}),
33 | }})).toBe('react.forward_ref');
34 | expect(typeOfComponent({ type: {
35 | $$typeof: Symbol('react.forward_ref'),
36 | render: () => ({ props: { __TYPE: 'CustomComponent' }}),
37 | }})).toBe('react.forward_ref');
38 | });
39 |
40 | test('undefined', () => {
41 | expect(typeOfComponent(null)).toBe(undefined);
42 | expect(typeOfComponent({})).toBe(undefined);
43 | expect(typeOfComponent({ props: {}})).toBe(undefined);
44 | expect(typeOfComponent({ props: { TYPE: 'CustomComponent' }})).toBe(undefined);
45 | expect(typeOfComponent({ props: { __TYPE: 'CustomComponent' }}, 'bogus key')).toBe(undefined);
46 | });
47 | });
--------------------------------------------------------------------------------
/src/typeOfComponent/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2 | /**
3 | * Gets the string type of the component's {customTypeKey}, string type of the core html (JSX intrinsic) element, or the function type
4 | *
5 | * @since v1.0.0
6 | * @param {any} component - The component to type check
7 | * @param {string} [customTypeKey='__TYPE'] - The custom component prop key to check the type
8 | * @returns {string} - The string representation of the type
9 | * @docgen_note
10 | * React Fragments will return type 'react.fragment'. Priority will be given to the {customTypeKey} if one exists
11 | */
12 | export const typeOfComponent = (
13 | component: any,
14 | customTypeKey = "__TYPE"
15 | ): string =>
16 | (component?.props && component.props[customTypeKey]) ||
17 | (component?.type && component.type[customTypeKey]) ||
18 | (typeof component?.type === "string" && component.type) ||
19 | (component?.type &&
20 | typeof component.type === "symbol" &&
21 | component.type.toString() === "Symbol(react.fragment)" &&
22 | "react.fragment") ||
23 | (typeof component?.type === "function" && component.type) ||
24 | (typeof component?.type === "object" &&
25 | component.type.$$typeof.toString() === "Symbol(react.forward_ref)" &&
26 | "react.forward_ref") ||
27 | (typeof component === "string" && "string") ||
28 | (typeof component === "function" && "function") ||
29 | undefined;
30 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export type NannyNode = React.ReactNode & { props: Record, type: any };
4 |
5 | export interface IDescendantDepth{ ancestor: T, depthToMatch: number }
--------------------------------------------------------------------------------
/tsconfig.es5.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES5",
5 | "declaration": true,
6 | "outDir": "./dist/lib/es5",
7 | "moduleResolution": "node",
8 | "lib": ["ES2017", "DOM"]
9 | },
10 | "include": ["src/**/*"]
11 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "ES6",
4 | "target": "ES5",
5 | "declaration": true,
6 | "outDir": "./dist/lib/es6",
7 | "moduleResolution": "node"
8 | },
9 | "include": ["src/**/*"],
10 | }
--------------------------------------------------------------------------------