Have you went through the official React's Tic Tac Toe tutorial? If you have, you might notice this bolded text in the introduction
You might be tempted to skip it because you’re not building games — but give it a chance.
I'm guessing even React team knows that it's not that interesting to build a Tic Tac Toe game, since they put that line in. While the project indeed gives you an understanding of React, you'll need a strong will to process the tutorial.
Now don't get me wrong, I'm grateful for this introduction project, but I just don't like it. If you're thinking the same thing, I'm going to write about alternative projects that you can build to learn React in this tutorial.
So what front-end projects you can pick up to learn about React? As I went through tutorials and blog posts, I noticed a good introduction tutorial for React must do these things:
- Teach the fundamentals of React like components, state and props
- Handling dynamic data, changes in state and props
- Shows the use of lifecycle method
Well, almost everything from the main concepts tab, really.
By the end of this tutorial, you will have a better understanding of the uses of React concepts like components, state and lifecycle methods and how they are used in common web application UI.
Note: For the CSS part of this tutorial, we'll use Bootstrap to make it look nicer without writing our own CSS. You can safely ignore the className
parts of the sample code, since they are from Bootstrap
Front-end Fun With List of Cards
Let's start with utilizing JSX, components and props — the basic ingredients of a React UI. Here is our final product:
https://codepen.io/nathansebhastian/pen/qgOJKe
So let's build it. All we have to do is to create the <Card/>
Component which returns JSX elements:
function Card(props) {
return (
<div className="card">
<img className="card-img-top"
src="via.placeholder.com/600x250.png"
alt="cap image" />
<div className="card-body">
<h5 className="card-title">Title Placeholder</h5>
<p className="card-text">Description Placeholder</p>
<a href="#" className="btn btn-primary">Learn more</a>
</div>
</div>
);
}
And then create a parent component that renders <Card/>
three times. We can name it <CardList/>
function CardList() {
return (
<div className="row">
<div className="col-sm-4">
<Card />
</div>
<div className="col-sm-4">
<Card />
</div>
<div className="col-sm-4">
<Card />
</div>
</div>
);
}
Don't forget to add the ReactDOM.render
call at the bottom of the code. This is the code responsible for bringing our React application into our HTML element.
ReactDOM.render(<CardList />, document.getElementById('root'));
Now we need to include our own data into these cards, so pass some props
into it
function CardList() {
return (
<div className="row">
<div className="col-sm-4">
<Card featureImage="sebhastian.com/static/eb0e936c0ef42ded5c6b…"
title="How To Make Interactive ReactJS Form"
description="Let's write some interactive form with React"
link="sebhastian.com/interactive-react-form"
/>
</div>
<div className="col-sm-4">
<Card
// your data
/>
</div>
<div className="col-sm-4">
<Card
// your data
/>
</div>
</div>
);
}
And then use these props in our <Card/>
component:
function Card(props) {
return (
<div className="card">
<img className="card-img-top" src={props.featureImage} alt="cap image" />
<div className="card-body">
<h5 className="card-title">{props.title}</h5>
<p className="card-text">{props.description}</p>
<a href={props.link} className="btn btn-primary">Learn more</a>
</div>
</div>
);
}
Now this <Card/>
component used JavaScript in its JSX, pretty similar to templating engines, isn't it?
You might be wondering, "why do we use function
instead of class
for declaring component?"
It's because we don't keep state
or use lifecycle methods. React components are declared as a class
in order to use those two things (although now we can do it with React hooks as well, but we'll leave hooks out for now.)
As we can see in the example, React's UI are made of three basic ingredients: components, JSX, and props.
- Component is a single piece of UI made out of methods and JSX.
- JSX is HTML supercharged with JS, enabling us to describe UI using JavaScript syntaxes.
- Props are arbitrary inputs that we pass down into a component.
There really is not much more we can learn from this simple static cards accept basic UI patterns, so let's move on to a more complex task.
Getting Complex With Wizard Form
In this second exercise, we will build A wizard form, which is a multi-step form designed to ease the filling process for a long and complex form. By only showing a few inputs on a screen, users will feel encouraged to fill the blank inputs rather than feeling overwhelmed and potentially abandoning the form.
Let’s take a look at how you can build a form like this with React:
https://codepen.io/nathansebhastian/pen/RvrOYq
The easiest way to create a multi-step form is to create a container form element, which contains all the wizard step component inside of it. This diagram will help you understand it clearly.
Although it seems to be more complex than a regular form, a wizard form still uses the same React principle. But since we have state
in this exercise, we'll need to include a new principle:
- State is used for storing dynamic data
Instead of having one form component, we will have one parent component and three child components. In the diagram above, <MasterForm/>
component will send data and functions to children components via props, and children components will trigger handleChange()
function to set values in the state of <MasterForm/>
. We'll need a function to move the form from one step to another as well.
Just like how CardList
send props to Card
, These children components will receive props from <MasterForm/>
for value
and onChange
props.
<Step1/>
component will render email address input<Step2/>
will render username input<Step3/>
will render password input and a submit button
The parent <MasterForm/>
will supply both data and function into child components, and child components will pass user inputs back to the parent using its props
.
First, we’ll create the form children components. This example will only include one input per form step. The comments will show the use of props
.
function Step1(props) {
if (props.currentStep !== 1) {
return null
}
return(
<div className="form-group">
<label htmlFor="email">Email address</label>
<input
className="form-control"
id="email"
name="email"
type="text"
placeholder="Enter email"
value={props.email}
onChange={props.handleChange}
/>
</div>
)
}
Since the child components look almost similar between one another, I'm just showing one of them above. You can take a look at the demo for the full code. Notice how we used function
instead of class
, since we don't use state
or lifecycle methods.
Then we can put this child component into the master form render()
function and pass in the necessary props. To handle events in which user type something into the text, we used the onChange
synthetic event, which is a part React core libraries for handling events. More details here.
Let's make the <MasterForm/>
component and initialize its state and methods. A currentStep
state will be initialized with the value of 1. This is used for step indicator so our form knows on what step we are currently. We'll use ES6 class since need local state for this component:
class MasterForm extends React.Component {
constructor(props) {
super(props)
this.state = {
currentStep: 1,
email: '',
username: '',
password: '',
}
}
// creating functions with ES6 arrow function syntax
handleChange = event => {
const {name, value} = event.target
this.setState({
[name]: value
})
}
handleSubmit = event => {
event.preventDefault()
const { email, username, password } = this.state
alert(`Your registration detail: \n
Email: ${email} \n
Username: ${username} \n
Password: ${password}`)
}
// render method here . . .
}
Next, we add the steps in the render method of <MasterForm/>
. It will send handleChange()
function and required state
values as props, note the highlighted code blocks:
render() {
return (
<React.Fragment>
<h1>A Wizard Form!</h1>
<p>Step {this.state.currentStep} </p>
<form onSubmit={this.handleSubmit}>
{/*
render the form steps and pass required props in
*/}
<Step1
currentStep={this.state.currentStep}
handleChange={this.handleChange}
email={this.state.email}
/>
<Step2
currentStep={this.state.currentStep}
handleChange={this.handleChange}
username={this.state.username}
/>
<Step3
currentStep={this.state.currentStep}
handleChange={this.handleChange}
password={this.state.password}
/>
</form>
</React.Fragment>
)
}
Because render()
must return a single element, the <React.Fragment>
component lets you return multiple elements in a render() method without creating an additional DOM element. More details here.
Then we add the next or previous step function, which will check if the current step has a previous or next step. If it is, it will push currentStep
up or down:
class MasterForm extends Component {
/*
* Test current step with ternary
* _next and _previous functions will be called on button click
*/
_next = () => {
let currentStep = this.state.currentStep
currentStep = currentStep >= 2? 3: currentStep + 1
this.setState({
currentStep: currentStep
})
}
_prev = () => {
let currentStep = this.state.currentStep
currentStep = currentStep <= 1? 1: currentStep - 1
this.setState({
currentStep: currentStep
})
}
// ... the rest of the code
We’ll make functions that will check whether the current step is 1 or 3. This is because we have 3 step wizard form. You can change them if you have more. The buttons will disappear if there is no next or previous step from the current step. These buttons will call on our _next
and _previous
methods.
/*
* the functions for our button
*/
previousButton(){
let currentStep = this.state.currentStep;
if(currentStep !==1){
return (
<button
className="btn btn-secondary"
type="button" onClick={this._prev}>
Previous
</button>
)
}
return null;
}
nextButton(){
let currentStep = this.state.currentStep;
if(currentStep <3){
return (
<button
className="btn btn-primary float-right"
type="button" onClick={this._next}>
Next
</button>
)
}
return null;
}
All that’s left is to render our next and previous buttons
/*
* add buttons to our form in render
*/
render(){
return(
<form onSubmit={this.handleSubmit}>
{/*
... other codes
*/}
{this.previousButton()}
{this.nextButton()}
</form>
)
}
If you're wondering why we used ()
on the call to buttons above, that's because we need to actually execute the button functions. The _next
and _previous
functions are only executed on button click, hence they mustn't have ()
on the call.
Phew! There's a lot of interactions between component and state here, but I hope by now you get to understand the use of state
in React application. To summarize, state
is just arbitrary data we defined in a component, and it becomes a part of that component forever. We can pass it down to another component, we can update it, and we can do conditional stuff based on what state
our component currently is in.
In this sample form, we have used state for keeping track of user's inputs and the current step of the wizard form. Since React is a one-way data flow from parent to child component, always remember that only the owner of the state
can mutate or update it.
To use state
we can use ES6 Class or React Hooks (will be explained in another tutorial.)
Still up for another exercise? Let's do it then!
P.S: if you're working on React form, checkout this in-depth tutorial by Arinich
GitHub Search App
Now for our third exercise, let's actually use some ES6 features to get data from GitHub API and display its results. This exercise will include all the things we learned from previous projects and new ones: lifecycle methods and rendering lists.
https://codepen.io/nathansebhastian/pen/LqpvrB
Note: There are extra css that I wrote for this app. Make sure to visit the CSS tab of the codepen above and paste it if you're not forking the pen
First, let's explore about Github API that we're going to use. Since we're only searching by username, we need this API url:
https:api.github.com/search/users?q={--search-st…
Let's get ready to build the app by writing the big header component first. It's just static Bootstrap Jumbotron actually:
const Header = () => {
return (
<div className="jumbotron">
<h1>Github Search App</h1>
<h2>Search users in GitHub using this simple React application.</h2>
<p>Click on the card to see more detail about individual user. The search default is nsebhastian (me!)</p>
</div>
);
};
Now let's think about making the input form. We're going to need:
- A search form
- Calling Github API when search form is submitted
- Display search result in a list of cards
We'll start with declaring the API constant
const API = 'api.github.com/';
Then let's initialize the "top" component with two state values: the searchText
and data
.
class App extends React.Component {
constructor(props){
super(props);
this.state = {
searchText: 'nsebhastian',
data: '',
}
}
fetchSearch = username => {
let url = `${API}search/users?q=${username}`;
fetch(url)
.then((res) => res.json() )
.then((data) => {
this.setState({
data: data
});
})
.catch((error) => console.log('Oops! . There Is A Problem' + error) )
}
componentDidMount() {
this.fetchSearch(this.state.searchText);
}
fetchSearch
function will fetch data from the API url, transform it into JSON object, then update our data
state with the freshly fetched data. It will be called in a component lifecycle method componentDidMount
. If you're not familiar with lifecycle methods, they are basically methods run on a particular times in the process of constructing and rendering components. There are other methods beside componentDidMount
, including constructor
method. Not all lifecycle methods are used frequently, some of them will be used more often than the rest.
Let's continue with our app by writing the render
method of App
component:
render() {
return (
<div>
<MyHeader />
<SearchForm
fetchSearch={this.fetchSearch}
/>
<Profiles
data={this.state.data}
/>
</div>
);
}
You might've guessed it by seeing the code that we need to create two more components, namely <SearchForm/>
and <Profiles/>
.
Let's start with the <SearchForm/>
. We have written form in React before, so this won't be hard. We just need a single text input and a submit button. Also, let me show you another way of getting input value without using state
:
class SearchForm extends React.Component {
render() {
return (
<div className="search-bar">
<form
className="input-group"
onSubmit={this.handleForm}>
<input
type="search"
ref="username"
placeholder="Type Username here"
className="form-control"/>
<span className="input-group-btn">
<button type="submit" className="btn btn-warning">Submit</button>
</span>
</form>
</div>
)
}
handleForm = event => {
event.preventDefault();
let username = this.refs.username.value
this.props.fetchSearch(username);
}
}
As you can see, we are getting username value by using ref
. This way we don't need to initialize state
at all. We have to use ES6 class for declaring the component, since we need to write the handleForm
function.
Now it's time to write the final component <Profiles/>
. I will use this opportunity to show you component declaration — arrow function style.
Profiles = props => {
if(props.data){
let data = props.data;
if (data.message === 'Not Found')
return (
<div className="notfound">
<h2>Oops !!!</h2>
<p>The Component Couldn't Find The You Were Looking For . Try Again </p>
</div>
);
else{
// map the users into JSX elements
let userList = data.items.map((name) => {
return (
<a key={name.id} href={name.html_url} target="blank">
<div className="bs-callout bs-callout-info">
<img className="user" src={name.avatar_url} alt={`${name.login}`}/>
<h4>Username : {name.login}</h4>
<p> Url : {name.html_url}</p>
<p> Score : {name.score} </p>
</div>
</a>
);
})
// then render it
return (
<div>{userList}</div>
);
}
}
else {
return <div>Fetching data . . .</div>
}
}
If you're following this tutorial from the start, I think you can understand what this <Profiles/>
component do. It will accept props
named data from its parent, then do something based on that props. We used the map
function to iterate and write JSX elements from the data
array. Then it's simply returned for rendering.
Notice how a key
props are passed into the <a>
element so that React can identify individual element in the list. More detail here.
Now you can search and click on the result to be taken to the GitHub user profile. Great job coming this far! We can actually improve the app by using React Router and create user page for a detailed view of individual user, but let's call it a day for now and go for React Router refactoring when we actually learn about React Router.
Conclusion
We've built three exercises to learn about React fundamentals, starting from a simple, static cards list, to a more complex React application that fetch data from GitHub API and display it. We also learned about dynamic data management using state
.
The take away from these tutorials are simple and reusable React patterns that you'll see in almost any React app:
- Component is a single piece of UI made out of methods and JSX.
- JSX is HTML supercharged with JS, enabling us to describe UI using JavaScript syntaxes.
- Props are arbitrary inputs that we pass down into a component.
- State is used for storing dynamic data. We can use it for rendering UI and storing fetched data
- Lifecycle methods are used for methods that need to be called on component render. The easiest example is for calling API and fetching data
So how was it? Isn't it feels more fun learning React by actually building pieces of components that you're more likely to use in your projects? Is it too hard for you? Please give me feedback so I can improve my writing skill.
React might seem difficult to grasp at first, but with proper introduction and direction, you can write fast and excellent front-end app with it. Indeed, I wrote a book to help you master React, so go check it out!