If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeHey! Our repLogs
live in state! And so, I think it's finally time to add some magic to our form and get it functional. Here's our next goal: when the user submits this form, we want to take its data and update the repLogs
state so that a new row is rendered in the table.
The form itself lives in RepLogs
, near the bottom. But, the state we need to modify lives in our parent: RepLogApp
. To communicate back up the tree, we'll follow a familiar pattern: pass a callback from parent to child, just like we did with onRowClick
.
Start in RepLogApp
: add the handler function: handleNewItemSubmit()
with an event object. To prevent the form from actually trying to submit, use event.preventDefault()
, just like normal JavaScript.
... lines 1 - 4 | |
export default class RepLogApp extends Component { | |
... lines 6 - 24 | |
handleNewItemSubmit(event) { | |
event.preventDefault(); | |
... lines 27 - 29 | |
} | |
... lines 31 - 41 | |
} | |
... lines 43 - 46 |
For now, log some stuff! I love when a good form submits! Oh, and also log event.target
. Because this function will handle the form
element's submit, event.target
will be the form itself. We're going to need that so we can read the values from its fields.
... lines 1 - 24 | |
handleNewItemSubmit(event) { | |
... lines 26 - 27 | |
console.log('I love when a good form submits!'); | |
console.log(event.target); | |
} | |
... lines 31 - 46 |
Pass this callback as a new prop: onNewItemSubmit = {this.handleNewItemSubmit}
. And, hey! We're starting to see a naming convention. This isn't anything official, but I like to name my methods "handleSomeEvent" and my props "onSomeEvent".
... lines 1 - 31 | |
render() { | |
return ( | |
<RepLogs | |
... lines 35 - 37 | |
onNewItemSubmit={this.handleNewItemSubmit} | |
/> | |
) | |
} | |
... lines 42 - 46 |
In RepLogs
, head straight down to propTypes
to describe the prop: onNewItemSubmit
is a required function.
... lines 1 - 90 | |
RepLogs.propTypes = { | |
... lines 92 - 94 | |
onNewItemSubmit: PropTypes.func.isRequired, | |
... line 96 | |
}; |
Love it! Back in render, destructure this into a variable. So: how can we attach a "submit" listener to the form? Ah... it's just onSubmit={onNewItemSubmit}
.
... lines 1 - 15 | |
export default function RepLogs(props) { | |
const { withHeart, highlightedRowId, onRowClick, repLogs, onNewItemSubmit } = props; | |
... lines 18 - 23 | |
return ( | |
... lines 25 - 51 | |
<form className="form-inline" onSubmit={onNewItemSubmit}> | |
... lines 53 - 87 | |
); | |
} | |
... lines 90 - 98 |
So simple! Go over to the browser and give it a nice refresh! Select an item... fill in a number and... we got it! Every time we submit by pressing enter or clicking the button, we see our insightful message. And as promised, the event.target
that we're logging is literally the raw, form DOM element.
This is actually really nice. React always guarantees that event.target
will be the element that you attached the listener to.
Next question! How can we read the values from our fields? Look at the form in RepLogs
: there's the select element and... the text area. Check it out: it has a name attribute: reps
. We can use that and normal, boring JavaScript to find that field and get its value.
By the way... if you've read a little bit about forms and React, this might not be what you were expecting. Don't worry. I'm going to show you a few different ways to get the values from form fields, including the pros and cons of each, and which method I recommend and when.
But right now, forget about React, and remember that, under the hood, there is a boring HTML form sitting on the page that we can interact with.
In RepLogApp
, it's time to flex our native JavaScript muscles! To read the reps
textarea, use event.target
- that's the form - .elements.namedItem('reps')
. This will give us the text
element. Reads its value with .value
.
... lines 1 - 24 | |
handleNewItemSubmit(event) { | |
... lines 26 - 28 | |
console.log(event.target.elements.namedItem('reps').value); | |
} | |
... lines 31 - 46 |
Let's go try it! Move over, refresh... select "My Laptop" and lift it 50 times. Yes! There's the 50! Victory!
But, before we go further, I need to ask an important philosophical question:
If your shirt isn't tucked into your pants, are your pants tucked into your shirt?
Hmm. Thought provoking. And also: if our smart component - RepLogApp
- should not be responsible for rendering any HTML, should its handleNewItemSubmit()
method be aware that there is an HTML form and a field with a name="reps"
attribute inside?
Actually... no! It makes no sense for handleNewItemSubmit()
to suddenly be aware of a specific HTML structure that's rendered by its child. In fact, all RepLogApp
should care about is that, when - somehow - a new rep log is created in the app, its handleNewItemSubmit()
function is called so that it can update the repLogs
state. If it's created with a form, or with some random fields during a 10-step process or just with black magic... RepLogApp
should not care!
So, check this out: copy the inside of the function: I'm going to move most of this callback into RepLogs
as a new handler function. Inside render()
, add a new function: handleFormSubmit()
with our normal event argument. Then, paste the logic.
... lines 1 - 23 | |
function handleFormSubmit(event) { | |
event.preventDefault(); | |
console.log('I love when a good form submits!'); | |
console.log(event.target.elements.namedItem('reps').value); | |
} | |
... lines 30 - 105 |
Down in onSubmit
, instead of calling the parent handler, call the new function: handleFormSubmit
.
... lines 1 - 30 | |
return ( | |
... lines 32 - 58 | |
<form className="form-inline" onSubmit={handleFormSubmit}> | |
... lines 60 - 94 | |
); | |
... lines 96 - 105 |
Yep, this feels much better. handleFormSubmit()
is responsible for calling event.preventDefault()
and uses the form structure - which is created right inside this component - to read the names of the fields. Finally, at the bottom, call the parent handler: onNewItemSubmit()
.
... lines 1 - 23 | |
function handleFormSubmit(event) { | |
... lines 25 - 29 | |
onNewItemSubmit('Big Fat Cat', event.target.elements.namedItem('reps').value); | |
} | |
... lines 32 - 107 |
Actually, this is the reason why I put the new function inside of render()
instead of above the function like I did with calculateTotalWeightFancier()
: our callback needs access to the props.
Here's the last important part: instead of passing the event object or the form element to the parent onNewItemSubmit()
callback, only pass it what it needs: the new rep log's raw data. For now, hardcode an item name - "Big fat cat" - but copy the number of true rep logs and paste.
... lines 1 - 24 | |
function handleFormSubmit(event) { | |
... lines 26 - 30 | |
onNewItemSubmit('Big Fat Cat', event.target.elements.namedItem('reps').value); | |
} | |
... lines 33 - 110 |
Back in RepLogApp
, clear out handleNewItemSubmit
and give it two fresh args: itemName
and reps
. Log a todo below: we will eventually use this to update the state. And log those values so we can check things!
... lines 1 - 24 | |
handleNewItemSubmit(itemName, reps) { | |
console.log('TODO - handle this new data'); | |
console.log(itemName, reps); | |
} | |
... lines 29 - 44 |
I love it! RepLogApp
still has a callback, but it's now unaware of the form. It doesn't care how rep logs are created, it only cares that its callback is executed when that happens. All the form logic is right where it should be.
Try it out! Refresh the page, select an item, enter 45 and... submit! The Big fat cat is hardcoded, but the 45 is our real data.
As simple as it is to read the values of the fields by using the name
attribute, you probably won't do this in practice. Instead, we'll learn two other ways: refs & state. We'll jump into refs
next.
Hey CloudCreators!
Sorry for the VERY slow reply - you sent this *just* as I was leaving for a holiday, and nobody else on the team was sure of a good answer for you :).
There is one thing you wrote that I think is really interesting - and could be a clue:
> The console.log is showing the reps but just for like a second and it goes away
Under normal circumstances, I can't think of any reason that a message would disappear from the console log... *except* if the page were performing a full page refresh/navigation. Is it possible that something is navigating the page? So, you see the console.log() for 1 moment, but that click also triggers a navigation. And so, about .5 seconds later, the page navigates away (or, more likely, it reloads) and the console clears. One way to test this is to click the "gear" icon on the upper right of the console area, and check "preserve log". Then, the log will stay even after a refresh. If that makes the log NOT disappear, then it confirms that something is navigating the whole page.
Let me know if that helps out :).
Cheers!
Yes I clicked the gear and preserved log and it works but idk why it was reloading again and again.
Hey CloudCreators!
Sorry for the slow reply! If it's working now and you can't repeat the problem, I wouldn't worry about it. Perhaps something was going weird with the Webpack build process :).
Cheers!
why you didn't add handleNewItemSubmit to the constructor and bind it like
this.handleRowClick = this.handleRowClick.bind(this);
Hey Mouerr,
Good question! But the answer is simple: we just don't need it for now. You need to understand why we call that .bind(this). Actually, we'll do it later in https://symfonycasts.com/sc... , but for now that binding context is not important because we just call console.log() inside, i.e. don't use "this" at all.
Cheers!
// composer.json
{
"require": {
"php": "^7.2.0",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/doctrine-bundle": "^1.6", // 1.9.1
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.3
"doctrine/doctrine-fixtures-bundle": "~3.0", // 3.0.2
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.1
"doctrine/orm": "^2.5", // v2.7.2
"friendsofsymfony/jsrouting-bundle": "^2.2", // 2.2.0
"friendsofsymfony/user-bundle": "dev-master#4125505ba6eba82ddf944378a3d636081c06da0c", // dev-master
"sensio/framework-extra-bundle": "^5.1", // v5.2.0
"symfony/asset": "^4.0", // v4.1.4
"symfony/console": "^4.0", // v4.1.4
"symfony/flex": "^1.0", // v1.17.6
"symfony/form": "^4.0", // v4.1.4
"symfony/framework-bundle": "^4.0", // v4.1.4
"symfony/lts": "^4@dev", // dev-master
"symfony/monolog-bundle": "^3.1", // v3.3.0
"symfony/polyfill-apcu": "^1.0", // v1.9.0
"symfony/serializer-pack": "^1.0", // v1.0.1
"symfony/swiftmailer-bundle": "^3.1", // v3.2.3
"symfony/twig-bundle": "^4.0", // v4.1.4
"symfony/validator": "^4.0", // v4.1.4
"symfony/yaml": "^4.0", // v4.1.4
"twig/twig": "2.10.*" // v2.10.0
},
"require-dev": {
"symfony/debug-pack": "^1.0", // v1.0.6
"symfony/dotenv": "^4.0", // v4.1.4
"symfony/maker-bundle": "^1.5", // v1.5.0
"symfony/phpunit-bridge": "^4.0", // v4.1.4
"symfony/web-server-bundle": "^4.0" // v4.1.4
}
}
// package.json
{
"dependencies": {
"@babel/plugin-proposal-object-rest-spread": "^7.12.1" // 7.12.1
},
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.12.5
"@symfony/webpack-encore": "^0.26.0", // 0.26.0
"babel-plugin-transform-object-rest-spread": "^6.26.0", // 6.26.0
"babel-plugin-transform-react-remove-prop-types": "^0.4.13", // 0.4.13
"bootstrap": "3", // 3.3.7
"copy-webpack-plugin": "^4.4.1", // 4.5.1
"core-js": "2", // 1.2.7
"eslint": "^4.19.1", // 4.19.1
"eslint-plugin-react": "^7.8.2", // 7.8.2
"font-awesome": "4", // 4.7.0
"jquery": "^3.3.1", // 3.3.1
"promise-polyfill": "^8.0.0", // 8.0.0
"prop-types": "^15.6.1", // 15.6.1
"react": "^16.3.2", // 16.4.0
"react-dom": "^16.3.2", // 16.4.0
"sass": "^1.29.0", // 1.29.0
"sass-loader": "^7.0.0", // 7.3.1
"sweetalert2": "^7.11.0", // 7.22.0
"uuid": "^3.2.1", // 3.4.0
"webpack-notifier": "^1.5.1", // 1.6.0
"whatwg-fetch": "^2.0.4" // 2.0.4
}
}
The console.log is showing the reps but just for like a second and it goes away. the Highlighed row id is changing when I click on a row but the color doesnt change it was happening in the start but now it doesnt work.
//RepLogApp.js
import React,{Component} from 'react';
import RepLogs from './RepLogs';
import PropTypes from 'prop-types';
export default class RepLogApp extends Component{
}
RepLogApp.propTypes = {
};