Designing React Components, Best Practices
React Components form the core units that we compose together to create web applications. If you know how to build and manipulate an individual React component, you can create anything your imagination comes up with!
If you want to follow along, playing with React is a simple npm tool away with Create-React-App!
Hello, World - The Simplest React Component
The simplest React component can be written with 1 line of code:
const HelloWorldComponent = () => <h1>Hello, World!</h1>
A lot is going on with that 1 line.
What is JSX?
The entire <h1>Hello, World!</h1>
block is NOT valid JavaScript. That's JSX. React describes it as a syntax extension of JavaScript. It will allow us to write HTML with the full power of JavaScript. Even more simply, JSX is an example of what would happen if JavaScript and HTML had a baby.
Here's an example of rendering an array of data into the DOM using JSX:
const someRandomNames = [
'Matthew',
'John',
'Jane',
'Tony Stark',
]
const RenderNames = () => (
<ul>
{someRandomNames.map(name => <li>{name}</li>)}
</ul>
)
By enclosing JavaScript expressions within {}
curly brackets, we can call any expression to return JSX or an array of JSX, and it will simply be rendered to the DOM. This is a clean way of using JavaScript to render data to the DOM.
Class Components vs Functional Components
There are two kinds of React Components: Class Components and Functional Components. The example above is an example of a functional component. Here is an example of a class component:
import { Component } from 'react'
class HelloWorldClassComponent extends Component {
render() {
return <h1>Hello, World!</h1>
}
}
This class component does the same thing as the functional component: they both render <h1>Hello, World!</h1>
to the DOM. The difference is that the functional component is a lot less code to write than the class-based component.
The React team themselves said that:
React needs a better primitive for sharing stateful logic.
There was a time (before React v16.8) where we were forced to write class components because they gave us access to state and the component lifecycle methods, but React now gives us Hooks to let us 'hook' into class component functionalities with a functional component.
Hooks let you use more of React’s features without classes.
Functional Components
Because of how short and concise functional components are compared to classical components, and since we can still tap into the same powers that a classical component has via Hooks, we will be using functional components going forward.
Functional Components are simply functions that return JSX.
Composing React Components
Let's add a bit of content to our component example:
const PageComponent = () => (
<>
<h1>Hello, World!</h1>
<p>A simple Hello World Component!</p>
<h2>Things We've Learned</h2>
<p>Here are a couple of things we've learned so far</p>
<ul>
<li>JSX</li>
<li>Class vs Function Components</li>
</ul>
<h2>What's We'll Learn Next</h2>
<p>Here's what we will be going over next</p>
<ul>
<li>Composing Components</li>
<li>State</li>
<li>Props</li>
</ul>
</>
)
Right off the bat, you'll notice that all of the content is enclosed within a React Fragment <> ... </>
. JSX expressions must have ONLY 1 parent element. We could have enclosed it all within a <div>
or a <section>
, but for conciseness and to not render any extra DOM, we'll use the Fragment.
Everything else within the JSX expression is just extra content written in plain HTML.
Breaking Down a Single Component into Many
We can imagine that after writing any significant amount of content, this 1 component is going to get very crowded. We can and should break this component down into composable pieces. Watch how we can split this large PageComponent into a few parts:
const Intro = () => (
<>
<h1>Hello, World!</h1>
<p>A simple Hello World Component!</p>
</>
)
const Learned = () => (
<>
<h2>Things We've Learned</h2>
<p>Here are a couple of things we've learned so far</p>
<ul>
<li>JSX</li>
<li>Class vs Function Components</li>
</ul>
</>
)
const ToLearn = () => (
<>
<h2>What's We'll Learn Next</h2>
<p>Here's what we will be going over next</p>
<ul>
<li>Composing Components</li>
<li>State</li>
<li>Props</li>
</ul>
</>
)
const PageComponent = () => (
<>
<Intro />
<Learned />
<ToLearn />
</>
)
We broke up the Page Component into 3 components: Intro, Learned, and ToLearn. By doing so, we can have these smaller child components focus on their specific task, allowing us to break down our ideas into smaller composable components.
As an app grows and acquires many moving parts, breaking down our concerns into small digestible components allows us to reason about their logic a lot more easily.
To define a child component, create a function that returns some JSX. To use that component, insert it into JSX enclosed in < />
tags.
const ChildComponent = () => <p>Content from child.</p>
const ParentComponent = () => (
<>
<p>Content from parent.</p>
<ChildComponent />
</>
)
Introducing State with the useState Hook
Up to this point, we've only rendered static information to the screen. We hard-coded our headers, hard-coded our content in the paragraphs, and hard-coded our lists.
React can do much more than handle static content, though. React fulfills the equation:
UI = fn(state)
UI is a function of state.
React allows us to bind our data to the component state which will instruct React to re-render our components if their state ever changes.
To implement state in our functional components, we will be taking advantage of the useState Hook. Here is a simple example that uses useState to declare a component state variable. We then bind that state to the value of an input and render that state to the DOM. As the state changes, the component will re-render to keep the DOM in sync with the state.
import { useState } from 'react'
const StatefulComponent = () => {
const [inputValue, setInputValue] = useState('')
return (
<>
<input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
<p>inputValue: {inputValue}</p>
</>
)
}
Again, a functional React component is simply a function that returns JSX. That same rule applies above (the function StatefulComponent
returns JSX). Included within the function, above the return, is where we use the useState
hook to define some state for the component.
The useState Hook is a function that declares state for the component by taking an initial value as an argument and returning a pair of values:
- The Current State (
inputValue
in our example) - A function that updates the State (
setInputValue
in our example)
One rule when using useState
is that we should never update the state by setting the current state variable. This won't instruct the component to re-render. Instead, the only way you should update your state is with the function returned by useState
.
Passing Data to Child Components w/ Props
We've learned about defining state in our components and about composing smaller functions together instead of defining really big components. Props are a way of passing data down to child components, allowing them to handle the responsibility of rendering that data.
Here is an example where we define some state and pass down data into a child component. A simple To-Do list:
import { useState } from 'react'
const Todo = () => {
const [inputValue, setInputValue] = useState('')
const [todoList, setTodoList] = useState([])
const handleSubmit = event => {
event.preventDefault()
setTodoList([
...todoList,
inputValue,
])
setInputValue('')
}
return (
<>
<form onSubmit={handleSubmit}>
<label htmlFor="listItem">List Item: </label>
<input id="listItem" value={inputValue} onChange={e => setInputValue(e.target.value)} />
</form>
<p>Todo List:</p>
<ul>
{todoList.map(item => <li>{item}</li>)}
</ul>
</>
)
}
This component, while not that large now, has the potential to grow larger over time. We should break this down into smaller components with specific responsibilities. This example Todo App has 2 main parts:
- Receive User Input
- Render the Input
Breaking down the Todo App
Let's break these roles down into separate components.
Receive Input:
import { useState } from 'react'
const ReceiveInput = ({ todoList, setTodoList }) => {
const [inputValue, setInputValue] = useState('')
const handleSubmit = event => {
event.preventDefault()
setTodoList([
...todoList,
inputValue,
])
setInputValue('')
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="listItem">List Item: </label>
<input id="listItem" value={inputValue} onChange={e => setInputValue(e.target.value)} />
</form>
)
}
Render Input:
const RenderInput = ({ todoList }) => (
<>
<p>Todo List:</p>
<ul>
{todoList.map(item => <li>{item}</li>)}
</ul>
</>
)
Composing the Components:
import { useState } from 'react'
const Todo = () => {
const [todoList, setTodoList] = useState([])
return (
<>
<ReceiveInput {...{ todoList, setTodoList }} />
<RenderInput todoList={todoList} />
</>
)
}
Now we have 3 components, each with specific roles:
ReceiveInput
is responsible for handling user input and updating the Todo list stateRenderInput
is responsible for rendering the Todo list stateTodo
is responsible for declaring the Todo list state and composing togetherReceiveInput
andRenderInput
Passing Props Down to Child Components
As functions, React Components have a prop
argument which can be used to extract data passed down from the parent. The Todo Component in the previous example is where we set JSX attributes which are then passed down as props for the two child components.
This technique helps in breaking down large components into smaller, composable parts.
Conclusion
With all of the tools React Components give a developer, creating a web application has never been easier before. React features such as props, state, and component composability allow a developer to work on small chunks of logic at a time and then compose them together into re-usable parts. This is analogous to how we can connect Lego pieces to create anything our imagination takes us.
For more information on React, check out their documentation for more information!
Happy coding!!! 💻