Gathering detailed insights and metrics for @lewisl9029/react-anonymous
Gathering detailed insights and metrics for @lewisl9029/react-anonymous
Gathering detailed insights and metrics for @lewisl9029/react-anonymous
Gathering detailed insights and metrics for @lewisl9029/react-anonymous
Create anonymous components to use hooks anywhere in your render tree.
npm install @lewisl9029/react-anonymous
Typescript
Module System
Node Version
NPM Version
JavaScript (100%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
11 Stars
22 Commits
2 Watchers
1 Branches
1 Contributors
Updated on May 18, 2024
Latest Version
6.0.0
Package Id
@lewisl9029/react-anonymous@6.0.0
Unpacked Size
14.09 kB
Size
4.60 kB
File Count
5
NPM Version
8.15.0
Node Version
18.7.0
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
1
1
Rules of hooks made less painful. Use hooks anywhere in your render tree by rendering an Anonymous
component.
See the example below for some use cases where this might be helpful:
1import * as React from "react"; 2import Anonymous from "@lewisl9029/react-anonymous"; 3 4const Example = ({ colors }) => { 5 const [isOpen, setIsOpen] = React.useState(false); 6 7 return ( 8 <div> 9 {isOpen 10 ? // Ever wanted to call a hook within a branching path? 11 <Anonymous key="true"> 12 {() => ( 13 <Modal 14 close={React.useCallback(() => setIsOpen(false), [setIsOpen])} 15 > 16 <ul> 17 {colors.map((color) => 18 // Or within a mapping function? 19 <Anonymous key={color}> 20 {() => ( 21 <li style={useMemo(() => ({ color }), [color])}>{color}</li> 22 )} 23 </Anonymous> 24 )} 25 </ul> 26 </Modal> 27 )} 28 </Anonymous> 29 : null} 30 </div> 31 ); 32};
I documented the motivation and use cases in much more detail in the following RFC: https://github.com/reactjs/rfcs/pull/197
Unfortunately it was declined, but I hope if we eventually demonstrate enough demand in userland, the React team could start reconsidering this and other solutions to the poor usage ergonomics caused by rules of hooks.
By now I'm sure we're all deeply familiar with the infamous Rules of Hooks:
Don’t call Hooks inside loops, conditions, or nested functions.
https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
Often, to adhere to these rules, we end up adding extra layers of indirection into our render function in the form of components whose sole purpose is to act as a container for hook calls.
Consider the case of a simple Modal component that accepts a close
function, where we would like to memoize the close
function using useCallback
.
We may want to write code that looks like this:
1const Example = () => { 2 const [isOpen, setIsOpen] = React.useState(false); 3 4 return ( 5 <div> 6 {isOpen ? ( 7 // This violates the rule of hooks on branching 8 <Modal close={React.useCallback(() => setIsOpen(false), [setIsOpen])}> 9 Blah 10 </Modal> 11 ) : null} 12 </div> 13 ); 14};
But due to the rule of hooks on branching, we're instead forced to write code that looks like this:
1const ModalWrapper = ({ setIsOpen }) => ( 2 <Modal close={React.useCallback(() => setIsOpen(false), [setIsOpen])}> 3 Blah 4 </Modal> 5); 6 7const Example = () => { 8 const [isOpen, setIsOpen] = React.useState(false); 9 10 return <div>{isOpen ? <ModalWrapper setIsOpen={setIsOpen} /> : null}</div>; 11};
So we're forced to add an extra layer of indirection to what used to be a simple, self-contained render function, in addition to being forced to write a bunch of boilerplate for creating the new component and drilling in all the necessary props (TypeScript users will feel double the pain here as they'd have to duplicate type declarations for the drilled-in props as well).
Some readers may point out that they prefer the latter version to the earlier one, as they might feel encapsulating everything in that branch into a ModalWrapper
component reduces noise and improves readability, i.e. that it's a useful layer of indirection.
That's a perfectly valid position to take, but I'd like to remind those readers that the decision on whether or not to add any layer of indirection should reflect a value judgement on whether or not we feel the indirection is actually useful (inherently subjective and should be made on case-by-case basis), not forced upon us by some arbitrary implementation detail of the library we're using.
This is where react-anonymous
comes in.
1npm i @lewisl9029/react-anonymous
or
yarn add @lewisl9029/react-anonymous
1import * as React from "react"; 2import Anonymous from "@lewisl9029/react-anonymous"; 3 4const Example = () => { 5 const [isOpen, setIsOpen] = React.useState(false); 6 7 return ( 8 <div> 9 {isOpen 10 ? // call anonymous anywhere in the tree, without introducing a separate named component 11 <Anonymous key="true"> 12 {() => ( 13 <Modal 14 close={React.useCallback(() => setIsOpen(false), [setIsOpen])} 15 > 16 Blah 17 </Modal> 18 )} 19 </Anonymous> 20 : null} 21 </div> 22 ); 23};
The Anonymous
component from react-anonymous
acts as a component boundary for all of your hook calls. You can add it anywhere inside the render tree to call hooks in a way that would otherwise have violated the rules of hooks, without adding any additional layers of indirection.
The key prop is used to distinguish different branches in cases like the following:
1isOpen 2 ? 3 <Anonymous key="true"> 4 {() => { 5 const [state] = useState('open') 6 return state 7 }} 8 </Anonymous> 9 : 10 <Anonymous key="false"> 11 {() => { 12 const [state] = useState('closed') 13 return state 14 }} 15 </Anonymous>
Without the key
, when isOpen
changes between renders, React would assume that the same component is being rendered and share the same set of hook calls & state between the two branches. It's not strictly necessary in the original example where we don't render a separate branch, but we recommend providing it anyways in case an alternate branch with an Anonymous
component is added later. We enforce this if you use TypeScript.
Note that the Anonymous
component also works great for looping:
1import * as React from "react"; 2import Anonymous from "@lewisl9029/react-anonymous"; 3 4const Example = ({ colors }) => { 5 return ( 6 <ul> 7 {colors.map((color) => 8 <Anonymous key={color}> 9 {() => ( 10 <li style={useMemo(() => ({ color }), [color])}>{color}</li> 11 )} 12 </Anonymous> 13 )} 14 </ul> 15 ); 16};
However, keep in mind that you still have to obey the rules of hooks within the Anonymous
component's render function:
1import * as React from "react"; 2import Anonymous from "@lewisl9029/react-anonymous"; 3 4const Example = ({ colors }) => { 5 const [isOpen, setIsOpen] = React.useState(false); 6 7 return ( 8 <div> 9 {isOpen 10 ? 11 <Anonymous key="true"> 12 {() => ( 13 <Modal 14 close={React.useCallback(() => setIsOpen(false), [setIsOpen])} 15 > 16 <ul> 17 {colors.map((color) => ( 18 // This still violates the rule of hooks on looping 19 <li style={useMemo(() => ({ color }), [color])}>{color}</li> 20 ))} 21 </ul> 22 </Modal> 23 )} 24 </Anonymous> 25 : null} 26 </div> 27 ); 28};
We can, however, nest additional layers of Anonymous
components to arbitrary depths to work around this:
1import * as React from "react"; 2import Anonymous from "@lewisl9029/react-anonymous"; 3 4const Example = ({ colors }) => { 5 const [isOpen, setIsOpen] = React.useState(false); 6 7 return ( 8 <div> 9 {isOpen 10 ? 11 <Anonymous key="true"> 12 {() => ( 13 <Modal 14 close={React.useCallback(() => setIsOpen(false), [setIsOpen])} 15 > 16 <ul> 17 {colors.map((color) => 18 <Anonymous key={color}> 19 {() => ( 20 // All good now 21 <li style={useMemo(() => ({ color }), [color])}>{color}</li> 22 )} 23 </Anonymous> 24 )} 25 </ul> 26 </Modal> 27 )} 28 </Anonymous> 29 : null} 30 </div> 31 ); 32};
The extra levels of indenting could make code impractical to read past a certain point, so at some point we may still want to break down into separate components. But note that now we can go back to adding indirection only when we feel it's useful, instead of being forced to every time we want to call a hook inside a branch or loop.
The anonymous component pattern also opens up possibilities for new use cases for hooks where previously the rules of hooks would have made usage ergonomics too prohibitively poor, due to the countless extra layers of indirection that would have been required.
For instance, a CSS-in-JS hook where styles can be applied anonymously anywhere in the tree regardless of branching/mapping (basically a more robust runtime alternative to @emotion/babel-plugin
's build-time css
prop):
1import * as React from "react"; 2import Anonymous from "@lewisl9029/react-anonymous"; 3import { useStyles } from "@lewisl9029/use-styles"; 4 5const Example = ({ colors }) => { 6 const [isOpen, setIsOpen] = React.useState(false); 7 8 return ( 9 <div> 10 {isOpen 11 ? 12 <Anonymous key="true"> 13 {() => ( 14 <Modal 15 close={React.useCallback(() => setIsOpen(false), [setIsOpen])} 16 > 17 <ul className={useStyles(() => ({ listStyleType: 'none' }), [])}> 18 {colors.map((color) => 19 <Anonymous key={color}> 20 {() => ( 21 <li className={useStyles(() => ({ color }), [color])}>{color}</li> 22 )} 23 </Anonymous> 24 )} 25 </ul> 26 </Modal> 27 )} 28 </Anonymous> 29 : null} 30 </div> 31 ); 32};
The anonymous component pattern can help reduce forced indirection in ways beyond just hooks usage as well, one use case I recently discovered is using a context in the same render function as where the provider is rendered:
1import * as React from 'react' 2import Anonymous from "@lewisl9029/react-anonymous"; 3 4const themeContext = React.createContext() 5 6const Example = () => { 7 return ( 8 <themeContext.Provider value={{ foreground: 'black', background: 'white' }}> 9 <Anonymous key="true"> 10 {() => { 11 // Calling useContext(themeContext) outside of anonymous would result in undefined. 12 // 13 // Since only components downstream from the component where the Provider 14 // is rendered can read from the Provider. 15 const theme = React.useContext(themeContext) 16 return ( 17 <span className={ 18 useStyles( 19 () => ({ 20 color: theme.foreground, 21 backgroundColor: theme.background 22 }), 23 [theme.background, theme.background] 24 )} 25 > 26 themed text 27 </span> 28 ) 29 } 30 </Anonymous> 31 </themeContext.Provider> 32 ) 33} 34
If you're using eslint-plugin-react-hooks, you'll get errors when trying to use react-anonymous
due to the plugin not recognizing that the Anonymous
render function can be treated as a valid component boundary.
I've created a fork of the plugin at https://www.npmjs.com/package/@lewisl9029/eslint-plugin-react-hooks-for-react-anonymous to add support for this pattern. The changes are very naive however, so I do anticipate some edge cases. Please feel free to report any issues you find with the plugin here.
The implementation is literally a 1-liner:
1export const Anonymous = ({ children }) => children();
By packaging it as a library, I'm mostly aiming to promote the pattern and make it easier for people to get started. Feel free to simply copy paste this into your project and use it directly, replacing the eslint plugin with my fork from above.
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
0 existing vulnerabilities detected
Reason
Found 0/22 approved changesets -- score normalized to 0
Reason
no SAST tool detected
Details
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Score
Last Scanned on 2025-07-07
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More