Styling your applications with CSS can be tedious. Luckily for us, others have built pre-styled web components in which we can place our react components. There are a number of pre-styled web components out there. It's likely you've already come across Bootstrap, Material, or possibly Semantic. Luckily for us, React Bootstrap and React Material Web Components, and Segment already exist for us. By using these pre-defined components, we can stand on the shoulders of giants, and riff off of their styling. While each of these are different in their own right, they're all more or less the same in their structure of how to install them.
Each of these have their own installation guides. You just need to make sure that you include the proper links within your header in your public/index.html file.
There's no point in reinventing the wheel. With these packages, you need only to import the required material, and then use them as their own JSX elements. Let's start with adding a linear loader to our page to make it apparent that we're loading data (the remainder of the code is from the previous lesson):
xxxxxxxxxx941import React, { useState, useEffect } from "react";2import NodeCache from "node-cache";3import shuffle from "lodash/shuffle";45import { LinearProgress } from "@rmwc/linear-progress";6import "@material/linear-progress/dist/mdc.linear-progress.css";78// initializing the cache9const myCache = new NodeCache({ stdTTL: 100, checkperiod: 120 });1011export const Reddit = props => {12 const cacheKey = "subreddits";13 const cachedStuff = myCache.get(cacheKey);14 const startingArrays = cachedStuff || [];1516 // Hooks17 const [allPosts, setAllPosts] = useState(startingArrays);18 const [searchTerm, setSearchTerm] = useState("");19 const [posts, setPosts] = useState(startingArrays);2021 // create a boolean hook for whether we want to see a loading bar:22 const [isLoading, setIsLoading] = useState(false);2324 const callWithFetch = async () => {25 if (cachedStuff) {26 setAllPosts(cachedStuff);27 setPosts(cachedStuff);28 return;29 }3031 // Set the is loading bar:32 setIsLoading(true);3334 const urls = [`r/${props.subreddit}.json`, `r/birdswitharms.json`];3536 const subredditArrays = urls.map(async url => {37 const response = await fetch(url);38 const json = await response.json();39 const allPosts = json.data.children.map(obj => obj.data);4041 return allPosts;42 });4344 const awaitedThings = await Promise.all(subredditArrays);45 const allPosts = awaitedThings.flat();46 const shuffledData = shuffle(allPosts);4748 myCache.set(cacheKey, shuffledData, 100);49 setAllPosts(shuffledData);50 setPosts(shuffledData);51 setIsLoading(false);52 };5354 useEffect(() => {55 if (allPosts.length === 0) {56 callWithFetch();57 }58 });5960 useEffect(() => {61 document.title = `Showing ${posts.length} posts`;62 });6364 const handleSearch = event => {65 setSearchTerm(event.target.value);66 setPosts(allPosts.filter(post => post.title.includes(event.target.value)));67 };686970 const LoadingBarDisplay = () => {71 return <> {isLoading && <LinearProgress />}</>;72 };7374 return (75 <div>76 <h1>r/{props.subreddit}</h1>77 <div> FILTER THE CUTENESS </div>78 <input type="text" value={searchTerm} onChange={handleSearch} />79 <br />80 <LoadingBarDisplay />81 <br />82 <ul>83 {posts.map(post => (84 <li key={post.id}>85 <br />86 <img src={post.thumbnail} />87 {post.title}88 </li>89 ))}90 </ul>91 </div>92 );93};94
Now that we have our loading bar set, let's futz with some of the pre-supplied component structure from the documentation. Reddit has a list like structure, but let's revamp it to have cards. First we'll need to take the basic card from the RMWC documentation and change it up! First we need to make sure that we import the Card data and the Typography data.
xxxxxxxxxx65123import {4 Card,5 CardMedia,6 CardPrimaryAction,7 CardActionIcon,8 CardActions,9 CardActionIcons,10 CardMediaContent11} from "@rmwc/card";12import "@material/card/dist/mdc.card.css";13import "@material/button/dist/mdc.button.css";14import "@material/icon-button/dist/mdc.icon-button.css";1516import { Typography } from "@rmwc/typography";17import "@material/typography/dist/mdc.typography.css";18192021const RedditCard = ({ post }) => {22 return (23 <Card style={{ width: "12.5rem" }}>24 <CardPrimaryAction>25 <CardMedia26 square27 style={{28 backgroundImage: `url(${post.thumbnail})`29 }}30 >31 <CardMediaContent>32 <Typography33 use="subtitle2"34 tag="div"35 theme="textPrimaryOnDark"36 style={{37 padding: "0.5rem 1rem",38 backgroundImage:39 "linear-gradient(to bottom, transparent 0%, rgba(0, 0, 0, 0.5) 100%)",40 bottom: "0",41 left: "0",42 right: "0",43 position: "absolute"44 }}45 >46 {post.title}47 </Typography>48 </CardMediaContent>49 </CardMedia>50 </CardPrimaryAction>51 <CardActions>52 <CardActionIcons>53 <a href={post.url}>54 <CardActionIcon icon="comment" />55 </a>56 <CardActionIcon onIcon="favorite" icon="favorite_border" />57 <CardActionIcon icon="bookmark_border" />58 <CardActionIcon icon="share" />59 </CardActionIcons>60 </CardActions>61 </Card>62 );63};6465 Now we've got a solid card to display our data!
We have our cards set up, but they're acting as block elements. What we need to do is place them inside of a grid! Luckily for us, instead of having to use flexbox or some other methods of adding grid data, we can just import the RMWC Layout Grid system:
xxxxxxxxxx261 23import { Grid, GridCell } from "@rmwc/grid";4import "@material/layout-grid/dist/mdc.layout-grid.css";56 789 return (10 <div>11 <h1>r/{props.subreddit}</h1>12 <div> FILTER THE CUTENESS </div>13 <input type="text" value={searchTerm} onChange={handleSearch} />14 <br />15 <LoadingBarDisplay />16 <br />17 <Grid>18 {posts.map(post => (19 <GridCell span={3} key={post.id}>20 <RedditCard post={post} />21 </GridCell>22 ))}23 </Grid>24 </div>25 );26};
We've added our RedditCard to our APICall.jsx, but the file is getting too big! So, let's create a brand new stateless component just for our card:
xxxxxxxxxx611import React from "react";2import {3 Card,4 CardPrimaryAction,5 CardMedia,6 CardActionIcon,7 CardActionIcons,8 CardActionButton,9 CardActionButtons,10 CardActions11} from "@rmwc/card";12import "@material/card/dist/mdc.card.css";13import "@material/button/dist/mdc.button.css";14import "@material/icon-button/dist/mdc.icon-button.css";1516import { Typography } from "@rmwc/typography";17import "@material/typography/dist/mdc.typography.css";1819export const RedditCard = ({ post }) => {20 console.log(post);2122 return (23 <Card style={{ width: "21rem" }}>24 <CardPrimaryAction>25 <CardMedia26 sixteenByNine27 style={{28 backgroundImage: `url(${post.thumbnail})`29 }}30 />31 <div style={{ padding: "0 1rem 1rem 1rem" }}>32 <Typography use="headline6" tag="h2">33 {post.title}34 </Typography>35 <Typography36 use="subtitle2"37 tag="h3"38 theme="textSecondaryOnBackground"39 style={{ marginTop: "-1rem" }}40 >41 by {post.author}42 </Typography>43 </div>44 </CardPrimaryAction>45 <CardActions>46 <CardActionButtons>47 <a href={post.url}>48 <CardActionButton>Read</CardActionButton>49 </a>50 <CardActionButton>Bookmark</CardActionButton>51 </CardActionButtons>52 <CardActionIcons>53 <CardActionIcon onIcon="favorite" icon="favorite_border" />54 <CardActionIcon icon="share" />55 <CardActionIcon icon="more_vert" />56 </CardActionIcons>57 </CardActions>58 </Card>59 );60};61Now we've got our APICall.jsx a bit cleaned up:
xxxxxxxxxx951import React, { useState, useEffect } from "react";2import NodeCache from "node-cache";3import shuffle from "lodash/shuffle";4import { RedditCard } from "./RedditCard";56import { LinearProgress } from "@rmwc/linear-progress";7import "@material/linear-progress/dist/mdc.linear-progress.css";89import { Grid, GridCell } from "@rmwc/grid";10import "@material/layout-grid/dist/mdc.layout-grid.css";1112// initializing the cache13const myCache = new NodeCache({ stdTTL: 100, checkperiod: 120 });1415export const Reddit = props => {16 const cacheKey = "subreddits";17 const cachedStuff = myCache.get(cacheKey);18 const startingArrays = cachedStuff || [];1920 // Hooks21 const [allPosts, setAllPosts] = useState(startingArrays);22 const [searchTerm, setSearchTerm] = useState("");23 const [posts, setPosts] = useState(startingArrays);2425 // create a boolean hook for whether we want to see a loading bar:26 const [isLoading, setIsLoading] = useState(false);2728 const callWithFetch = async () => {29 if (cachedStuff) {30 setAllPosts(cachedStuff);31 setPosts(cachedStuff);32 return;33 }3435 // Set the is loading bar:36 setIsLoading(true);3738 const urls = [`r/${props.subreddit}.json`, `r/birdswitharms.json`];3940 const subredditArrays = urls.map(async url => {41 const response = await fetch(url);42 const json = await response.json();43 const allPosts = json.data.children.map(obj => obj.data);4445 return allPosts;46 });4748 const awaitedThings = await Promise.all(subredditArrays);49 const allPosts = awaitedThings.flat();50 const shuffledData = shuffle(allPosts);5152 myCache.set(cacheKey, shuffledData, 100);53 setAllPosts(shuffledData);54 setPosts(shuffledData);55 setIsLoading(false);56 };5758 useEffect(() => {59 if (allPosts.length === 0) {60 callWithFetch();61 }62 });6364 useEffect(() => {65 document.title = `Showing ${posts.length} posts`;66 });6768 const handleSearch = event => {69 setSearchTerm(event.target.value);70 setPosts(allPosts.filter(post => post.title.includes(event.target.value)));71 };7273 const LoadingBarDisplay = () => {74 return <> {isLoading && <LinearProgress />}</>;75 };7677 return (78 <div>79 <h1>r/{props.subreddit}</h1>80 <div> FILTER THE CUTENESS </div>81 <input type="text" value={searchTerm} onChange={handleSearch} />82 <br />83 <LoadingBarDisplay />84 <br />85 <Grid>86 {posts.map(post => (87 <GridCell span={3} key={post.id}>88 <RedditCard post={post} />89 </GridCell>90 ))}91 </Grid>92 </div>93 );94};95