- Lab
- Core Tech

Guided: React Foundations - Props and State
In this guided lab, you will enhance a help desk ticketing application to capture user input, handle events, and pass data between components using basic React props and state patterns.

Path Info
Table of Contents
-
Challenge
Overview
Welcome!
In this guided lab, you will enhance a simple helpdesk application to respond to user input and display tickets. As you perform these tasks, you'll learn how components pass data between each other using props and can respond to user input using state.
What to Expect
You should be familiar with how to render a React application using JSX and simple components. This was covered in the first lab, React Fundamentals: JSX and Components.
The JavaScript code in the lab is written in idiomatic React, which is typically ES2015+ syntax. You should be able to work with arrow functions, ES modules, and
const
.info> Stuck on a task? Check out the
solution/
folder in the project directory for full solution files and reference code for each step. Files with numbers (likeindex.2.jsx
) correspond to the task order in the step.Getting Started
To make sure the lab environment is set up and working, you'll start by running the Globomantics Helpdesk demo application. Once set up, you can visit {{localhost:5173}} to view the app in your browser, or in the Web Browser tab in the editor to the right.
What you will see is a plain HTML and CSS webpage from the
index.html
file.info> Web browser not working? Sometimes while reading through the lab, the dev server will shut down and you'll have to reconnect to the Terminal. Just run
npm run dev
again to start the server back up. Other times, the web browser may not always reflect the latest code due to the auto-save feature of the lab. Just click the refresh button next to the address bar in the tab to force-refresh the page.How is the app hosted?
You may wonder what "VITE" means and how the app is being hosted on the above URL. This comes from the JavaScript development tool Vite.js.
Vite is a frontend tool that bundles and builds JavaScript applications, including React. There are other options available depending to build and host React applications depending on what you need, such as Next.js.
Vite is versatile and supports many other frameworks and libraries, including plain HTML and CSS with very little configuration.
A Simple Helpdesk App
The application is a simple helpdesk where you can enter ticket information related to a Globomantics product.
There are several React components you created previously, including:
FormGroup
to wrap form fields with a special<div>
ProductField
for creating a dynamic product drop-downHeader
for encapsulating the header of the pageApp
for hosting our app
However, there are some glaring issues you have to address!
Namely: the support team can't create a new ticket!
Your boss's Slack DM this morning said it all:
Boss: If you could make sure the team can submit a ticket and then view the details afterwards, that would be swell. Thanks!
-
Challenge
Introducing Props
Introducing Props
Surprise! You've been using props throughout previous labs, like
key
,className
,name
,id
, andchildren
... but how do they work exactly?When you pass props to a JSX element or React component, you are really creating a JavaScript object behind the scenes.
Remember how you manually created elements in React in a previous lab? You used the
createElement
API, which the JSX compiles to, like this:React.createElement("form", null, null);
You already learned the third argument is
children
. The second argument to the function isprops
and it accepts a JS object.So for example, the JSX in the
Header
component:<a href="/">Globomantics Helpdesk</a>
Uses the
href
prop, and gets compiled to:React.createElement("a", { href: "/" }, "Globomantics Helpdesk");
What props can you pass?
Technically, you can pass any prop to a component or JSX element. The catch is that the element needs to do something with the prop. Again, because it's a JS object behind-the-scenes, an object can contain any amount of properties but if they aren't consumed, they won't do anything.
JSX HTML elements like
main
,div
, anda
all accept standard JavaScript DOM attributes as props. You've used some already:- className
- href
- name
- htmlFor
React handles these on your behalf, creating the underlying DOM elements during rendering and passing these props, like this:
const anchorEl = document.createElement("a"); anchorEl.href = "/"; anchorEl.className = "header-link";
Since props are translated to JavaScript object properties, they can be any valid JavaScript type:
- Strings
- Numbers
- Booleans
- Arrays
- Objects
- Functions
String props can be passed using quotes, like in the
Header
component:<a href="/">Globomantics Helpdesk</a>
But other non-string types should be passed using JSX expression syntax, like you did in
ProductField
:<option key={name}>{name}</option>
name
is a string but it's a variable, so you need to substitute its value using the JSX expression syntax.What about the `key` prop?
The
key
prop is not a built-in JavaScript DOM property, so how does that work? React layers in some special props on top of JSX likekey
andref
that it uses internally.As you work through this lab, you'll get more familiar with passing props around.
Submitting the form
In a traditional server-side application, the ticket
<form>
would POST to the server at the URL specified in itsaction
attribute. The server would then take the ticket data and render a new version of the page (or redirect to a new page) to view the details.In a client-side React application, you need to intercept this form submission and handle it in the browser. You can do that using the
onSubmit
prop for the<form>
JSX element.Props as event handlers
Props that accept functions are usually called "event handlers" (or "handlers" for short). The
onSubmit
prop is a handler and accepts a JavaScript function callback that is passed a JavaScript DOM SubmitEvent object.Handling form submission
The first step to creating a ticket is to implement a simple JavaScript form submission handler so you can access the ticket data. The
<form>
JSX element you just wrote compiles to:React.createElement("form", { onSubmit: handleFormSubmit action: "/", method: "post" }, /* ...children */);
This should make it a little clearer why JSX accepts JavaScript expressions in its syntax -- it all gets compiled to JS function calls!
How does `onSubmit` get handled?
You may notice that there is no
onSubmit
property on theHTMLFormElement
object in native JavaScript, it's actually onsubmit.React wraps these native DOM events with event handler props prefixed with
on
and then the event name, like:- onChange
- onSubmit
- onClick
React does this for standardization and for internally taking care of any browser quirks so you don't have to.
In the console logs in the Web Browser, when you submit the form, you can see the event logged to the console is the type
BaseSyntheticEvent
. They are called "synthetic events" because they are events React emits that wrap the native browser events.Go ahead and submit a ticket in the Web Browser tab. Notice that it still results in a blank page.
Just because you added a JavaScript event handler doesn't mean you prevent the event from bubbling up to the browser.
To fix this, you can call the Event.preventDefault() API to prevent the event from bubbling up.
In this case, you want to prevent the form from issuing a POST request which doesn't work in this app since you don't have a backend server. OK, you've successfully prevented the form from submitting to the server. Is there a way to see what values were submitted?
Extracting form values
Within the body of the handler, you can extract form values using the FormData web API and passing in the form's DOM element reference to the constructor, like this:
const form = e.target; const formData = new FormData(form);
You can then use the
FormData.get(name)
method to retrieve the form values by input name.To be clear, this is not a React API you need to import -- this is a native browser web API. Within the event handler, you are not within the scope of React anymore. React concerns itself mainly with rendering and handling events, but after that, you are writing plain old JavaScript. Now after you fill out the ticket form, and click the Create ticket button, in the developer console you'll see something like this printed out:
{ summary: "...", body: "...", product: "..." }
The form submission event is being handled and you assembled a
ticketData
object containing the form values.Now, what can you guess what you'll do next with the ticket data? Display it!
-
Challenge
Passing Data Between Components
Passing Data Between Components
Now that you've captured ticket data into an object, you can display the submitted ticket information in the UI.
To do that, you need to somehow pass the
ticketData
object to other components in your app so you can render the values. How can you do that from your form event handler?The short answer is: you can't.
The function you declared is outside the scope of the
<App>
component, so there are no React APIs you can utilize to help you store the data and pass it around.info> Remember: React components encapsulate behavior and data. By declaring the handler outside a component, you are not really following the principles of React. If you moved the
App
component to another file, the handler would not come with it. It isn't "co-located."The good news is that with a minor refactoring, you can take advantage of React props to pass data around the component tree.
Creating a
TicketForm
componentIn order to better co-locate the form submission handler with the form rendering, you should extract a
TicketForm
component from theApp
so you can move thehandleFormSubmit
callback from a module scope into a component scope.Why not move the function to `<App>`?
Technically, you could. However, it is usually better to keep behavior and logic related to a subset of the application together. By extracting a `TicketForm` component, it's easier to understand that the form submission handling only relates to the `<form>` and not the entire application. You are effectively "hiding" the form submission details from the rest of the app -- that is the essence of encapsulation.
To keep things simple, you'll display the new ticket information alongside the submission form.
Creating a
TicketDisplay
componentTo begin with, create a new
TicketDisplay
component that will display ticket data that is passed through props. This will be rendered within the<App>
component. There is now a section of the page that should display submitted ticket data.However, if you try submitting ticket data again in the browser... nothing happens. This makes sense, as you haven't yet done anything with the
ticketData
object in the form submission handler.The problem is: how can you pass data up to the
<App>
component so that you can then pass it back down to the<TicketDisplay>
component?Passing data up through prop callbacks
Prop callbacks to the rescue! Just as you handled the form submission event, you can declare your own handler prop in the
TicketForm
component. This way, the<App>
component can pass a callback that will receive theticketData
object.In React, it's a convention to prefix handler props with
on
to denote they are callbacks for event handling. You can declare a component that accepts a callback prop simply through destructuring it or expecting it to be passed:const HelloWorld = ({ onGreeting }) => { return ( <button onClick={() => onGreeting('Hello World!')}> Say Hello </button> ) };
In the example above, notice how you can call the prop callback and pass it data from within the scope of the component. This is how you can pass data back up the component tree.
What's a callback, again?
A callback is a function that is passed around as a value so that _another_ piece of code can call it and pass some data to it. In JavaScript, functions are first-class citizens, they are like tiny packages you can ship around to different parts of your codebase just by passing them by reference.
For example, the typical event handler is a callback that accepts an event object:
const handleFormSubmit = (event) => { // do something with `event` };
It can be passed around by reference to React components via props or even to other functions.
This successfully passes the data up the tree to
App
.The next question is: how can you pass it back down to the
TicketDisplay
component through its props? It is currently "stuck" inside the callback handler and can't get out! -
Challenge
Introducing Component State
Introducing Component State
In order to "capture" the
ticketData
object to pass in the JSX, you need some way of introducing local state to the component.Normally in JavaScript applications, that's what variables are used for.
Storing local component state
Why don't you try introducing a variable in the component, and assigning it a value when the callback is executed. Will that work? Now if you run the app in the browser, you'll see a blank page! If you check the console, you would see an error:
Cannot read properties of undefined (reading 'summary')
Oops! This is because the ticket variable is not initialized with a value so by default it is
undefined
.In React, one common pattern you'll use is to conditionally render a part of the component tree if an expression is falsy.
Conditional rendering
In this case, you want to avoid rendering the
TicketDisplay
component if the ticket data isn't populated. This is called "conditional rendering."Within the JSX, you can use a ternary conditional expression like this:
<div> {myVariable ? <MyComponent name={myVariable.name} /> : null} </div>
Notice how the condition will return
null
ifmyVariable
is falsy, otherwise it renders the component. Since this is "just" JavaScript,myVariable.name
will not be evaluated unlessmyVariable
is truthy, avoiding the error.What about the `&&` conditional?
One approach to conditional rendering you may see in the wild is like this:
<div>{myVariable && <MyComponent name={myVariable.name} />}</div>
This avoids some mental gymnastics of ternary expressions but has a subtle bug. If
myVariable
isfalse
or0
sometimes it can be treated as a string and rendered into the page!A ternary expression makes sure that never happens by forcing a
null
(or empty) render if the expression returns false.Did the ticket data get displayed? No? Don't worry, it's not your fault. This is where plain JavaScript doesn't quite work the way you initially expect with React components.
Hooking into React
Using local variables like this is not recommended for React apps because React has no idea your local variables exist. In other words, React will never know when the value of your variable is updated so it can't re-render the changes.
Instead, you need a way to declare local variables that are "hooked" into React's engine and notify it of changes.
Storing component state
React offers the
useState
API to do exactly that. It allows you to store a value and when it gets updated, internally it "notifies" React and then React re-renders the component with the updated value. This is called "component state" and its really what makes React, well, reactive.The
useState
function accepts a default value (if any) and returns an array with two objects, the current value and a callback function (called the "set-state action").The idiomatic way to write a
useState
statement is using array destructuring syntax:const [currentValue, setValue] = useState(false);
Syntax: What is array destructuring?
Array destructuring syntax was introduced in ES2015 to make it simpler to extract items from an array and assign them to variables.
For example, the code above is equivalent to this:
const myState = useState(false); const currentValue = myState[0]; const setValue = myState[1];
Array destructuring makes this simpler and combines all these statements into a single statement.
Updating component state
When you want to update the current value of a state variable, you call the set-state action (or callback) with the new value.
The set-state action accepts a single argument for the value to set, like this:
setValue(true);
Passing a set-state callback
You can optionally pass a callback that gets passed the previous state values, for example to toggle the value:setValue(prevState => !prevState);
This can be useful in situations where you need to reference previous state because set-state actions are executed asynchronously, and there's no guarantee the value will be correct if you reference the state variable directly due to JavaScript closures.
To tell React to re-render with the new ticket data, you'll need to refactor the the way you set the ticket data variable to use the
useState
function instead. Now in the Web Browser if you try to fill out the form and submit a ticket, theTicketDisplay
component renders with the submitted data!Go ahead and modify the ticket data and re-submit the form. Each time, the displayed data is updated.
Congratulations, you satisfied your boss. Right?
-
Challenge
Passing External State
Passing External State
As you show the new Helpdesk application to your boss, they tell you they're happy with what you've built but it would be great if they could create links to pass the product name in the URL to pre-populate the form.
For example, like so:
{{localhost:5173}}/?product=GloboCMS
Boss: This would make it easier when handling support tickets for customers, so that the team doesn't need to manually fill in the product field every time. Please tell me you can do that, right?
Indeed you can! You can pass data from outside React into your components since they are all "just" JavaScript functions and you can execute any logic you normally would on a web page.
URLs are stateful
The URL is an example of external state -- state that is stored outside of React. A URL can contain a query string (
?key=value
), route information (/products/1234
), or even a hash value (#reviews
).In order to set the default product in the drop-down, you'll need a way to capture a product name from the query string and pass it to the
<ProductField>
component.The browser stores the current URL details in the
window.location
object. The query string is specifically stored inwindow.location.search
, and will contain the full query string value ("?key=value"
).To parse the query string, you don't need to write any custom code! You can use the native URLSearchParams class and the
get(key)
method, like this:const searchParams = new URLSearchParams( window.location.search ); // Get a querystring value by key const myValue = searchParams.get("myKey");
Now you can start by reading the query string from the URL in the
App
component so that its loaded when the application renders initially. Now that you are reading the value ofproduct
from the URL query string, you need to pass it all the way down to the product drop-down, which is within theProductField
component.You'll need to go down each layer of the component tree and pass the value using props.
Drilling props
To pass data down from a top-level component down to a lower-level component, you need to use a technique called "prop drilling."
Prop drilling is an easy way to pass data from a top-level component down deeper into the component tree. Now that the
<select>
drop-down supports a default value, click the link below.{{localhost:5173}}/?product=GloboHR
The form will now render with the product field set to "GloboHR" from the URL. If you try changing the product, then submit the form, it even resets to the default value again.
Why doesn't GloboFinance+ work?
The
+
character is a special character in a URL, so you need to encode it as%2B
. This is called percent-encoding.Your boss is ecstatic now -- thank you for adding that feature! You know what would be even better though? If, after submission, the form remembered the last product you chose instead of resetting it to the default value.
Boss: Our customer support folks will be entering a lot of tickets one after the other, so having to change the product after submission will get a bit tedious.
Alright, how can you do this? The data in the form is reset on submission using the native web API, HTMLFormElement.reset.
Surely you can't control or override that... can you?
-
Challenge
Controlling Form Inputs
Controlling Form Inputs
Surprise (again)! In this application, you have been using a pattern in React called "uncontrolled inputs." This means, you rely on the browser to manage the state of the form field values. As the user types, the browser is tracking the value, and then you can get the final form values on submission in the
handleFormSubmitted
event handler usingFormData
.The problem as you've seen is that when
form.reset()
is called, which is a native browser function, that the value of theproduct
input is reset to its default selection (GloboCMS
).This approach can work well for simple forms, however your new requirement is to "remember" the last value after submission.
This is called creating a "controlled input" and it's when you tell React to take over and opt-out of browser handling.
Controlling the product value
To create a controlled input, you have to set its
value
prop. In order to do that, you'll start by introducing local component state to the<ProductField>
component to hold the value of the selected product. Fantastic, now try changing the product drop-down after the page loads.Is it working? No? What's going on? It seems "stuck" and won't change the value!
Handling input changes
As you've taken over control from the browser, you are passing the form's value using local state. However, you aren't yet updating the state using the set-state action, are you?
This is working "as designed." You now have full control over the value of the input. This is why sometimes creating controlled inputs adds more overhead, so it's always a good question to ask: do you really need to use controlled inputs for all form fields?
In order to update the selected product, you need to pass a callback to the
onChange
handler prop for the<select>
element, then call the set-state action. OK, now try changing the product in the form. It's working! What's more, when the form is submitted, the state value is retained and the product field doesn't change to thedefaultValue
anymore.Your boss should be pretty happy, but there's something you as the developer need to be aware of.
Avoiding malicious input
Try clicking this link to set a product that doesn't exist:
{{localhost:5173}}/?product=NotARealProduct
The product drop-down doesn't show this fake product, but let's try logging the state value in the
ProductField
component. When you click the malicious link, In the browser console you should see:"NotARealProduct"
Uh-oh! As a malicious user, you've injected your own fake product into the local component state.
Default state values
When using
defaultValue
and an uncontrolled input originally, the browser ensures the value of the<select>
matches one of the<option>
values. However, now that you are storing your own local state, it's up to you to validate the selected option!info> A best practice is to never trust user input and always sanitize or validate it on the client-side even if your server or backend has validation.
Before you send this to your boss, you should take a security-first approach and validate the default product against your
products
array (the "source of truth"). That way, no one can enter a value that doesn't exist in the list. Now try clicking the malicious link again:{{localhost:5173}}/?product=NotARealProduct
If you log the value of the state variable to the console, you would see it is now
undefined
instead of the fake product.info> Note: Instead of
undefined
you could choose a different product to be the default value. The browser will handle anundefined
value natively, using the first option in the list as the default value.A security-first mindset should be taken across all aspects of the application, from the database to the client-side. This is commonly referred to as "defense-in-depth." Remember, security is only as strong as the weakest link!
When you use controlled inputs in React, you need to be thinking about how to handle bad or malicious data from users.
-
Challenge
Recap and What's Next
Recap
Congratulations, you now have an interactive Helpdesk application that displays newly created tickets for the support team!
Here's what you just learned:
- You handled events using callback functions passed as props
- You passed data up through the component tree using props
- You passed data down through the component tree using "prop drilling"
- You used the
useState
API to introduce local component state to notify React to re-render the application when new ticket data is submitted - You captured external state from the URL to use within your app
- You took a security-first approach by validating the product passed from the URL as user input
- You learned how to perform conditional rendering
- You used "uncontrolled" form inputs to let the browser handle and set input values
- You added a "controlled" form input for the product field to opt-in to using React component state for the input value
That's a lot. You deserve a pat on the back!
What's Next
While displaying a single submitted ticket is somewhat helpful, there are a few things that would be ideal in an app like this:
- The support team needs to manage tickets after creation. Can you display a list of submitted tickets?
- The user provided ticket data, but the support team needs additional data like "time created" or even a ticket identifier.
- The data disappears when reloading the page. That won't be feasible for a production deployment!
To handle these cases, you'll have to leverage more APIs like
useState
, such asuseCallback
,useContext
, anduseEffect
-- these are called hooks and they are fundamental to building React apps. In addition, you'll need to work with asynchronous code to fetch data from a database or API so you can update application state.In the next lab, React Fundamentals: Hooks and Context, you'll explore even more APIs by enhancing the Helpdesk app to store data in a database.
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.