|
| 1 | +All react hooks are created in the packages/rooks/src/hooks folder. |
| 2 | +A hook must be exported by name. |
| 3 | +It should then be reexported from the packages/rooks/src/index.ts file (for regular hooks) or packages/rooks/src/experimental.ts file (for experimental hooks). |
| 4 | + |
| 5 | +## Hook Creation Process |
| 6 | + |
| 7 | +Instead of using the create script, I will manually create all the necessary files and updates: |
| 8 | + |
| 9 | +### Required Information |
| 10 | + |
| 11 | +When creating a hook, I need these parameters: |
| 12 | + |
| 13 | +- **name**: Hook name in camelCase starting with 'use' (e.g., useIdle) |
| 14 | +- **packageName**: Package name in hyphen-separated words starting with 'use' (e.g., use-idle) |
| 15 | +- **description**: Description of the hook |
| 16 | +- **category**: One of: ui, misc, state, effects, navigator, form, events, lifecycle, experimental |
| 17 | +- **isExperimental**: Boolean indicating if this is an experimental hook |
| 18 | + |
| 19 | +### Files to Create |
| 20 | + |
| 21 | +1. **Test file**: `packages/rooks/src/__tests__/${name}.spec.ts` |
| 22 | + |
| 23 | +```typescript |
| 24 | +import { act } from "@testing-library/react"; |
| 25 | +import { renderHook } from "@testing-library/react-hooks"; |
| 26 | +import { ${name} } from "../hooks/${name}"; |
| 27 | + |
| 28 | +describe("${name}", () => { |
| 29 | + it("should be defined", () => { |
| 30 | + expect.hasAssertions(); |
| 31 | + expect(${name}).toBeDefined(); |
| 32 | + }); |
| 33 | + |
| 34 | + it("should initialize correctly", () => { |
| 35 | + expect.hasAssertions(); |
| 36 | + const { result } = renderHook(() => ${name}(/* initial params */)); |
| 37 | + |
| 38 | + expect(result.current).toBeDefined(); |
| 39 | + // Add more specific initialization tests based on hook functionality |
| 40 | + }); |
| 41 | + |
| 42 | + // Add comprehensive tests for all hook functionality |
| 43 | + // Test each function/method the hook exposes |
| 44 | + // Test edge cases and error conditions |
| 45 | + // Test memoization with rerender() if applicable |
| 46 | + |
| 47 | + // Example test pattern: |
| 48 | + // it("should handle specific functionality", () => { |
| 49 | + // expect.hasAssertions(); |
| 50 | + // const { result } = renderHook(() => ${name}(/* params */)); |
| 51 | + // |
| 52 | + // act(() => { |
| 53 | + // // Trigger hook action |
| 54 | + // }); |
| 55 | + // |
| 56 | + // expect(result.current).toEqual(/* expected result */); |
| 57 | + // }); |
| 58 | +}); |
| 59 | +``` |
| 60 | + |
| 61 | +2. **Hook file**: `packages/rooks/src/hooks/${name}.ts` |
| 62 | + |
| 63 | +```typescript |
| 64 | +import { useCallback, useMemo, useState } from "react"; |
| 65 | + |
| 66 | +// Define comprehensive TypeScript types for parameters and return values |
| 67 | +export interface ${name}Options { |
| 68 | + // Define options interface if needed |
| 69 | +} |
| 70 | + |
| 71 | +export interface ${name}ReturnValue { |
| 72 | + // Define return value interface with all properties and methods |
| 73 | +} |
| 74 | + |
| 75 | +/** |
| 76 | + * ${name} |
| 77 | + * @description ${description} |
| 78 | + * @param {ParamType} param1 - Description of first parameter |
| 79 | + * @param {ParamType} param2 - Description of second parameter (if applicable) |
| 80 | + * @returns {${name}ReturnValue} Description of return value |
| 81 | + * @see {@link https://rooks.vercel.app/docs/${name}} |
| 82 | + * |
| 83 | + * @example |
| 84 | + * |
| 85 | + * const result = ${name}(/* example params */); |
| 86 | + * |
| 87 | + * // Show practical usage examples |
| 88 | + * // Demonstrate key functionality |
| 89 | + * // Include multiple use cases if applicable |
| 90 | + */ |
| 91 | +function ${name}( |
| 92 | + /* Define properly typed parameters */ |
| 93 | +): ${name}ReturnValue { |
| 94 | + // Initialize state using useState |
| 95 | + const [state, setState] = useState(/* initial state */); |
| 96 | + |
| 97 | + // Define memoized callback functions using useCallback |
| 98 | + const someAction = useCallback(() => { |
| 99 | + // Implementation |
| 100 | + }, [/* dependencies */]); |
| 101 | + |
| 102 | + // Use useMemo for complex computed values or return objects |
| 103 | + const returnValue = useMemo(() => { |
| 104 | + return { |
| 105 | + // Return object with all exposed functionality |
| 106 | + }; |
| 107 | + }, [/* dependencies */]); |
| 108 | + |
| 109 | + return returnValue; |
| 110 | +} |
| 111 | + |
| 112 | +export { ${name} }; |
| 113 | +``` |
| 114 | + |
| 115 | +3. **Documentation file**: `apps/website/content/docs/hooks/${name}.mdx` |
| 116 | + |
| 117 | +````markdown |
| 118 | +--- |
| 119 | +id: ${name} |
| 120 | +title: ${name} |
| 121 | +sidebar_label: ${name} |
| 122 | +--- |
| 123 | + |
| 124 | +## About |
| 125 | + |
| 126 | +${description} |
| 127 | + |
| 128 | +Additional details about what the hook does, when to use it, and any important considerations. |
| 129 | + |
| 130 | +## Examples |
| 131 | + |
| 132 | +```jsx |
| 133 | +import { ${name} } from "rooks"; |
| 134 | + |
| 135 | +export default function App() { |
| 136 | + const result = ${name}(/* example parameters */); |
| 137 | + |
| 138 | + return ( |
| 139 | + <div> |
| 140 | + {/* Practical example showing hook usage */} |
| 141 | + <p>Hook value: {JSON.stringify(result)}</p> |
| 142 | + </div> |
| 143 | + ); |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +```jsx |
| 148 | +// More complex example if applicable |
| 149 | +import { ${name}, useInput } from "rooks"; |
| 150 | + |
| 151 | +export default function AdvancedExample() { |
| 152 | + const hookResult = ${name}(/* advanced params */); |
| 153 | + const input = useInput(""); |
| 154 | + |
| 155 | + return ( |
| 156 | + <div> |
| 157 | + {/* Show practical integration with other hooks */} |
| 158 | + <input {...input} /> |
| 159 | + <button onClick={() => hookResult.someMethod()}> |
| 160 | + Trigger Action |
| 161 | + </button> |
| 162 | + </div> |
| 163 | + ); |
| 164 | +} |
| 165 | +``` |
| 166 | +```` |
| 167 | + |
| 168 | +### Required Updates |
| 169 | + |
| 170 | +4. **Add to hooks list**: Update `data/hooks-list.json` |
| 171 | + |
| 172 | + - Add new entry to the "hooks" array: `{"name": "${name}", "description": "${description}", "category": "${category}"}` |
| 173 | + - Keep the array sorted alphabetically by name |
| 174 | + |
| 175 | +5. **Export from main index**: |
| 176 | + - For regular hooks: Add `export { ${name} } from "@/hooks/${name}";` to `packages/rooks/src/index.ts` |
| 177 | + - For experimental hooks: Add `export { ${name} } from "./hooks/${name}";` to `packages/rooks/src/experimental.ts` |
| 178 | + |
| 179 | +### Experimental Hooks |
| 180 | + |
| 181 | +Experimental hooks have these differences: |
| 182 | + |
| 183 | +- They are exported from `packages/rooks/src/experimental.ts` instead of `packages/rooks/src/index.ts` |
| 184 | +- They include the experimental category and warning in documentation |
| 185 | +- All other file structures and paths remain the same |
| 186 | + |
| 187 | +### Development Approach |
| 188 | + |
| 189 | +Hooks must be created in a TDD approach. First step is to always create tests with expected behaviour. Then you must ask me to confirm that the test suite is good or if it should be improved. After tests are approved by me, then you can continue with editing the hook. |
| 190 | + |
| 191 | +Do not proceed further without asking me for clarifying questions regarding the test cases you wrote and implementation details. Ask me at least 1-2 clarification questions on implementation. |
| 192 | + |
| 193 | +First ensure that the hook has a comprehensive test suite. If there are no tests yet, first add test cases in the packages/rooks/src/**tests** directory. |
| 194 | + |
| 195 | +When ran in the terminal within the packages/rooks directory with pnpm test-hooks hookName eg `pnpm test-hooks useWillUnmount`, all the tests should pass. |
| 196 | + |
| 197 | +If tests don't pass, update the hook until all the tests pass. |
| 198 | + |
| 199 | +This project is a mono repo, with rooks being the main package. The hooks are present in the src/hooks folder of the package. The tests are present in src/**tests** folder of the rooks package. The rooks package is in the packages folder. |
| 200 | + |
| 201 | +### Code Style Guide |
| 202 | + |
| 203 | +Follow the code style guide in [code-style-guide.md](mdc:code-style-guide.md) |
| 204 | + |
| 205 | +Key patterns to follow based on existing hooks: |
| 206 | + |
| 207 | +- Use proper TypeScript interfaces for complex return types |
| 208 | +- Use `useCallback` for all functions to prevent unnecessary re-renders |
| 209 | +- Use `useMemo` for complex return objects or computed values |
| 210 | +- Include comprehensive JSDoc with @description, @param, @returns, @see, @example |
| 211 | +- Start each test with `expect.hasAssertions()` |
| 212 | +- Test initialization, all functionality, and memoization where applicable |
| 213 | +- Use descriptive test names that explain what is being tested |
| 214 | + |
| 215 | +### Examples |
| 216 | + |
| 217 | +See [useCounter.ts](mdc:packages/rooks/src/hooks/useCounter.ts) to understand how we write hooks. |
| 218 | +See [useArrayState.ts](mdc:packages/rooks/src/hooks/useArrayState.ts) for a more complex hook example. |
| 219 | +See [useCountdown.spec.tsx](mdc:packages/rooks/src/__tests__/useCountdown.spec.tsx) for inspiration on how to write tests for a hook. |
| 220 | + |
| 221 | +Write fully typesafe code. Writing any / unknown is not allowed. Only in test cases mocking situations, you can use any/unknown, but only if there is no better option. |
0 commit comments