├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── feature_request.md │ └── pull_request_template.md └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENCE.md ├── README.md ├── bin ├── cli.ts └── hookform-codemod.ts ├── jest.config.js ├── package.json ├── transforms ├── __testfixtures__ │ └── v7 │ │ ├── move-errors-to-formState │ │ ├── already-migrated-context.input.js │ │ ├── already-migrated-context.output.js │ │ ├── already-migrated.input.js │ │ ├── already-migrated.output.js │ │ ├── function-component-context.input.js │ │ ├── function-component-context.output.js │ │ ├── function-component.input.js │ │ ├── function-component.output.js │ │ ├── with-custom-errors-context.input.js │ │ ├── with-custom-errors-context.output.js │ │ ├── with-custom-errors.input.js │ │ ├── with-custom-errors.output.js │ │ ├── with-formState-1-context.input.js │ │ ├── with-formState-1-context.output.js │ │ ├── with-formState-1.input.js │ │ ├── with-formState-1.output.js │ │ ├── with-formState-2-context.input.js │ │ ├── with-formState-2-context.output.js │ │ ├── with-formState-2.input.js │ │ ├── with-formState-2.output.js │ │ ├── with-formState-3-context.input.js │ │ ├── with-formState-3-context.output.js │ │ ├── with-formState-3.input.js │ │ └── with-formState-3.output.js │ │ └── update-register │ │ ├── already-migrated-context.input.js │ │ ├── already-migrated-context.output.js │ │ ├── already-migrated.input.js │ │ ├── already-migrated.output.js │ │ ├── custom-register-context.input.js │ │ ├── custom-register-context.output.js │ │ ├── custom-register.input.js │ │ ├── custom-register.output.js │ │ ├── function-component-2-context.input.js │ │ ├── function-component-2-context.output.js │ │ ├── function-component-2.input.js │ │ ├── function-component-2.output.js │ │ ├── function-component-context.input.js │ │ ├── function-component-context.output.js │ │ ├── function-component.input.js │ │ ├── function-component.output.js │ │ ├── function-components-context.input.js │ │ ├── function-components-context.output.js │ │ ├── function-components.input.js │ │ ├── function-components.output.js │ │ ├── no-transform-context.input.js │ │ ├── no-transform-context.output.js │ │ ├── no-transform.input.js │ │ ├── no-transform.output.js │ │ ├── template-string-register.input.js │ │ └── template-string-register.output.js ├── __tests__ │ └── v7 │ │ ├── move-errors-to-formState.test.ts │ │ └── update-register.test.ts └── v7 │ ├── move-errors-to-formState.ts │ └── update-register.ts ├── tsconfig.json ├── utils ├── defineTest.ts ├── getUseFormDeclarators.ts └── keys.ts └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true 5 | }, 6 | extends: ['standard'], 7 | parser: '@typescript-eslint/parser', 8 | parserOptions: { 9 | ecmaVersion: 12, 10 | sourceType: 'module' 11 | }, 12 | ignorePatterns: ['**/__testfixtures__/**/*.js'], 13 | plugins: ['@typescript-eslint'], 14 | rules: { 15 | semi: ['error', 'always'], 16 | 'comma-dangle': ['error', 'never'], 17 | 'space-before-function-paren': ['error', 'never'] 18 | }, 19 | overrides: [ 20 | { 21 | files: 'transforms/__testfixtures__/**', 22 | rules: { 23 | 'no-undef': 'off' 24 | } 25 | } 26 | ] 27 | }; 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **Screenshots** 19 | If applicable, add screenshots to help explain your problem. 20 | 21 | **Desktop (please complete the following information):** 22 | 23 | - OS: [e.g. MacOs, Linux] 24 | - Node.js [e.g. 12, 14] 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/react-hook-form/react-hook-form/discussions 5 | about: Ask questions and discuss with other community members 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | ## Proposed Changes 4 | 5 | - 6 | - 7 | - 8 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - beta 7 | pull_request: 8 | paths-ignore: 9 | - '.gitignore' 10 | - '.npmignore' 11 | - '*.md' 12 | 13 | jobs: 14 | build: 15 | name: Lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout repo 20 | uses: actions/checkout@v2 21 | 22 | - name: Use Node ${{ matrix.node }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node }} 26 | 27 | - name: Install deps and build (with cache) 28 | uses: bahmutov/npm-install@v1 29 | 30 | - name: Lint 31 | run: | 32 | yarn lint 33 | yarn lint:types 34 | - name: Test 35 | run: yarn test --ci 36 | 37 | publish-module: 38 | name: 'Publish Module to NPM on Node ${{ matrix.node }} and ${{ matrix.os }}' 39 | needs: build 40 | if: github.repository == 'react-hook-form/codemod' && (github.ref =='refs/heads/master') 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout repo 44 | uses: actions/checkout@v2 45 | - name: 'Use Node ${{ matrix.node }}' 46 | uses: actions/setup-node@v1 47 | with: 48 | node-version: '${{ matrix.node }}' 49 | registry-url: 'https://registry.npmjs.org/' 50 | - name: Install dependencies 51 | uses: bahmutov/npm-install@v1 52 | - name: Build 53 | run: yarn build 54 | - name: Publish 55 | run: npx semantic-release 56 | env: 57 | NODE_AUTH_TOKEN: '${{secrets.NPM_TOKEN}}' 58 | GITHUB_TOKEN: '${{secrets.GITHUB_TOKEN}}' 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Coverage directory used by tools like istanbul 9 | coverage 10 | 11 | # Dependency directories 12 | node_modules/ 13 | 14 | # Optional npm cache directory 15 | .npm 16 | 17 | # Optional eslint cache 18 | .eslintcache 19 | 20 | # Output of 'npm pack' 21 | *.tgz 22 | 23 | # Yarn Integrity file 24 | .yarn-integrity 25 | 26 | .DS_Store 27 | dist 28 | .idea/ 29 | package-lock.json -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint:types && yarn lint-staged -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | transforms/__testfixtures__ -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none" 4 | } 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at bluebill1049@hotmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `React Hook Form` 2 | 3 | As the creators and maintainers of this project, we want to ensure that `codemod` lives and continues to grow and evolve. We would like to encourage everyone to help and support this library by contributing. 4 | 5 | ## Code contributions 6 | 7 | Here is a quick guide to doing code contributions to the library. 8 | 9 | 1. Fork and clone the repo to your local machine `git clone https://github.com/YOUR_GITHUB_USERNAME/codemod.git` 10 | 11 | 2. Create a new branch from `master` with a meaningful name for a new feature or an issue you want to work on: `git checkout -b your-meaningful-branch-name` 12 | 13 | 3. Install packages by running: 14 | 15 | > yarn 16 | 17 | 4. If you've added a code that should be tested, ensure the test suite still passes. 18 | 19 | > yarn test 20 | 21 | 5. Try to write some unit tests to cover as much of your code as possible. 22 | 23 | 6. Ensure your code lints without errors. 24 | 25 | > yarn lint 26 | 27 | 7. Ensure build passes. 28 | 29 | > yarn build 30 | 31 | 8. Push your branch: `git push -u origin your-meaningful-branch-name` 32 | 33 | 9. Submit a pull request to the upstream codemod repository. 34 | 35 | 10. Choose a descriptive title and describe your changes briefly. 36 | 37 | ## Coding style 38 | 39 | Please follow the coding style of the project. React Hook Form uses eslint and prettier. If possible, enable their respective plugins in your editor to get real-time feedback. The linting can be run manually with the following command: `yarn lint` 40 | 41 | ## License 42 | 43 | By contributing your code to the codemod GitHub repository, you agree to license your contribution under the MIT license. 44 | 45 | ### Contributors 46 | 47 | Thank you to all the people who have already contributed to React Hook Form! 48 | 49 | 50 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present Beier(Bill) Luo 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 OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | React Hook Form Logo - React hook custom hook for form validation 5 | 6 |

7 |
8 | 9 |

Performant, flexible and extensible forms with easy to use validation.

10 | 11 |
12 | 13 | [![npm downloads](https://img.shields.io/npm/dm/@hookform/codemod.svg?style=for-the-badge)](https://www.npmjs.com/package/@hookform/codemod) 14 | [![npm](https://img.shields.io/npm/dt/@hookform/codemod.svg?style=for-the-badge)](https://www.npmjs.com/package/@hookform/codemod) 15 | [![npm](https://img.shields.io/bundlephobia/minzip/@hookform/codemod?style=for-the-badge)](https://bundlephobia.com/result?p=@hookform/codemod) 16 | 17 |
18 | 19 | ## Goal 20 | 21 | Migrate your React Hook Form codebase from older version to new without the hassle by using our codemod scripts 22 | 23 | ## Usage 24 | 25 | `npx @hookform/codemod [...options]` 26 | 27 | - `transform` - name of transform, see available transforms below. 28 | - `path` - files or directory to transform 29 | - use the `--dry` option for a dry-run and use `--print` to print the output for comparison 30 | 31 | This will start an interactive wizard, and then run the specified transform. 32 | 33 | ## Included transforms 34 | 35 | ### Migrate from V6 to V7 36 | 37 | #### `v7/update-register` 38 | 39 | Update the `register` API inside a component that use `useForm` of React Hook Form. This transform is not applied if the component doesn't use `useForm`. 40 | 41 | npx @hookform/codemod v7/update-register 42 | 43 |
44 | Examples 45 | 46 | ```diff 47 | - 48 | + 49 | 50 | - 51 | + 52 | 53 | - 54 | + 55 | 56 | - 57 | + 58 | 59 | - 60 | + 61 | ``` 62 | 63 | With a custom `register` name 64 | 65 | ```diff 66 | function MyForm() { 67 | const { register: customRegister } = useForm(); 68 | 69 | return ( 70 |
71 | - 72 | + 73 |
74 | ); 75 | } 76 | ``` 77 | 78 |
79 | 80 | #### `v7/move-errors-to-formState` 81 | 82 | It moves `errors` API into `formState` inside a component that use `useForm` of React Hook Form. This transform is not applied if the component doesn't use `useForm`. 83 | 84 | npx @hookform/codemod v7/move-errors-to-formState 85 | 86 |
87 | Examples 88 | 89 | ```diff 90 | - const { errors } = useForm(); 91 | + const { formState: { errors } } = useForm(); 92 | 93 | - const { errors: customErrors } = useForm(); 94 | + const { formState: { errors: customErrors } } = useForm(); 95 | 96 | - const { errors, formState: { isDirty } } = useForm(); 97 | + const { formState: { isDirty, errors } } = useForm(); 98 | 99 | - const { errors: customErrors, formState: { isDirty } } = useForm(); 100 | + const { formState: { isDirty, errors: customErrors } } = useForm(); 101 | ``` 102 | 103 | With a custom `register` name 104 | 105 | ```diff 106 | function MyForm() { 107 | - const { errors, formState } = useForm(); 108 | + const { formState } = useForm(); 109 | + const { errors } = formState; 110 | 111 | const isDirty = formState.isDirty; 112 | 113 | return ( 114 | // 115 | ); 116 | } 117 | ``` 118 | 119 |
120 | 121 | ## Backers 122 | 123 | Thanks goes to all our backers! [[Become a backer](https://opencollective.com/react-hook-form#backer)]. 124 | 125 | 126 | 127 | 128 | 129 | ## Organizations 130 | 131 | Thanks goes to these wonderful organizations! [[Contribute](https://opencollective.com/react-hook-form/contribute)]. 132 | 133 | 134 | 135 | 136 | 137 | ## Contributors 138 | 139 | Thanks goes to these wonderful people! [[Become a contributor](CONTRIBUTING.md)]. 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /bin/cli.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | * 7 | */ 8 | // Based on https://github.com/reactjs/react-codemod/blob/dd8671c9a470a2c342b221ec903c574cf31e9f57/bin/cli.js 9 | // @hookform/codemod optional-name-of-transform optional/path/to/src [...options] 10 | 11 | const globby = require('globby'); 12 | const inquirer = require('inquirer'); 13 | const meow = require('meow'); 14 | const path = require('path'); 15 | const execa = require('execa'); 16 | const chalk = require('chalk'); 17 | const isGitClean = require('is-git-clean'); 18 | 19 | const transformerDirectory = path.join(__dirname, '../', 'transforms'); 20 | const jscodeshiftExecutable = require.resolve('.bin/jscodeshift'); 21 | 22 | function checkGitStatus(force) { 23 | let clean = false; 24 | let errorMessage = 'Unable to determine if git directory is clean'; 25 | try { 26 | clean = isGitClean.sync(process.cwd()); 27 | errorMessage = 'Git directory is not clean'; 28 | } catch (err) { 29 | if (err && err.stderr && err.stderr.indexOf('Not a git repository') >= 0) { 30 | clean = true; 31 | } 32 | } 33 | 34 | if (!clean) { 35 | if (force) { 36 | console.log(`WARNING: ${errorMessage}. Forcibly continuing.`); 37 | } else { 38 | console.log('Thank you for using @hookform/codemod!'); 39 | console.log( 40 | chalk.yellow( 41 | '\nBut before we continue, please stash or commit your git changes.' 42 | ) 43 | ); 44 | console.log( 45 | '\nYou may use the --force flag to override this safety check.' 46 | ); 47 | process.exit(1); 48 | } 49 | } 50 | } 51 | 52 | function runTransform({ files, flags, transformer }) { 53 | const transformerPath = path.join(transformerDirectory, `${transformer}.js`); 54 | 55 | let args = []; 56 | 57 | const { dry, print } = flags; 58 | 59 | if (dry) { 60 | args.push('--dry'); 61 | } 62 | if (print) { 63 | args.push('--print'); 64 | } 65 | 66 | args.push('--verbose=2'); 67 | 68 | args.push('--ignore-pattern=**/node_modules/**'); 69 | 70 | args.push('--extensions=tsx,ts,jsx,js'); 71 | args.push('--parser=tsx'); 72 | 73 | args = args.concat(['--transform', transformerPath]); 74 | 75 | if (flags.jscodeshift) { 76 | args = args.concat(flags.jscodeshift); 77 | } 78 | 79 | args = args.concat(files); 80 | 81 | console.log(`Executing command: jscodeshift ${args.join(' ')}`); 82 | 83 | const result = execa.sync(jscodeshiftExecutable, args, { 84 | stdio: 'inherit', 85 | stripEof: false 86 | }); 87 | 88 | if (result.error) { 89 | throw result.error; 90 | } 91 | } 92 | 93 | const TRANSFORMER_INQUIRER_CHOICES = [ 94 | { 95 | name: 'v7/update-register: Transforms register api from v6 to v7', 96 | value: 'v7/update-register' 97 | }, 98 | { 99 | name: 'v7/move-errors-to-formState: Move `errors` key to `formState` key', 100 | value: 'v7/move-errors-to-formState' 101 | } 102 | ]; 103 | 104 | function expandFilePathsIfNeeded(filesBeforeExpansion) { 105 | const shouldExpandFiles = filesBeforeExpansion.some((file) => 106 | file.includes('*') 107 | ); 108 | return shouldExpandFiles 109 | ? globby.sync(filesBeforeExpansion) 110 | : filesBeforeExpansion; 111 | } 112 | 113 | function run() { 114 | const cli = meow( 115 | { 116 | description: 'Codemods for updating react-hook-form apps.', 117 | help: ` 118 | Usage 119 | $ npx @hookform/codemod <...options> 120 | transform One of the choices from https://github.com/react-hook-form/codemod/blob/master/README.md 121 | path Files or directory to transform. Can be a glob like src/**.js 122 | Options 123 | --force Bypass Git safety checks and forcibly run codemods 124 | --dry Dry run (no changes are made to files) 125 | --print Print transformed files to your terminal 126 | --jscodeshift (Advanced) Pass options directly to jscodeshift 127 | ` 128 | }, 129 | { 130 | boolean: ['force', 'dry', 'print', 'help'], 131 | string: ['_'], 132 | alias: { 133 | h: 'help' 134 | } 135 | } 136 | ); 137 | 138 | if (!cli.flags.dry) { 139 | checkGitStatus(cli.flags.force); 140 | } 141 | 142 | if ( 143 | cli.input[0] && 144 | !TRANSFORMER_INQUIRER_CHOICES.find((x) => x.value === cli.input[0]) 145 | ) { 146 | console.error('Invalid transform choice, pick one of:'); 147 | console.error( 148 | TRANSFORMER_INQUIRER_CHOICES.map((x) => '- ' + x.value).join('\n') 149 | ); 150 | process.exit(1); 151 | } 152 | 153 | inquirer 154 | .prompt([ 155 | { 156 | type: 'input', 157 | name: 'files', 158 | message: 'On which files or directory should the codemods be applied?', 159 | when: !cli.input[1], 160 | default: '.', 161 | // validate: () => 162 | filter: (files) => files.trim() 163 | }, 164 | { 165 | type: 'list', 166 | name: 'transformer', 167 | message: 'Which transform would you like to apply?', 168 | when: !cli.input[0], 169 | pageSize: TRANSFORMER_INQUIRER_CHOICES.length, 170 | choices: TRANSFORMER_INQUIRER_CHOICES 171 | } 172 | ]) 173 | .then((answers) => { 174 | const { files, transformer } = answers; 175 | 176 | const filesBeforeExpansion = cli.input[1] || files; 177 | const filesExpanded = expandFilePathsIfNeeded([filesBeforeExpansion]); 178 | 179 | const selectedTransformer = cli.input[0] || transformer; 180 | 181 | if (!filesExpanded.length) { 182 | console.log( 183 | `No files found matching ${filesBeforeExpansion.join(' ')}` 184 | ); 185 | return null; 186 | } 187 | 188 | return runTransform({ 189 | files: filesExpanded, 190 | flags: cli.flags, 191 | transformer: selectedTransformer 192 | }); 193 | }); 194 | } 195 | 196 | module.exports = { 197 | run: run, 198 | runTransform: runTransform, 199 | checkGitStatus: checkGitStatus, 200 | jscodeshiftExecutable: jscodeshiftExecutable, 201 | transformerDirectory: transformerDirectory 202 | }; 203 | -------------------------------------------------------------------------------- /bin/hookform-codemod.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Copyright 2015-present, Facebook, Inc. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | * 9 | */ 10 | // Based on https://github.com/reactjs/react-codemod/blob/dd8671c9a470a2c342b221ec903c574cf31e9f57/bin/react-codemod.js 11 | // @hookform/codemod optional-name-of-transform optional/path/to/src [...options] 12 | 13 | require('./cli').run(); 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node' 4 | }; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hookform/codemod", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "bin": "./dist/bin/hookform-codemod.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "test": "jest --bail", 9 | "lint": "eslint . --ext .ts,.js --ignore-path .gitignore", 10 | "lint:types": "tsc --noEmit", 11 | "postinstall": "husky install", 12 | "prepublishOnly": "pinst --disable", 13 | "postpublish": "pinst --enable" 14 | }, 15 | "files": [ 16 | "dist" 17 | ], 18 | "dependencies": { 19 | "chalk": "^4.1.0", 20 | "execa": "^5.0.0", 21 | "globby": "^11.0.1", 22 | "inquirer": "^7.3.3", 23 | "is-git-clean": "^1.1.0", 24 | "jscodeshift": "^0.11.0", 25 | "meow": "^8.1.0" 26 | }, 27 | "devDependencies": { 28 | "@types/jscodeshift": "^0.7.2", 29 | "@typescript-eslint/eslint-plugin": "^4.11.1", 30 | "@typescript-eslint/parser": "^4.11.1", 31 | "eslint": "^7.17.0", 32 | "eslint-config-standard": "^16.0.2", 33 | "eslint-plugin-import": "^2.22.1", 34 | "eslint-plugin-node": "^11.1.0", 35 | "eslint-plugin-promise": "^4.2.1", 36 | "husky": "^5.0.9", 37 | "jest": "^26.6.3", 38 | "lint-staged": ">=10", 39 | "pinst": "^2.1.4", 40 | "prettier": "2.2.1", 41 | "react-hook-form": "^6.14.0", 42 | "semantic-release": "^17.3.8", 43 | "ts-jest": "^26.4.4", 44 | "typescript": "^4.1.3" 45 | }, 46 | "lint-staged": { 47 | "*.{js,ts}": "eslint --cache --fix", 48 | "*.{js,css,md}": "prettier --write" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/already-migrated-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { formState: { 5 | errors, 6 | } } = useFormContext(); 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/already-migrated-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { formState: { 5 | errors, 6 | } } = useFormContext(); 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/already-migrated.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { formState: { 5 | errors, 6 | } } = useForm(); 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/already-migrated.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { formState: { 5 | errors, 6 | } } = useForm(); 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/function-component-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors } = useFormContext(); 5 | 6 | return ( 7 |
8 | {errors.username.message} 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/function-component-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { formState: { 5 | errors, 6 | } } = useFormContext(); 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/function-component.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors } = useForm(); 5 | 6 | return ( 7 |
8 | {errors.username.message} 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/function-component.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { formState: { 5 | errors, 6 | } } = useForm(); 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-custom-errors-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors: customErrors } = useFormContext(); 5 | 6 | return ( 7 |
8 | {customErrors.username.message} 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-custom-errors-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { formState: { 5 | errors: customErrors, 6 | } } = useFormContext(); 7 | 8 | return ( 9 |
10 | {customErrors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-custom-errors.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors: customErrors } = useForm(); 5 | 6 | return ( 7 |
8 | {customErrors.username.message} 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-custom-errors.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { formState: { 5 | errors: customErrors, 6 | } } = useForm(); 7 | 8 | return ( 9 |
10 | {customErrors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-1-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors, formState: { isDirty } } = useFormContext(); 5 | 6 | return ( 7 |
8 | {errors.username.message} 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-1-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { 5 | formState: { 6 | isDirty, 7 | errors, 8 | }, 9 | } = useFormContext(); 10 | 11 | return ( 12 |
13 | {errors.username.message} 14 |
15 | ); 16 | }; 17 | 18 | export default Form; 19 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-1.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors, formState: { isDirty } } = useForm(); 5 | 6 | return ( 7 |
8 | {errors.username.message} 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-1.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { 5 | formState: { 6 | isDirty, 7 | errors, 8 | }, 9 | } = useForm(); 10 | 11 | return ( 12 |
13 | {errors.username.message} 14 |
15 | ); 16 | }; 17 | 18 | export default Form; 19 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-2-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors, formState } = useFormContext(); 5 | 6 | const diry = formState.isDirty; 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-2-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { 5 | formState, 6 | } = useFormContext(); 7 | 8 | const { 9 | errors, 10 | } = formState; 11 | 12 | const diry = formState.isDirty; 13 | 14 | return ( 15 |
16 | {errors.username.message} 17 |
18 | ); 19 | }; 20 | 21 | export default Form; 22 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-2.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors, formState } = useForm(); 5 | 6 | const diry = formState.isDirty; 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-2.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { 5 | formState, 6 | } = useForm(); 7 | 8 | const { 9 | errors, 10 | } = formState; 11 | 12 | const diry = formState.isDirty; 13 | 14 | return ( 15 |
16 | {errors.username.message} 17 |
18 | ); 19 | }; 20 | 21 | export default Form; 22 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-3-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors: customErrors, formState } = useFormContext(); 5 | 6 | const diry = formState.isDirty; 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-3-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { 5 | formState, 6 | } = useFormContext(); 7 | 8 | const { 9 | errors: customErrors, 10 | } = formState; 11 | 12 | const diry = formState.isDirty; 13 | 14 | return ( 15 |
16 | {errors.username.message} 17 |
18 | ); 19 | }; 20 | 21 | export default Form; 22 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-3.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { errors: customErrors, formState } = useForm(); 5 | 6 | const diry = formState.isDirty; 7 | 8 | return ( 9 |
10 | {errors.username.message} 11 |
12 | ); 13 | }; 14 | 15 | export default Form; 16 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/move-errors-to-formState/with-formState-3.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { 5 | formState, 6 | } = useForm(); 7 | 8 | const { 9 | errors: customErrors, 10 | } = formState; 11 | 12 | const diry = formState.isDirty; 13 | 14 | return ( 15 |
16 | {errors.username.message} 17 |
18 | ); 19 | }; 20 | 21 | export default Form; 22 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/already-migrated-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register } = useFormContext(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default Form; 17 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/already-migrated-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register } = useFormContext(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default Form; 17 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/already-migrated.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register } = useForm(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default Form; 17 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/already-migrated.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register } = useForm(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default Form; 17 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/custom-register-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext as useRenamedForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register: customRegister } = useRenamedForm(); 5 | 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/custom-register-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext as useRenamedForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register: customRegister } = useRenamedForm(); 5 | 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/custom-register.input.js: -------------------------------------------------------------------------------- 1 | import { useForm as useRenamedForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register: customRegister } = useRenamedForm(); 5 | 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/custom-register.output.js: -------------------------------------------------------------------------------- 1 | import { useForm as useRenamedForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register: customRegister } = useRenamedForm(); 5 | 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default Form; 14 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-component-2-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register, handleSubmit } = useFormContext(); 5 | 6 | return ( 7 |
8 | 9 | 14 | 15 | 16 | !!value 21 | })} 22 | type="password" 23 | /> 24 | 25 |
26 | ); 27 | }; 28 | 29 | export default Form; 30 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-component-2-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register, handleSubmit } = useFormContext(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 | !!value 16 | })} 17 | type="password" /> 18 | 19 |
20 | ); 21 | }; 22 | 23 | export default Form; 24 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-component-2.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register, handleSubmit } = useForm(); 5 | 6 | return ( 7 |
8 | 9 | 14 | 15 | 16 | !!value 21 | })} 22 | type="password" 23 | /> 24 | 25 |
26 | ); 27 | }; 28 | 29 | export default Form; 30 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-component-2.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register, handleSubmit } = useForm(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 | !!value 16 | })} 17 | type="password" /> 18 | 19 |
20 | ); 21 | }; 22 | 23 | export default Form; 24 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-component-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | function Input() { 4 | return ; 5 | } 6 | 7 | const Form = () => { 8 | const { register } = useFormContext(); 9 | 10 | useEffect(() => { 11 | register('example'); 12 | }, [register]); 13 | 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default Form; 26 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-component-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | function Input() { 4 | return ; 5 | } 6 | 7 | const Form = () => { 8 | const { register } = useFormContext(); 9 | 10 | useEffect(() => { 11 | register('example'); 12 | }, [register]); 13 | 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default Form; 26 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-component.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | function Input() { 4 | return ; 5 | } 6 | 7 | const Form = () => { 8 | const { register } = useForm(); 9 | 10 | useEffect(() => { 11 | register('example'); 12 | }, [register]); 13 | 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default Form; 26 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-component.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | function Input() { 4 | return ; 5 | } 6 | 7 | const Form = () => { 8 | const { register } = useForm(); 9 | 10 | useEffect(() => { 11 | register('example'); 12 | }, [register]); 13 | 14 | return ( 15 |
16 | 17 | 18 | 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default Form; 26 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-components-context.input.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register } = useFormContext(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default Form; 17 | 18 | export const Form2 = () => { 19 | const { register } = useFormContext(); 20 | 21 | return ( 22 |
23 | 24 | 25 | 26 | 27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-components-context.output.js: -------------------------------------------------------------------------------- 1 | import { useFormContext } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register } = useFormContext(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default Form; 17 | 18 | export const Form2 = () => { 19 | const { register } = useFormContext(); 20 | 21 | return ( 22 |
23 | 24 | 25 | 26 | 27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-components.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register } = useForm(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default Form; 17 | 18 | export const Form2 = () => { 19 | const { register } = useForm(); 20 | 21 | return ( 22 |
23 | 24 | 25 | 26 | 27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/function-components.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const Form = () => { 4 | const { register } = useForm(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 |
13 | ); 14 | }; 15 | 16 | export default Form; 17 | 18 | export const Form2 = () => { 19 | const { register } = useForm(); 20 | 21 | return ( 22 |
23 | 24 | 25 | 26 | 27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/no-transform-context.input.js: -------------------------------------------------------------------------------- 1 | const Form = () => { 2 | return ( 3 |
4 | 5 |
6 | ); 7 | }; 8 | 9 | export default Form; 10 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/no-transform-context.output.js: -------------------------------------------------------------------------------- 1 | const Form = () => { 2 | return ( 3 |
4 | 5 |
6 | ); 7 | }; 8 | 9 | export default Form; 10 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/no-transform.input.js: -------------------------------------------------------------------------------- 1 | const Form = () => { 2 | return ( 3 |
4 | 5 |
6 | ); 7 | }; 8 | 9 | export default Form; 10 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/no-transform.output.js: -------------------------------------------------------------------------------- 1 | const Form = () => { 2 | return ( 3 |
4 | 5 |
6 | ); 7 | }; 8 | 9 | export default Form; 10 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/template-string-register.input.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const id = 1; 4 | 5 | const Form = () => { 6 | const { register } = useForm(); 7 | 8 | useEffect(() => { 9 | register(`example.${id}`); 10 | }, [register]); 11 | 12 | return ( 13 |
14 | 15 |
16 | ); 17 | }; 18 | 19 | export default Form; 20 | -------------------------------------------------------------------------------- /transforms/__testfixtures__/v7/update-register/template-string-register.output.js: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | 3 | const id = 1; 4 | 5 | const Form = () => { 6 | const { register } = useForm(); 7 | 8 | useEffect(() => { 9 | register(`example.${id}`); 10 | }, [register]); 11 | 12 | return ( 13 |
14 | 15 |
16 | ); 17 | }; 18 | 19 | export default Form; 20 | -------------------------------------------------------------------------------- /transforms/__tests__/v7/move-errors-to-formState.test.ts: -------------------------------------------------------------------------------- 1 | import { defineTest } from '../../../utils/defineTest'; 2 | 3 | /* global jest */ 4 | jest.autoMockOff(); 5 | 6 | const fixtures = [ 7 | 'function-component', 8 | 'function-component-context', 9 | 'with-custom-errors', 10 | 'with-custom-errors-context', 11 | 'with-formState-1', 12 | 'with-formState-1-context', 13 | 'with-formState-2', 14 | 'with-formState-2-context', 15 | 'with-formState-3', 16 | 'with-formState-3-context', 17 | 'already-migrated', 18 | 'already-migrated-context' 19 | ]; 20 | 21 | fixtures.forEach(defineTest(__dirname, 'v7/move-errors-to-formState')); 22 | -------------------------------------------------------------------------------- /transforms/__tests__/v7/update-register.test.ts: -------------------------------------------------------------------------------- 1 | import { defineTest } from '../../../utils/defineTest'; 2 | 3 | /* global jest */ 4 | jest.autoMockOff(); 5 | 6 | const fixtures = [ 7 | 'already-migrated', 8 | 'function-component-2', 9 | 'function-component', 10 | 'function-components', 11 | 'no-transform', 12 | 'custom-register', 13 | 'already-migrated-context', 14 | 'function-component-2-context', 15 | 'function-component-context', 16 | 'function-components-context', 17 | 'no-transform-context', 18 | 'custom-register-context', 19 | 'template-string-register' 20 | ]; 21 | 22 | fixtures.forEach(defineTest(__dirname, 'v7/update-register')); 23 | -------------------------------------------------------------------------------- /transforms/v7/move-errors-to-formState.ts: -------------------------------------------------------------------------------- 1 | import { FileInfo, API, ASTPath, VariableDeclarator } from 'jscodeshift'; 2 | import { 3 | findUseFormImportDeclarations, 4 | findUseFormDeclarators 5 | } from '../../utils/getUseFormDeclarators'; 6 | import { USE_FORM_CONTEXT } from '../../utils/keys'; 7 | 8 | /** 9 | * `move-errors-to-formState` codemod which transforms react-hook-form v6 register api to v7 10 | */ 11 | export default function transformer(file: FileInfo, api: API, options) { 12 | const j = api.jscodeshift; 13 | const root = j(file.source); 14 | const printOptions = options.printOptions || { 15 | quote: 'single', 16 | trailingComma: true 17 | }; 18 | 19 | const transformDeclaration = (useFormDeclarator: ASTPath) => { 20 | /** 21 | * We search for all `errors` properties 22 | * @example 23 | * const { errors } = useForm(); 24 | * ^ 25 | * */ 26 | const errorsProperties = j(useFormDeclarator).find(j.Identifier, { 27 | name: 'errors' 28 | }); 29 | 30 | errorsProperties 31 | .filter((p) => p.name === 'key') 32 | .forEach((errorProperty) => { 33 | /** 34 | * Retrieve `errors` property name 35 | * @example 36 | * const { errors } = useForm(); 37 | * const { errors: customErrors } = useForm(); 38 | * */ 39 | const error = errorProperty.parentPath.value; 40 | 41 | /** 42 | * We search for all `formState` 43 | * @example 44 | * const { formState } = useForm(); 45 | * const { formState: customFormState } = useForm(); 46 | * */ 47 | const formStateProperties = j(useFormDeclarator).find(j.Identifier, { 48 | name: 'formState' 49 | }); 50 | 51 | /** 52 | * If any `formState`, create one then add `errors` 53 | * @example 54 | * const { formState: { errors } } = useForm(); 55 | * */ 56 | if (formStateProperties.length === 0) { 57 | if (useFormDeclarator.value.id.type === 'ObjectPattern') { 58 | useFormDeclarator.value.id.properties.push( 59 | j.property( 60 | 'init', 61 | j.identifier('formState'), 62 | j.objectPattern([error]) 63 | ) 64 | ); 65 | } 66 | } else { 67 | formStateProperties.forEach((formStateProperty) => { 68 | if (formStateProperty.name !== 'key') { 69 | return; 70 | } 71 | 72 | /** 73 | * If `formState` is an object then add `errors` 74 | * @example 75 | * const { formState: { isDirty, errors } } = useForm(); 76 | * ^ 77 | * */ 78 | if ( 79 | formStateProperty.parent.value.value.type === 'ObjectPattern' 80 | ) { 81 | formStateProperty.parentPath.value.value.properties.push( 82 | error.value 83 | ); 84 | } else if ( 85 | formStateProperty.parentPath.value.value.type === 'Identifier' 86 | ) { 87 | /** 88 | * If `formState` is a key, create a `const` after `useForm` declaration 89 | * @example 90 | * const { formState } = useForm(); 91 | * const { errors } = formState; 92 | * */ 93 | j(useFormDeclarator.parentPath.parentPath).insertAfter( 94 | j.variableDeclaration('const', [ 95 | j.variableDeclarator( 96 | j.objectPattern([error]), 97 | j.identifier( 98 | formStateProperty.parentPath.value.value.name 99 | ) 100 | ) 101 | ]) 102 | ); 103 | } 104 | }); 105 | } 106 | 107 | j(errorProperty.parentPath).remove(); 108 | }); 109 | }; 110 | 111 | findUseFormImportDeclarations(root, j).forEach((useFormImportDeclaration) => { 112 | const useFormDeclarators = findUseFormDeclarators( 113 | root, 114 | j 115 | )(useFormImportDeclaration); 116 | 117 | if (useFormDeclarators) { 118 | useFormDeclarators.forEach(transformDeclaration); 119 | } 120 | 121 | const useFormContextDeclarators = findUseFormDeclarators( 122 | root, 123 | j, 124 | USE_FORM_CONTEXT 125 | )(useFormImportDeclaration); 126 | 127 | if (useFormContextDeclarators) { 128 | useFormContextDeclarators.forEach(transformDeclaration); 129 | } 130 | }); 131 | 132 | return root.toSource(printOptions); 133 | } 134 | -------------------------------------------------------------------------------- /transforms/v7/update-register.ts: -------------------------------------------------------------------------------- 1 | import { FileInfo, API, ASTPath, VariableDeclarator } from 'jscodeshift'; 2 | import { 3 | findUseFormImportDeclarations, 4 | findUseFormDeclarators 5 | } from '../../utils/getUseFormDeclarators'; 6 | import { REGISTER, USE_FORM_CONTEXT } from '../../utils/keys'; 7 | 8 | /** 9 | * `update-register` codemod which transforms react-hook-form v6 register api to v7 10 | */ 11 | export default function transformer(file: FileInfo, api: API, options) { 12 | const j = api.jscodeshift; 13 | const root = j(file.source); 14 | const printOptions = options.printOptions || { 15 | quote: 'single', 16 | trailingComma: true 17 | }; 18 | 19 | const transformDeclaration = ( 20 | useFormDeclarator: ASTPath 21 | ) => { 22 | /** 23 | * We search for all `register` properties 24 | * @example 25 | * const { register } = useForm(); 26 | * ^ 27 | * */ 28 | const registerProperties = j(useFormDeclarator).find(j.Identifier, { 29 | name: REGISTER 30 | }); 31 | 32 | registerProperties.forEach((registerProperty) => { 33 | /** 34 | * Retrieve `register` property name 35 | * @example 36 | * const { register } = useForm(); 37 | * const { register: registerCustomName } = useForm(); 38 | * */ 39 | const register = registerProperty.parentPath.value.value.name; 40 | 41 | /** 42 | * Retrieve the related parent component 43 | */ 44 | j(registerProperty) 45 | .closestScope() 46 | .forEach((component) => { 47 | /** 48 | * We search for all `register` 49 | * */ 50 | const registerIdentifiers = j(component).find(j.Identifier, { 51 | name: register 52 | }); 53 | 54 | registerIdentifiers.forEach((registerIdentifier) => { 55 | const isCallExpression = 56 | registerIdentifier.parent.value.type === 'CallExpression'; 57 | 58 | /** 59 | * We filter `register` to keep only `ref` 60 | * @example 61 | * 62 | * 63 | * 64 | * 65 | * */ 66 | if ( 67 | registerIdentifier.parent.value.type !== 68 | 'JSXExpressionContainer' && 69 | !isCallExpression 70 | ) { 71 | return; 72 | } 73 | 74 | const maybeJsxAttribute = isCallExpression 75 | ? registerIdentifier.parent.parent.parent 76 | : registerIdentifier.parent.parent; 77 | 78 | if (maybeJsxAttribute.value.name?.name !== 'ref') return; 79 | 80 | const jsxOpeningElement = j(maybeJsxAttribute.parentPath); 81 | // We search for all `name` attribute of the JSX element 82 | const nameAttributes = jsxOpeningElement.find(j.JSXAttribute, { 83 | name: { name: 'name' } 84 | }); 85 | 86 | nameAttributes.forEach((nameAttribute) => { 87 | if ( 88 | nameAttribute.value.value.type !== 'Literal' && 89 | nameAttribute.value.value.type !== 'StringLiteral' && 90 | nameAttribute.value.value.type !== 'JSXExpressionContainer' // Handle template string case 91 | ) { 92 | return; 93 | } 94 | 95 | const name = 96 | nameAttribute.value.value.type === 'JSXExpressionContainer' 97 | ? nameAttribute.value.value.expression 98 | : j.literal(nameAttribute.value.value.value); 99 | 100 | /** 101 | * Add name to register('name') 102 | * ^ 103 | * */ 104 | const args = [name]; 105 | 106 | // If `register` have params, we add them to our new args 107 | if (isCallExpression) { 108 | args.push(...registerIdentifier.parent.value.arguments); 109 | } 110 | 111 | /** 112 | * Replace `name` attribute with the new `register` api and args 113 | * @example 114 | * name="example" => {...register("example")} 115 | * */ 116 | j(nameAttribute).replaceWith( 117 | j.jsxSpreadAttribute( 118 | j.callExpression(j.identifier(register), args) 119 | ) 120 | ); 121 | }); 122 | 123 | // We remove the old `ref` attribute 124 | j(maybeJsxAttribute).remove(); 125 | }); 126 | }); 127 | }); 128 | }; 129 | 130 | findUseFormImportDeclarations(root, j).forEach((useFormImportDeclaration) => { 131 | const useFormDeclarators = findUseFormDeclarators( 132 | root, 133 | j 134 | )(useFormImportDeclaration); 135 | 136 | if (useFormDeclarators) { 137 | useFormDeclarators.forEach(transformDeclaration); 138 | } 139 | 140 | const useFormContextDeclarators = findUseFormDeclarators( 141 | root, 142 | j, 143 | USE_FORM_CONTEXT 144 | )(useFormImportDeclaration); 145 | 146 | if (useFormContextDeclarators) { 147 | useFormContextDeclarators.forEach(transformDeclaration); 148 | } 149 | }); 150 | 151 | return root.toSource(printOptions); 152 | } 153 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "sourceMap": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "target": "es2015", 8 | "downlevelIteration": true, 9 | "preserveWatchOutput": true, 10 | "outDir": "dist" 11 | }, 12 | "include": ["**/*.ts"], 13 | "exclude": ["node_modules", "transforms/__tests__", "dist"] 14 | } 15 | -------------------------------------------------------------------------------- /utils/defineTest.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { defineTest as jscodeshiftDefineTest } from 'jscodeshift/dist/testUtils'; 3 | 4 | export const defineTest = (basePath: string, testDirectory: string) => ( 5 | testSuite: string 6 | ): void => 7 | jscodeshiftDefineTest( 8 | path.join(basePath, '..'), 9 | testDirectory, 10 | null, 11 | `${testDirectory}/${testSuite}` 12 | ); 13 | -------------------------------------------------------------------------------- /utils/getUseFormDeclarators.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ASTPath, 3 | Collection, 4 | ImportDeclaration, 5 | JSCodeshift, 6 | VariableDeclarator 7 | } from 'jscodeshift'; 8 | import { REACT_HOOK_FORM, USE_FORM } from './keys'; 9 | 10 | export function findUseFormImportDeclarations( 11 | root: Collection, 12 | j: JSCodeshift 13 | ) { 14 | /** 15 | * We search for all react-hook-form's imports 16 | * @example 17 | * import { ... } from "react-hook-form" 18 | * */ 19 | const reactHookFormImports = root.find(j.ImportDeclaration, { 20 | source: { value: REACT_HOOK_FORM } 21 | }); 22 | 23 | return reactHookFormImports; 24 | } 25 | 26 | export const findUseFormDeclarators = ( 27 | root: Collection, 28 | j: JSCodeshift, 29 | specifierName = USE_FORM 30 | ) => ( 31 | path: ASTPath 32 | ): Collection | null => { 33 | /** 34 | * We search for `useForm` in import node 35 | * @example 36 | * import { useForm } from "react-hook-form"; 37 | * ^ 38 | * */ 39 | const useFormImport = path.value.specifiers.find( 40 | (specifier) => 41 | specifier.type === 'ImportSpecifier' && 42 | specifier.imported.name === specifierName 43 | ); 44 | 45 | if (!useFormImport) return null; 46 | 47 | /** 48 | * Retrieve useForm method name: `useForm` or `useFormCustomName` 49 | * @example 50 | * import { useForm } from "react-hook-form"; 51 | * import { useForm: useFormCustomName } from "react-hook-form"; 52 | * */ 53 | const useForm = useFormImport.local.name; 54 | 55 | /** 56 | * We search for all uses of `useForm` or `useFormCustomName` 57 | * @example 58 | * const { ... } = useForm(); 59 | * const { ... } = useFormCustomName(); 60 | * */ 61 | return root.find(j.VariableDeclarator, { 62 | init: { callee: { name: useForm } } 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /utils/keys.ts: -------------------------------------------------------------------------------- 1 | export const REACT_HOOK_FORM = 'react-hook-form'; 2 | export const USE_FORM = 'useForm'; 3 | export const USE_FORM_CONTEXT = 'useFormContext'; 4 | export const REGISTER = 'register'; 5 | --------------------------------------------------------------------------------