├── .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 | [![Build Status](https://travis-ci.com/TheSpicyMeatball/react-nanny.svg?branch=main)](https://travis-ci.com/TheSpicyMeatball/react-nanny) 2 | [![Coverage Status](https://coveralls.io/repos/github/TheSpicyMeatball/react-nanny/badge.svg?branch=main)](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 | 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 |
45 |

To Do

46 | 49 |
50 |
51 |

Completed

52 | 55 |
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 | 69 | 70 | 71 | 72 | 73 |
functionDescription
getChildGets first child by specified predicate
getChildDeepGets first child by specified predicate (deep search)
getChildByTypeGets first child by specified type
getChildByTypeDeepGets first child by specified type (deep search)
getChildrenGets all children by specified predicate
getChildrenDeepGets all children by specified predicate (deep search)
getChildrenByTypeGets all children by specified type
getChildrenByTypeDeepGets all children by specified type (deep search)
getChildrenWithDescendantGets all children by specified predicate or that have a descendant node in their lineage which matches the predicate
getChildrenWithDescendantByTypeGets all children by specified type or that have a descendant node in their lineage which match the specified type
getDescendantDepthGets the depth to the first descendant (or self) of each root child that match the specified predicate
getDescendantDepthByTypeGets the depth to the first descendant (or self) of each root child that match the specified types
noEmptyChildrenDeepEnsure that there is some level of content and not just a bunch of empty divs, spans, etc (deep search)
overridePropsImmutably override props of the children of the original component and (optionally) the original component
overridePropsDeepImmutably override props of the children and all descendants (deep)
removeChildrenRemoves all children by specified predicate
removeChildrenDeepRemoves all children by specified predicate (deep search)
removeChildrenByTypeRemoves all children by specified type
removeChildrenByTypeDeepRemoves all children by specified type (deep search)
typeOfComponentGets the string type of the component's {customTypeKey}, string type of the core html (JSX intrinsic) element, or the function type

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 | 214 | ` + 215 | (hasDefault ? '' : '') + 216 | ` 217 | 218 | ` + 219 | util.param.map(x => ( 220 | `` + 221 | `` + 222 | (hasDefault ? `` : '') + 223 | '' 224 | )).join('') + 225 | ` 226 |
ParamTypeDefault

${x.name}${x.optional ? ' (optional)' : ''}

${x.description}
${htmlEncode(x.type)}${x.optional && x.defaultValue !== undefined ? x.defaultValue : ''}
` + 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 | 242 | 243 | 244 | 245 | ` + 246 | utils.map(x => ( 247 | `` + 248 | `` 249 | )) 250 | .join('') + 251 | ` 252 |
functionDescription
${x.name}${x.description ? x.description.value : ''}

` 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 | [![Build Status](https://travis-ci.com/TheSpicyMeatball/react-nanny.svg?branch=main)](https://travis-ci.com/TheSpicyMeatball/react-nanny) 2 | [![Coverage Status](https://coveralls.io/repos/github/TheSpicyMeatball/react-nanny/badge.svg?branch=main)](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 | 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 |
45 |

To Do

46 | 49 |
50 |
51 |

Completed

52 | 55 |
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 | [![Build Status](https://travis-ci.com/TheSpicyMeatball/react-nanny.svg?branch=main)](https://travis-ci.com/TheSpicyMeatball/react-nanny) 2 | [![Coverage Status](https://coveralls.io/repos/github/TheSpicyMeatball/react-nanny/badge.svg?branch=main)](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 | 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 |
45 |

To Do

46 | 49 |
50 |
51 |

Completed

52 | 55 |
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 | 69 | 70 | 71 | 72 | 73 |
functionDescription
getChildGets first child by specified predicate
getChildDeepGets first child by specified predicate (deep search)
getChildByTypeGets first child by specified type
getChildByTypeDeepGets first child by specified type (deep search)
getChildrenGets all children by specified predicate
getChildrenDeepGets all children by specified predicate (deep search)
getChildrenByTypeGets all children by specified type
getChildrenByTypeDeepGets all children by specified type (deep search)
getChildrenWithDescendantGets all children by specified predicate or that have a descendant node in their lineage which matches the predicate
getChildrenWithDescendantByTypeGets all children by specified type or that have a descendant node in their lineage which match the specified type
getDescendantDepthGets the depth to the first descendant (or self) of each root child that match the specified predicate
getDescendantDepthByTypeGets the depth to the first descendant (or self) of each root child that match the specified types
noEmptyChildrenDeepEnsure that there is some level of content and not just a bunch of empty divs, spans, etc (deep search)
overridePropsImmutably override props of the children of the original component and (optionally) the original component
overridePropsDeepImmutably override props of the children and all descendants (deep)
removeChildrenRemoves all children by specified predicate
removeChildrenDeepRemoves all children by specified predicate (deep search)
removeChildrenByTypeRemoves all children by specified type
removeChildrenByTypeDeepRemoves all children by specified type (deep search)
typeOfComponentGets the string type of the component's {customTypeKey}, string type of the core html (JSX intrinsic) element, or the function type

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

predicate

The predicate to determine if the given child is a match
(child: TC) => boolean

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

predicate

The predicate to determine if the given child is a match
(child: TChild) => boolean

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

types

Types of children to match
TC | TC[]

{ customTypeKey: '__TYPE', prioritized: false } (optional)

The configuration params
GetChildByTypeConfig

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 | 10 | 11 | 12 | 13 |
ParamTypeDefault

children

JSX children
T

types

Types of children to match
TC | TC[]

config (optional)

The configuration params
GetChildByTypeConfig{ customTypeKey: '__TYPE', prioritized: false }

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

predicate

The predicate to determine if the given child is a match
(child: TC) => boolean

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

predicate

The predicate to determine if the given child is a match
(child: TC) => boolean

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

types

Types of children to match
TC | TC[]

{ customTypeKey: '__TYPE', skipWhenFound: false } (optional)

The configuration params
GetChildrenByTypeConfig

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 | 10 | 11 | 12 | 13 |
ParamTypeDefault

children

JSX children
T

types

Types of children to match
TC | TC[]

config (optional)

The configuration params
GetChildrenByTypeConfig{ customTypeKey: '__TYPE' }

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

predicate

The predicate to determine if the given child is a match
(child: TC) => boolean

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 | 10 | 11 | 12 | 13 |
ParamTypeDefault

children

JSX children
T

types

Types of children to match
TC | TC[]

config (optional)

The configuration params
GetChildrenWithDescendantByTypeConfig{ customTypeKey: '__TYPE' }

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

predicate

The predicate to determine if the given child is a match
(child: T) => boolean

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 | 10 | 11 | 12 | 13 |
ParamTypeDefault

children

JSX children
T

types

Types of children to match
TC | TC[]

config (optional)

The configuration params
GetDescendantDepthByTypeConfig{ customTypeKey: '__TYPE' }

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 | 10 | 11 | 12 | 13 |
ParamTypeDefault

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 }

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

getChildOverrides

Callback function that returns an object containing the props you wish to override for each child
(child: T) => object

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 | 10 | 11 | 12 | 13 |
ParamType

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

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

predicate

The predicate to determine if the given child is a match
(child: TC) => boolean

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

predicate

The predicate to determine if the given child is a match
(child: T) => boolean

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 | 10 | 11 | 12 | 13 |
ParamType

children

JSX children
T

types

Types of children to match
TC | TC[]

{ customTypeKey: '__TYPE' } (optional)

The configuration params
RemoveChildrenByTypeConfig

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 | 10 | 11 | 12 | 13 |
ParamTypeDefault

children

JSX children
T

types

Types of children to match
TC | TC[]

config (optional)

The configuration params
RemoveChildrenByTypeConfig{ customTypeKey: '__TYPE' }

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 | 10 | 11 | 12 | 13 |
ParamTypeDefault

component

The component to type check
any

customTypeKey (optional)

The custom component prop key to check the type
string'__TYPE'

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 | } --------------------------------------------------------------------------------