├── .canvas ├── .github └── workflows │ └── canvas-sync-ruby-update.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── __tests__ └── App.test.js ├── components └── Form.js └── index.js /.canvas: -------------------------------------------------------------------------------- 1 | --- 2 | :lessons: 3 | - :id: 250558 4 | :course_id: 6667 5 | :canvas_url: https://learning.flatironschool.com/courses/6667/pages/react-forms-submit 6 | :type: page 7 | - :id: 296593 8 | :course_id: 7553 9 | :canvas_url: https://learning.flatironschool.com/courses/7553/pages/react-forms-submit 10 | :type: page 11 | -------------------------------------------------------------------------------- /.github/workflows/canvas-sync-ruby-update.yml: -------------------------------------------------------------------------------- 1 | name: Sync with Canvas Ruby v2.7 2 | 3 | on: 4 | push: 5 | branches: [master, main] 6 | paths: 7 | - 'README.md' 8 | 9 | jobs: 10 | sync: 11 | name: Sync with Canvas 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Set up Ruby 19 | uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: 2.7 22 | 23 | - name: Install github-to-canvas 24 | run: gem install github-to-canvas 25 | 26 | # Secret stored in learn-co-curriculum Settings/Secrets 27 | - name: Sync from .canvas file 28 | run: github-to-canvas -a -lr 29 | env: 30 | CANVAS_API_KEY: ${{ secrets.CANVAS_API_KEY }} 31 | CANVAS_API_PATH: ${{ secrets.CANVAS_API_PATH }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Learn-specific .results.json 43 | .results.json 44 | 45 | # Yarn Lockfile 46 | yarn.lock 47 | 48 | # DS_Store 49 | .DS_Store 50 | 51 | .eslintcache -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Learn.co Curriculum 2 | 3 | We're really excited that you're about to contribute to the [open curriculum](https://learn.co/content-license) on [Learn.co](https://learn.co). If this is your first time contributing, please continue reading to learn how to make the most meaningful and useful impact possible. 4 | 5 | ## Raising an Issue to Encourage a Contribution 6 | 7 | If you notice a problem with the curriculum that you believe needs improvement 8 | but you're unable to make the change yourself, you should raise a Github issue 9 | containing a clear description of the problem. Include relevant snippets of 10 | the content and/or screenshots if applicable. Curriculum owners regularly review 11 | issue lists and your issue will be prioritized and addressed as appropriate. 12 | 13 | ## Submitting a Pull Request to Suggest an Improvement 14 | 15 | If you see an opportunity for improvement and can make the change yourself go 16 | ahead and use a typical git workflow to make it happen: 17 | 18 | * Fork this curriculum repository 19 | * Make the change on your fork, with descriptive commits in the standard format 20 | * Open a Pull Request against this repo 21 | 22 | A curriculum owner will review your change and approve or comment on it in due 23 | course. 24 | 25 | # Why Contribute? 26 | 27 | Curriculum on Learn is publicly and freely available under Learn's 28 | [Educational Content License](https://learn.co/content-license). By 29 | embracing an open-source contribution model, our goal is for the curriculum 30 | on Learn to become, in time, the best educational content the world has 31 | ever seen. 32 | 33 | We need help from the community of Learners to maintain and improve the 34 | educational content. Everything from fixing typos, to correcting 35 | out-dated information, to improving exposition, to adding better examples, 36 | to fixing tests—all contributions to making the curriculum more effective are 37 | welcome. 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Learn.co Educational Content License 2 | 3 | Copyright (c) 2016 Flatiron School, Inc 4 | 5 | The Flatiron School, Inc. owns this Educational Content. However, the Flatiron School supports the development and availability of educational materials in the public domain. Therefore, the Flatiron School grants Users of the Flatiron Educational Content set forth in this repository certain rights to reuse, build upon and share such Educational Content subject to the terms of the Educational Content License set forth [here](http://learn.co/content-license) (http://learn.co/content-license). You must read carefully the terms and conditions contained in the Educational Content License as such terms govern access to and use of the Educational Content. 6 | 7 | Flatiron School is willing to allow you access to and use of the Educational Content only on the condition that you accept all of the terms and conditions contained in the Educational Content License set forth [here](http://learn.co/content-license) (http://learn.co/content-license). By accessing and/or using the Educational Content, you are agreeing to all of the terms and conditions contained in the Educational Content License. If you do not agree to any or all of the terms of the Educational Content License, you are prohibited from accessing, reviewing or using in any way the Educational Content. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Forms Submit 2 | 3 | ## Learning Goals 4 | 5 | - Handle a form's submit event in React 6 | - Use controlled inputs to validate values 7 | 8 | ## Introduction 9 | 10 | In this lesson, we'll discuss how to handle form submission in React. 11 | 12 | If you want to code along there is starter code in the `src` folder. Make sure 13 | to run `npm install && npm start` to see the code in the browser. 14 | 15 | ## Submitting a Controlled Form 16 | 17 | Now that we've learned how to control a form with state, we want to set up a way 18 | to submit our form. For this, we add the `onSubmit` event listener to our `form` 19 | element: 20 | 21 | ```jsx 22 | // src/components/Form.js 23 | return ( 24 |
25 | 26 | 27 | 28 |
29 | ); 30 | ``` 31 | 32 | Now, whenever the form is submitted (by pressing the Enter or Return key in an 33 | input field, or by clicking a Submit button), the `handleSubmit` callback 34 | function will be called. We don't have the `handleSubmit` function yet, so let's 35 | write it out: 36 | 37 | ```jsx 38 | function handleSubmit(event) { 39 | event.preventDefault(); 40 | const formData = { 41 | firstName: firstName, 42 | lastName: lastName, 43 | }; 44 | props.sendFormDataSomewhere(formData); 45 | setFirstName(""); 46 | setLastName(""); 47 | } 48 | ``` 49 | 50 | Let's look at each line of code in this function: 51 | 52 | - `event.preventDefault()`: The default behavior of a form is to 53 | [try and submit the form data based on a defined action][], which effectively 54 | causes the browser to refresh the page. We didn't (and don't need to) define 55 | an action. The result, however, is that the form makes a new request to the 56 | current page, causing a refresh. By using `event.preventDefault()`, we stop 57 | this behavior from happening. 58 | 59 | [try and submit the form data based on a defined action]: https://www.w3schools.com/html/html_forms.asp 60 | 61 | - `const formData = { firstName: firstName, lastName: lastName }`: Here, we are 62 | putting together the current form data into an object using the values stored 63 | in state. 64 | - `props.sendFormDataSomewhere(formData)`: A form, when submitted, should send 65 | the form data somewhere. As mentioned a moment ago, the traditional HTML way 66 | was to send data to a server or another page using the `action` attribute. In 67 | React, we handle requests with asynchronous JavaScript. We won't go into the 68 | details of how this works just yet, but we can think of 69 | `sendFormDataSomewhere()` as the code that handles sending our data off. This 70 | function might be defined in the same form component, or can be passed down as 71 | a prop. 72 | - `setFirstName("")`: if we want to clear the input fields, all we need to do is 73 | set state! In a traditional JavaScript form, you might do something like 74 | `event.target.reset()` to clear out the form fields. Here, because we are 75 | using controlled inputs, setting state to an empty string clears out the 76 | values from the input fields once the data has been submitted. 77 | 78 | You can contrast this to handling an _uncontrolled_ form being submitted, in 79 | which case you would need to access the input fields from the DOM instead 80 | of accessing the values from state: 81 | 82 | ```jsx 83 | function handleSubmit(event) { 84 | event.preventDefault(); 85 | // in an uncontrolled form, you need to access the input fields from the DOM 86 | const formData = { 87 | firstName: e.target[0].value, 88 | lastName: e.target[1].value, 89 | }; 90 | props.sendFormDataSomewhere(formData); 91 | } 92 | ``` 93 | 94 | Since we don't have a server to send our data to, let's remove our 95 | `sendFormDataSomewhere()` function. Instead, we'll demonstrate submission by 96 | modifying our `Form` component to access submitted values from state and list 97 | them in the DOM: 98 | 99 | ```jsx 100 | import React, { useState } from "react"; 101 | 102 | function Form() { 103 | const [firstName, setFirstName] = useState("Sylvia"); 104 | const [lastName, setLastName] = useState("Woods"); 105 | const [submittedData, setSubmittedData] = useState([]); 106 | 107 | function handleFirstNameChange(event) { 108 | setFirstName(event.target.value); 109 | } 110 | 111 | function handleLastNameChange(event) { 112 | setLastName(event.target.value); 113 | } 114 | 115 | function handleSubmit(event) { 116 | event.preventDefault(); 117 | const formData = { firstName: firstName, lastName: lastName }; 118 | const dataArray = [...submittedData, formData]; 119 | setSubmittedData(dataArray); 120 | setFirstName(""); 121 | setLastName(""); 122 | } 123 | 124 | const listOfSubmissions = submittedData.map((data, index) => { 125 | return ( 126 |
127 | {data.firstName} {data.lastName} 128 |
129 | ); 130 | }); 131 | 132 | return ( 133 |
134 |
135 | 136 | 137 | 138 |
139 |

Submissions

140 | {listOfSubmissions} 141 |
142 | ); 143 | } 144 | 145 | export default Form; 146 | ``` 147 | 148 | The above component will render previous form submissions on the page! We have 149 | a fully functioning controlled form. 150 | 151 | ## Validating Inputs 152 | 153 | One benefit we get from having our form's input values held in state is an easy 154 | way to perform validations when the form is submitted. For example, let's say we 155 | want to require that a user enter some data into our form fields before they 156 | can submit the form successfully. 157 | 158 | In our `handleSubmit` function, we can add some validation logic to check if the 159 | form inputs have the required data, and hold some error messages in state: 160 | 161 | ```jsx 162 | // add state for holding error messages 163 | const [errors, setErrors] = useState([]); 164 | 165 | function handleSubmit(event) { 166 | event.preventDefault(); 167 | // first name is required 168 | if (firstName.length > 0) { 169 | const formData = { firstName: firstName, lastName: lastName }; 170 | const dataArray = [...submittedData, formData]; 171 | setSubmittedData(dataArray); 172 | setFirstName(""); 173 | setLastName(""); 174 | setErrors([]); 175 | } else { 176 | setErrors(["First name is required!"]); 177 | } 178 | } 179 | ``` 180 | 181 | Then, we can display an error message to our user in the JSX: 182 | 183 | ```jsx 184 | return ( 185 |
186 |
187 | 188 | 189 | 190 |
191 | {/* conditionally render error messages */} 192 | {errors.length > 0 193 | ? errors.map((error, index) => ( 194 |

195 | {error} 196 |

197 | )) 198 | : null} 199 |

Submissions

200 | {listOfSubmissions} 201 |
202 | ); 203 | ``` 204 | 205 | ## Conclusion 206 | 207 | By setting up our form components using controlled inputs, we give React state 208 | control over the data being displayed in the DOM. As a benefit of having the 209 | form data in state, we can more easily access it once a form is submitted and 210 | either pass it along to another component or use it to make a fetch request. We 211 | can also more easily perform some validation logic when the form data is 212 | submitted. 213 | 214 | ## Resources 215 | 216 | - [React Forms](https://reactjs.org/docs/forms.html) 217 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@babel/preset-env", 4 | ["@babel/preset-react", { runtime: "automatic" }], 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hooks-forms-submit", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^17.0.1", 7 | "react-dom": "^17.0.1", 8 | "react-scripts": "^5.0.1" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "test": "jest" 13 | }, 14 | "eslintConfig": { 15 | "extends": [ 16 | "react-app", 17 | "react-app/jest" 18 | ] 19 | }, 20 | "browserslist": { 21 | "production": [ 22 | ">0.2%", 23 | "not dead", 24 | "not op_mini all" 25 | ], 26 | "development": [ 27 | "last 1 chrome version", 28 | "last 1 firefox version", 29 | "last 1 safari version" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-co-curriculum/react-hooks-forms-submit/96d1885d2363eeeaa947f95dd768de0024a562d5/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-co-curriculum/react-hooks-forms-submit/96d1885d2363eeeaa947f95dd768de0024a562d5/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learn-co-curriculum/react-hooks-forms-submit/96d1885d2363eeeaa947f95dd768de0024a562d5/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/__tests__/App.test.js: -------------------------------------------------------------------------------- 1 | test("Test passing", () => { 2 | return new Promise((resolve, reject) => { 3 | resolve(true); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /src/components/Form.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | function Form(props) { 4 | const [firstName, setFirstName] = useState("Sylvia"); 5 | const [lastName, setLastName] = useState("Woods"); 6 | 7 | function handleFirstNameChange(event) { 8 | setFirstName(event.target.value); 9 | } 10 | 11 | function handleLastNameChange(event) { 12 | setLastName(event.target.value); 13 | } 14 | 15 | return ( 16 |
17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | 24 | export default Form; 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Form from './components/Form' 4 | 5 | ReactDOM.render( 6 |
, 7 | document.getElementById('root') 8 | ); --------------------------------------------------------------------------------