How to Conditionally Render a List of Cards with React
Conditionally rendering components in React can be confusing when you are first getting started. There are a lot of little quirks and patterns that take time to get used to. This post will show you a very common pattern of conditionally displaying a list of data with React so that you can be one step closer to building full-blown web apps.
Prerequisites
I will be using the react-bootstrap
library to help with styling, but I will not be going over the details of how to use it.
Before going through this post, you should be familiar with:
- setting up a React project from scratch or with
create-react-app
- passing props
- creating components
- installing and using a React component library
If you'd like me to write about these topics, reach out to me on Twitter or shoot me an email.
Creating the layout
Here is a layout with a Navbar
and a group of hard-coded cards.
import React from "react";
import Container from "react-bootstrap/Container";
import Form from "react-bootstrap/Form";
import FormControl from "react-bootstrap/FormControl";
import Button from "react-bootstrap/Button";
import Navbar from "react-bootstrap/Navbar";
import Card from "react-bootstrap/Card";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import "bootstrap/dist/css/bootstrap.min.css";
function App() {
return (
<Container fluid="lg">
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">Trello Clone</Navbar.Brand>
<Form inline>
<FormControl
type="text"
placeholder="Enter title"
className="mr-sm-2"
/>
<Button variant="primary">Add Card</Button>
</Form>
</Navbar>
<Row noGutters={true}>
<Col>
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title>Card Title</Card.Title>
</Card.Body>
</Card>
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title>Card Title</Card.Title>
</Card.Body>
</Card>
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title>Card Title</Card.Title>
</Card.Body>
</Card>
</Col>
</Row>
</Container>
);
}
export default App;
The section that we care about is the Col
containing the three Card
components. We are going to convert this into a conditionally rendered set of cards.
The first thing that we need to do is move the card titles to state. For that we import useState
and add an array of card titles. Let's also get rid of the hard-coded Card
components.
import React, { useState } from "react";
// other imports
function App() {
const [titles, setTitles] = useState(["title1", "title2", "title3"]);
return (
<Container fluid="lg">
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">Trello Clone</Navbar.Brand>
<Form inline>
<FormControl
type="text"
placeholder="Enter title"
className="mr-sm-2"
/>
<Button variant="primary">Add Card</Button>
</Form>
</Navbar>
<Row noGutters={true}>
<Col></Col>
</Row>
</Container>
);
}
export default App;
From here, we want need to map
over the titles, and create an array of Card
components for each title.
function App() {
const [titles, setTitles] = useState(["title1", "title2", "title3"]);
return (
<Container fluid="lg">
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">Trello Clone</Navbar.Brand>
<Form inline>
<FormControl
type="text"
placeholder="Enter title"
className="mr-sm-2"
/>
<Button variant="primary">Add Card</Button>
</Form>
</Navbar>
<Row noGutters={true}>
<Col>
{titles.map((title) => (
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title>{title}</Card.Title>
</Card.Body>
</Card>
))}
</Col>
</Row>
</Container>
);
}
This will display the cards with a dynamic title coming from state. inside the Card.Title
component. At this point, we have a static list of titles, but we want this to be dynamic. This is where the conditional rendering comes in.
Conditionally rendering an array
Let's handle the case where there are no cards to display. First, we want to remove all the titles from array. Then, we need to use the ternary operator to handle the case where the array has no elements.
import React, { useState } from "react";
import Container from "react-bootstrap/Container";
import Form from "react-bootstrap/Form";
import FormControl from "react-bootstrap/FormControl";
import Button from "react-bootstrap/Button";
import Navbar from "react-bootstrap/Navbar";
import Card from "react-bootstrap/Card";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Alert from "react-bootstrap/Alert";
import "bootstrap/dist/css/bootstrap.min.css";
function App() {
const [titles, setTitles] = useState([]);
return (
<Container fluid="lg">
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">Trello Clone</Navbar.Brand>
<Form inline>
<FormControl
type="text"
placeholder="Enter title"
className="mr-sm-2"
/>
<Button variant="primary">Add Card</Button>
</Form>
</Navbar>
<Row noGutters={true}>
<Col>
{titles.length > 0 ? (
titles.map((title) => (
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title>{title}</Card.Title>
</Card.Body>
</Card>
))
) : (
<Alert variant={"warning"}>
Add a card title to see it display
</Alert>
)}
</Col>
</Row>
</Container>
);
}
export default App;
Here we are displaying titles if there are any contained in the titles state. Otherwise, we display an Alert
component telling the user to add a card title. If the ternary operator is a little confusing, here's an equivalent App
component using an if-else
block.
function App() {
const [titles, setTitles] = useState([]);
const renderTitles = () => {
if (titles.length > 0) {
return titles.map((title) => (
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title>{title}</Card.Title>
</Card.Body>
</Card>
));
} else {
return (
<Alert variant={"warning"}>Add a card title to see it display</Alert>
);
}
};
return (
<Container fluid="lg">
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">Trello Clone</Navbar.Brand>
<Form inline>
<FormControl
type="text"
placeholder="Enter title"
className="mr-sm-2"
/>
<Button variant="primary">Add Card</Button>
</Form>
</Navbar>
<Row noGutters={true}>
<Col>{renderTitles()}</Col>
</Row>
</Container>
);
}
This example uses a normal if-else
block to display the exact same output as the previous example. Both the ternary operator and if-else
statements are completely valid ways to conditionally render components, but the ternary operator syntax is usually shorter and commonly used.
Now that we are conditionally rendering the Card
components or an Alert
, let's finish this up by allowing the user to update the array of titles from the navbar.
Taking user input to update state
We will be using the form in the navbar to allow the user to submit new titles. First, let's take a look at the code.
import React, { useState } from "react";
import Container from "react-bootstrap/Container";
import Form from "react-bootstrap/Form";
import FormControl from "react-bootstrap/FormControl";
import Button from "react-bootstrap/Button";
import Navbar from "react-bootstrap/Navbar";
import Card from "react-bootstrap/Card";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Alert from "react-bootstrap/Alert";
import "bootstrap/dist/css/bootstrap.min.css";
function App() {
const [title, setTitle] = useState("");
const [titles, setTitles] = useState([]);
const addTitle = () => {
setTitles((allTitles) => [...allTitles, title]);
};
const handleTitleChange = (event) => {
setTitle(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
addTitle();
setTitle("");
};
return (
<Container fluid="lg">
<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">Trello Clone</Navbar.Brand>
<Form inline onSubmit={handleSubmit}>
<FormControl
type="text"
placeholder="Enter title"
className="mr-sm-2"
onChange={handleTitleChange}
value={title}
/>
<Button variant="primary">Add Card</Button>
</Form>
</Navbar>
<Row noGutters={true}>
<Col>
{titles.length > 0 ? (
titles.map((title) => (
<Card style={{ width: "18rem" }}>
<Card.Body>
<Card.Title>{title}</Card.Title>
</Card.Body>
</Card>
))
) : (
<Alert variant={"warning"}>
Add a card title to see it display
</Alert>
)}
</Col>
</Row>
</Container>
);
}
export default App;
Both title
and setTitle
were added to state so that they can keep track of the value the user enters in the FormControl
component. We set the value
of the FormControl
to the title from state so React is aware of what the user enters into the input. We also created a handleTitleChange
function so the title can be updated onChange
.
We also created the addTitle
function to handle updating state by creating a new titles array including the user input title.
Finally, we added a handleSubmit
function and added it to the onSubmit
property of the Form
component. Now, whenever the "Add Card" button is clicked or the user presses enter, the form will get submitted. This will run handleSubmit
which adds the new title to state and resets the input value.
Since the titles are rendered based on state, submitting the form will automatically cause a new card to be created!
Wrapping up
We have covered conditionally rendering a list of cards based on state and updating the state when a form is submitted. If you have any questions, or if you liked this article, feel free to reach out!