Author:
Last Updated: 31 December, 2022
📊 Table of Contents
- 📊 Table of Contents
- 🥅 The Goal
- 📦 Setup
- 🔠 Header
- 📝 Form
- 🏞️ Output
- 🖌️ Touch-ups
- 💈Styling
- 🧨 Taking This Further
🥅 The Goal
We will be making a Profile Generator to generate Profile Cards for Oasis members. Below is a screenshot of what we’ll be building. Users enter information in the blue section and then click Generate!
. Upon clicking, the green section appears. This tutorial won’t cover every step, but the sample code is available below too so you can follow along there.
📦 Setup
First, launch VS Code, setup your repo, and run create-react-app
. See
Now that we’re ready to code, let’s start by blocking out our main components. From the example, we see there are three clear sections: the header, input form, and output Let’s reflect this in code:
// App.js
import Header from "./Header"; // import each of your 3 components
import Form from "./Form";
import Output from "./Output";
export default function App() {
return (
<div>
<Header />
<Form />
<Output />
</div>
);
}
// Header.js
export default function Header() { /* ... */ }
// ...
While we’re at it, let’s think about which components need access to which properties. We need four state variables to track values for each input, and one to decide if the output should be shown.
// App.js
import { useState } from "react"; // required to use state
import Header from "./Header"; // import each component
import Form from "./Form";
import Output from "./Output";
export default function App() {
// input field values
const [photo, setPhoto] = useState(null);
const [name, setName] = useState("");
const [title, setTitle] = useState("");
const [fact, setFact] = useState("");
return (
<div>
<Header />
<Form />
<Output />
</div>
);
}
Not every variable needs to be passed to every component. The Form
needs everything, but the Output
only needs photo
, name
, title
, and fact
, and the Header
doesn’t need anything. It is important to only provide each component the data it actually needs.
// App.js
import { useState } from "react"; // required to use state
import Header from "./Header"; // import each component
import Form from "./Form";
import Output from "./Output";
export default function App() {
// input field values
const [photo, setPhoto] = useState(null);
const [name, setName] = useState("");
const [title, setTitle] = useState("");
const [fact, setFact] = useState("");
// whether to show the output
const [showOutput, setShowOutput] = useState(false);
return (
<div>
<Header />
<Form
photo={photo} setPhoto={setPhoto}
name={name} setName={setName}
title={title} setTitle={setTitle}
fact={fact} setFact={setFact}
showOutput={showOutput} setShowOutput={setShowOutput}
/>
<Output photo={photo} name={name} title={title} fact={fact} />
</div>
);
}
🔠 Header
The header is a quick component to set up. It contains only h1
and p
elements:
export default function Header() {
return (
<>
<h1>Profile Generator</h1>
<p>Generate profile cards for Oasis members.</p>
</>
);
}
📝 Form
To create the form, we need to tell React what to do when the form is submitted. This can be accomplished by accessing the onSubmit
event. We also need to tell React what to do when each input is changed so that we can keep track of its value. We do this by accessing the event
property in the onChange
event.
Let’s create a form using HTML and then access these onChange
and onSubmit
events using React:
// in Form.js
// function header:
export default function Form({ /*...*/ name, setName /*...*/ }) {
// in return ...
<form
onSubmit={(e) => { // on submission ...
e.preventDefault(); // stop submit from reloading the page
setShowOutput(true); // display the output,
// using the setShowOutput() state setter passed into this Form component
}}
>
<label htmlFor="member-name">Name: </label> { /* htmlFor connects the label the appropriate input field */ }
<input
id="member-name" // match id to label's htmlFor
type="text" // type of HTML input (text, email, phone, etc.) - see https://www.w3schools.com/html/html_form_input_types.asp
value={name} // display value of the provided `name` state prop
onChange={(e) => { // access the onChange event handler; `e` is short for event but can be named anything */
setName(e.target.value); // get the value of the event (text in the input field)
}}
required // mark this HTML field as required
/>
{/* continue for rest of inputs */}
</form>
Continue building this out for the rest of the input fields you need. One input which will prove challenging is the photo because we need to turn the URL into a photo we can display. We do this by accessing the file’s path from the event, creating a URL from it, and then by getting an object at that URL inside of your onChange
:
<input
id="member-photo"
type="file"
// no `value` because value is a path, but we want to store a photo
onChange={(event) => {
setPhoto(URL.createObjectURL(event.target.files[0]));
}}
required
/>
Once this is all complete, it’s time to move on to the output section.
🏞️ Output
For the output, we need to display an image and text using the passed in properties:
export default function Output({ photo, name, title, fact }) {
return (
<div>
<img src={photo ?? ""} alt="profile photo" />
<h1>{name}</h1>
<p>{title}</p>
<p>Fun Fact:{fact}</p>
</div>
);
}
photo ?? ""
evaluates to ""
if photo
is a nullish value (null
or undefined
). This is called the Nullish Coalescing operator.Now we’ve got all the pieces in place!
🖌️ Touch-ups
It’s time to add some functionality details before we overhaul styling. First, we want to only display the output when showOutput
is true. Add conditionality to the display of Output
using a ternary operator:
{showOutput ? (<Output />) : (<></>)}
&&
):{showOutput && (<Output />)}
Next, you’ll notice that once we set showOutput
to true, any changes we make are live! Let’s update the onChange methods to hide output. (This is a challenge to you. The solution is in the sample code.)
💈Styling
The final version of this project makes use of some styling. The best way to learn this is by reviewing the sample code on your own after reviewing concepts covered in
To create a column layout, Flexbox was used. You can learn more about that here:
Another common (and relatively new) CSS layout method is Grid. A similar guide can be found here:
Both Flexbox and Grid have their own pros and cons. As you build increasingly complex layouts, you may wish (or even need) to combine both methods to achieve your design goals.
🧨 Taking This Further
In addition to styling your web app, here are some other ideas of how you could extend this based on what you’ve learned so far:
- let people customize their card’s background color
- support resetting the form (clearing all data)
- add a field to differentiate mentors from general members. This could add a badge/label, change the color, or even hide the title field for non-mentors!
- store and show a list of all profiles the user has generated
- allow members to download an image of their profile card
- anything else!