React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.
In this article, we’ll look at how to create simple apps with React.
Getting Started
The easiest way to create a React app is to use the Create React App Node package.
We can run it by running:
npx create-react-app my-app
Then we can go to the my-app
and run the app by running:
cd my-app
npm start
Create React App is useful for creating a single-page app.
React apps don’t handle any backend logic or databases.
We can use npm run build
to build the app to create the built app for production.
Creating Our First React App
Once we ran Create React App as we did above, we can create our first app. To create it, we go into App.js
and then start changing the code there.
To make writing our app easy, we’ll use JSX to do it. It’s a language that resembles HTML, but it’s actually just syntactic sugar on top of JavaScript.
Therefore, we’ll use the usual JavaScript constructs in JSX.
We’ll start by creating a Hello World app. To do this, we replace what’s there with the following in index.js
:
import React from "react";
import ReactDOM from "react-dom";
function App() {
return (
<div>
<h1>Hello World</h1>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In the code above, we have the App
component, which is just a function. It returns:
<div>
<h1>Hello World</h1>
</div>
which is our JSX code to display Hello World. h1
is a heading and div
is a div element.
The code above looks like HTML, but it’s actually JSX.
What we have above is a function-based component since the component is written as a function.
In the last 2 lines, we get the element with ID root
from public/index.html
and put our App
component inside it.
Another way to write the code above is to write:
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
render() {
return (
<div>
<h1>Hello World</h1>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The code above is a class-based component, which has a render
method to render the same JSX code into HTML.
The difference between the 2 examples is that one is a function and the other is a class that extends React.Component
.
Otherwise, they’re the same. Any component file has to include:
import React from "react";
import ReactDOM from "react-dom";
Otherwise, we’ll get an error.
React doesn’t require JSX, it’s just much more convenient to use it. A third way to create a Hello World app is to use the React.createElement
method.
We can use the method as follows:
import React from "react";
import ReactDOM from "react-dom";
const e = React.createElement;
const App = e("h1", {}, "Hello World");
const rootElement = document.getElementById("root");
ReactDOM.render(App, rootElement);
The first argument of the createElement
method is the tag name as a string, the second argument has the props, which are objects that we pass to the component created, and the third argument is the inner text of them element.
We won’t be using this very often since it’ll get very complex if we have to nest components and adding interaction.
Embedding Expressions in JSX
We can embed JavaScript expressions between curly braces. For example, we can write:
import React from "react";
import ReactDOM from "react-dom";
function App() {
const greeting = "Hello World";
return (
<div>
<h1>{greeting}</h1>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Then we see Hello World on the screen again.
Something more useful world calling a function as follows:
import React from "react";
import ReactDOM from "react-dom";
function App() {
const formatName = user => {
return `${user.firstName} ${user.lastName}`;
};
const user = {
firstName: "Jane",
lastName: "Smith"
};
return (
<div>
<h1>{formatName(user)}</h1>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In the code above, we defined a formatName
function inside the App
component that takes a user
object and returns user.firstName
and user.lastName
joined together.
Then we defined a user
object with those properties and called the function inside the curly braces.
Whatever’s return will be displayed between the braces. In this case, it’ll be Jane Smith.
Lifting State Up
We should lift any shared state up to their closest common ancestor.
This way, one state can be shared between multiple child components by passing them down via props.
For example, if we want to build a calculator for converting lengths, we can write the following:
import React from "react";
import ReactDOM from "react-dom";
class LengthInput extends React.Component {
constructor(props) {
super(props);
const { length } = this.props;
this.state = { length };
}
handleChange(e) {
this.props.onChange(e);
}
componentWillReceiveProps(props) {
const { length } = props;
this.setState({ length });
}
render() {
const { unit } = this.props;
return (
<>
<label>Length ({unit})</label>
<input
value={this.state.length}
onChange={this.handleChange.bind(this)}
placeholder={this.props.unit}
/>
<br />
</>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { lengthInMeter: 0, lengthInFeet: 0 };
}
handleMeterChange(e) {
this.setState({ lengthInMeter: +e.target.value });
this.setState({ lengthInFeet: +e.target.value \* 3.28 });
}
handleFeetChange(e) {
this.setState({ lengthInFeet: +e.target.value });
this.setState({ lengthInMeter: +e.target.value / 3.28 });
}
render() {
return (
<div className="App">
<LengthInput
length={this.state.lengthInMeter}
onChange={this.handleMeterChange.bind(this)}
unit="meter"
/>
<LengthInput
length={this.state.lengthInFeet}
onChange={this.handleFeetChange.bind(this)}
unit="feet"
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
In the code above, we have a length converter than converts meters to feet when we’re typing in the meters field and vice versa.
What we’re doing is that we keep the lengths all in the App
component. This is why the principle is called lifting the states up.
Since we’re keeping the lengths in the App
component, we have to pass them down to the LengthInput
components.
To do that, we pass props to them. Also, we pass in the units, and the change handler functions down to our LengthInput
components, so that they can the functions to update the states in the App
component.
In the handleFeetChange
and handleMeterChange
functions, we set the state according to the values entered in the LengthInput
components.
We call this.setState
in both functions to set the states. Each time setState
is called, render
will be called, so that the latest state will be passed down to our LengthInput
components.
In the LengthInput
components, we have the componentWillReceiveProps
hook which will get the latest prop values and then set the length
state accordingly.
this.state.length
is set as the value of the input
elements in LengthInput
, so the inputted value will be shown.
The advantage of lifting the states up to the parent component is that we only have to keep one copy of the state. Also, we don’t have to repeat the processing of the states in different child components.
The values of the inputs stay in sync because they’re computed from the same state.
Uncontrolled Components
Uncontrolled components are where we use the DOM properties to manipulate form inputs.
We can use a ref
to get form values directly from the DOM.
For example, we can write the following:
class App extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
}
handleSubmit(event) {
alert(`Name submitted: ${this.input.current.value}`);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
In the code above we created the this.input
ref in the constructor and attached it to the input element.
Then when we type in something and click Submit, we get whatever we typed into the input box displayed in the alert box since we accessed it with this.input.current.value
.
This makes integrating React and non-React code easier since we’re using the DOM to get the value.
Default Values
The value
attribute on form elements will override the value in the DOM.
With an uncontrolled component, we often want to specify the initial value and leave subsequent updates uncontrolled.
We can set the defaultValue
attribute to do this:
class App extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
}
handleSubmit(event) {
alert(`Name submitted: ${this.input.current.value}`);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<label>
Name:
<input type="text" defaultValue="Foo" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
In the code above, we set the defaultValue
attribute to Foo
, which is the value of this.input.current.value
if we don’t change the input value.
The file input Tag
File inputs are always uncontrolled components because its value can only be set by a user and not programmatically.
Therefore, we have to use a ref to access its value.
For example, we can write the following:
class App extends React.Component {
constructor(props) {
super(props);
this.fileRef = React.createRef();
}
handleSubmit(event) {
alert(`Filename: ${this.fileRef.current.files[0].name}`);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="file" ref={this.fileRef} />
<input type="submit" value="Submit" />
</form>
);
}
}
In the code above, we created a ref called fileRef
and attached it to the file input. Then we get the files[0].name
property that has the name of the selected file.
We can use the File API to do things with the selected file.
Lists and Keys
We can transform lists into HTML by calling the array’s map
method.
For example, if we want to display an array of numbers as a list, we can write:
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{[1, 2, 3, 4, 5].map(num => (
<span>{num}</span>
))}
</div>
);
}
}
In the code above, we called map
on the [1, 2, 3, 4, 5]
array. In the map
method’s callback, we returned a span
with the number inside and we do the same for each element.
Then we get:
12345
displayed on the screen.
We can assign it to a variable and then pass it into the ReactDOM.render
method as follows:
import React from "react";
import ReactDOM from "react-dom";
const nums = [1, 2, 3, 4, 5].map(num => <span>{num}</span>);
const rootElement = document.getElementById("root");
ReactDOM.render(nums, rootElement);
We’ll get the same items displayed.
Also, we can use the same code inside the render
method:
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
const nums = [1, 2, 3, 4, 5].map(num => <span>{num}</span>);
return <div>{nums}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Keys
When we render lists, we should provide a value for key
prop for each rendered element so that React can identify which items have changed, added, or removed.
It gives elements a stable identity. We should pick a key by using a string that uniquely identifies a list item among its siblings.
A key should be a string value.
For example, if we want to render a list of to-do items, we should pick the id
field as the key
‘s value as follows:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ id: 1, text: "eat" },
{ id: 2, text: "drink" },
{ id: 3, text: "sleep" }
]
};
}
render() {
return (
<div>
{this.state.todos.map(todo => (
<p key={todo.id.toString()}>{todo.text}</p>
))}
</div>
);
}
}
In the code above, we have the key={todo.id.toString()}
prop to set the key
to the todo
‘s id
converted to a string.
If we have no stable identity for our items, we can use the index for the entry as a last resort:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [{ text: "eat" }, { text: "drink" }, { text: "sleep" }]
};
}
render() {
return (
<div>
{this.state.todos.map((todo, index) => (
<p key={index.toString()}>{todo.text}</p>
))}
</div>
);
}
}
index
is always available and it’s unique for each array element, so it can be used as a value for the key
prop.
Extracting Components with Keys
If we render components, we should put the key
prop in the component rather than the element that’s being rendered.
For example, the following is incorrectly using the key
prop:
function TodoItem({ todo }) {
return <p key={todo.id.toString()}>{todo.text}</p>;
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ id: 1, text: "eat" },
{ id: 2, text: "drink" },
{ id: 3, text: "sleep" }
]
};
}
render() {
return (
<div>
{this.state.todos.map(todo => (
<TodoItem todo={todo} />
))}
</div>
);
}
}
In the TodoItem
component, we have:
<p key={todo.id.toString()}>{todo.text}</p>;
with the key
prop. We don’t want this there because we don’t need to identify a unique li
since it’s isolated from the outside already. Instead, we want to identify a unique TodoItem
.
Instead, we should write:
function TodoItem({ todo }) {
return <p>{todo.text}</p>;
}class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ id: 1, text: "eat" },
{ id: 2, text: "drink" },
{ id: 3, text: "sleep" }
]
};
}
render() {
return (
<div>
{this.state.todos.map(todo => (
<TodoItem todo={todo} key={todo.id.toString()} />
))}
</div>
);
}
}
We should add keys to items return with map
‘s callback.
Keys Only Need to Be Unique Among Siblings
Keys only need to be unique among sibling elements.
For example, we can write:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: [
{ id: 1, text: "eat" },
{ id: 2, text: "drink" },
{ id: 3, text: "sleep" }
],
comments: [
{ id: 1, text: "eat" },
{ id: 2, text: "drink" },
{ id: 3, text: "sleep" }
]
};
}
render() {
return (
<div>
<div>
{this.state.posts.map(post => (
<p key={post.id.toString()}>{post.text}</p>
))}
</div>
<div>
{this.state.comments.map(comment => (
<p key={comment.id.toString()}>{comment.text}</p>
))}
</div>
</div>
);
}
Since posts
and comments
aren’t rendered in the same div
, the key
values can overlap.
The key
prop doesn’t get passed to components. If we need the same value in a component we have to use a different name.
Embedding map() in JSX
We can embed the expression that calls map
straight between the curly braces.
For example, we can write:
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{[1, 2, 3, 4, 5].map(num => (
<span key={num.toString()}>{num}</span>
))}
</div>
);
}
}
useState
The useState
hook lets us manage the internal state of a function component. It takes an initial value as an argument and returns an array with the current state and a function to update it.
It returns the initial state when the component is initially rendered.
We can pass in a function to update the value if the new value is computed using the previous state.
For example, we can write the following to update the value based on a previous one:
function App() {
const [count, setCount] = React.useState(0);
return (
<>
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>
Decrement
</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Increment
</button>
</>
);
}
In the code above, we have:
setCount(prevCount => prevCount - 1)}
and:
setCount(prevCount => prevCount + 1)}
which decrements the count
and increment it respectively by passing in functions that take the previous count as the parameter and return the new count.
Otherwise, we can just pass in the new value to the state update function as we did in:
setCount(0)
useState
doesn’t automatically merge update objects. We can replicate this behavior with the spread syntax:
function App() {
const [nums, setNums] = React.useState({});
return (
<>
<p>{Object.keys(nums).join(",")}</p>
<button
onClick={() =>
setNums(oldNums => {
const randomObj = { [Math.random()]: Math.random() };
return { ...oldNums, ...randomObj };
})
}
>
Click Me
</button>
</>
);
}
In the code above, we have:
setNums(oldNums => {
const randomObj = { [Math.random()]: Math.random() };
return { ...oldNums, ...randomObj };
})
to create a randomObj
object with a random number as the key and value, and we merge that into another object with the old value then return it.
Then we display it with:
Object.keys(nums).join(",")
by getting the keys and joining them together.
Lazy initial state
We can pass in a function to useState
if we want to delay the setting of the initial state.
It’ll be ignored after the initial render if we pass in a function.
This is useful if the initial state is computed from some expensive operation.
For example, we can write:
function App() {
const [count, setCount] = React.useState(() => 0);
return (
<>
Count: {count}
<button onClick={() => setCount(() => 0)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>
Decrement
</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Increment
</button>
</>
);
}
In the code above, we have:
React.useState(() => 0)
which is a function that just returns 0.
We’ll see the same results as before.
Bailing out of a state update
If we update a state hook with the same value as the current. React will skip updating the state without rendering the children or firing effects.
React uses Object.is()
to compare the current and new states, which is close to the ===
operator except that +0
and -0
are treated to be not equal and NaN
is equal to itself.
React may still render before bailing out but it won’t go deeper into the tree if it finds that the old and new values are the same.
useEffect
We can use the useEffect
hook to do various operations that aren’t allowed inside the main body of the function component, which is anything outside the rendering phase.
Therefore, we can use this to do any mutations, subscriptions, setting timers, and other side effects.
It takes a callback to run the code.
We can return a function inside to run any cleanup code after each render and also when the component unmounts.
The callback passed into useEffect
fires after layout and paint, during a deferred event.
This makes this suitable for running operations that shouldn’t block the browser from updating the screen.
Code that must be run synchronously can be put into the callback of the useLayoutEffect
hook instead, which is the synchronous version of useEffect
.
It’s guaranteed to fire before any new renders. React will always flush the previous render’s effects before starting a new update.
Conditionally firing an effect
We can pass in a second argument to useEffect
with an array of values that requires an effect to be run when they change.
For example, we can use it to get data from an API on initial render as follows:
function App() {
const [joke, setJoke] = React.useState({});
useEffect(() => {
(async () => {
const response = await fetch("https://api.icndb.com/jokes/random");
const res = await response.json();
console.log(res);
setJoke(res);
})();
}, []);
return (
<>
<p>{joke.value && joke.value.joke}</p>
</>
);
}
Passing an empty array as the second argument will stop it from loading in subsequent renders.
We can pass in a value to the array to watch the value in the array change and then run the callback function:
function App() {
const [joke, setJoke] = React.useState({});
const [id, setId] = React.useState(1);
useEffect(() => {
(async () => {
const response = await fetch(`https://api.icndb.com/jokes/${id}`));
const res = await response.json();
console.log(res);
setJoke(res);
})();
}, [id]);
return (
<>
<button onClick={() => setId(Math.ceil(Math.random() * 100))}>
Random Joke
</button>
<p>{joke.value && joke.value.joke}</p>
</>
);
}
In the code above, when we click the Random Joke button, setId
is called with a new number between 1 and 100. Then id
changes, which triggers the useEffect
callback to run.
Then joke
is set with a new value, then the new joke
is displayed on the screen.
useContext
We can use the useContext
hook to read shared data shared from a React context. It accepts the context object returned from React.createContext
as an argument and returns the current context value.
The current context value is determined by the value
prop of the nearest context provider.
We can use it as follows:
const ColorContext = React.createContext("green");function Button() {
const color = React.useContext(ColorContext);
return <button style={{ color }}>button</button>;
}function App() {
return (
<>
<ColorContext.Provider value="blue">
<Button />
</ColorContext.Provider>
</>
);
}
In the code above, we created a new React context with:
const ColorContext = React.createContext("green");
Then in App
, we wrapped out Button
with the ColorContext.Provider
with the value
prop set to blue
.
Then in Button
, we have:
const color = React.useContext(ColorContext);
to get the value passed in from the ColorContext.Provider
and set it to color
.
Finally, we set the color
style of the button
with the color
‘s value.
A component calling useContext
will always re-render when the context value changes. If re-rendering is expensive, then we can optimize it with memoization.
useContext
is the React hooks version of Context.Consumer
.
useReducer
This hook is an alternative to useState
. It accepts a reducer function of type (state, action) => newState
.
useReducer
is preferable to useState
when we have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
It also lets us optimize performance for components that trigger deep updates because we can pass dispatch
down instead of callbacks.
For example, we can write:
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";function reducer(state, action) {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = React.useReducer(reducer, { count: 0 });
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>
<button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>
</>
);
}
In the code above, we have our reducer
which returns the new state depends on the action.type
‘s value. In this case, it’s either 'INCREMENT'
or 'DECREMENT'
.
If it’s ‘INCREMENT’
, we return { count: state.count + 1 }
.
If it’s ‘DECREMENT’
, we return { count: state.count — 1 }
.
Otherwise, we throw an error.
Then in App
, we call useReducer
by passing in a reducer
as the first argument and the initial state as the second argument.
Then we get the state
object, which has the current state object and a dispatch
function, which we can call with an action
object, which has the type
property with the value being one of ‘INCREMENT’
or ‘DECREMENT'
.
We used the dispatch
function in the buttons to update the state.
Finally, we display the latest state in state.count
.
Lazy initialization
We can pass in a function to the 3rd argument of useReducer
to initialize the state lazily.
The initial state will be set to init(initialArg)
.
For instance, we can rewrite the previous example as follows:
const init = initialCount => {
return { count: initialCount };
};
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";
function reducer(state, action) {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = React.useReducer(reducer, 0, init);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>
<button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>
</>
);
}
First, we have:
const init = initialCount => {
return { count: initialCount };
};
to return the initial state.
And instead of writing:
React.useReducer(reducer, { count: 0 });
We have:
React.useReducer(reducer, 0, init);
0 is passed in as the initialCount
of init
.
Then the rest of the code is the same as before.
Bailing out of a dispatch
If the same value is returned from a Reducer hook is the same as the current state, React will bail out without rendering the children or firing effects.
The comparison is done using the Object.is()
algorithm.
If we’re doing expensive operations while rendering, we can optimize it with useMemo
.
Conclusion
We can create a React app with the Create React App Node package.
Then we can add components as a function, class, or with React.createElement
.
The first 2 ways are used most often since they return JSX in the function or the render
method of the component class respectively.
JSX is much more convenient than createElement
for writing JavaScript code with React, especially when our app gets complex.
We can embed JavaScript expressions in between curly braces.
Hooks are used to change internal state, commit side effects, or hold any other logic.
React Router lets us navigate between different pages,
The Context API lets us share data between any components.