In this YouTube tutorial by Thu Nghiem (the author’s website is devchallenges.IO) he builds a Country Ranker using a free external API. Sweet project in the Next JS environment.
The app has a search feature, sorting and filtering, and individual pages for each country. It is responsive using no CSS frameworks, and has a theme switcher with a light-mode and dark-mode.
The project created in the tutorial is hosted at worldranks.vercel.app and I think there is link to the GitHub repo as well. I really enjoyed this tutorial and in two hours (leverage 2x to cut in half) was a very entertaining watch.
He built the design of Figma which I thought was cool and apparently you can get access to the file.
He ran create-next-app for boilerplate project and then moved the pages and styles folders into a new folder named src, (you will need to stop and restart server after changing the folder structure like this).
Created some CSS variables in the global CSS file and also pasted in a google font via @import at the top of this file as well. In index.js he removed everything in the footer and main elements. In the Home.module.css file he removed everything but the hollowed-out selectors of container, main, and footer.
6m: He created a Layout.js component in src/components/Layout folder. He cut and pasted in the guts of index.js into Layout.js, (Everything in the return, and alos imported Head from next/Head). Imported and used Layout.js in index.js. Note: All stylesheets were imported using import styles from “whatEvPath” and referenced via styles.container or styles.selectorName, etc. as an example.
8m: Used an inline SVG in layout for logo by pasting into a header tag. Added className for header. Added some styles for container, header, footer in Layout.module.css
10m: favicon
10m: He fetched data from api using getStaticProps which gets all data at build time and the API is at restcountries.eu/rest/v2/all. Data only updated when the project is built, (unless you bring in the revalidate key).
13m: SearchInput component and CSS file for that comp. Stop the server and install material UI for icons created in SearchInput component, (@material-ui/core and @material-ui/icon). Imported and used SearchRounded component from Material UI. Styled wrapper, input, and input::placeholder for SearchInput comp.
What I liked about his folder structures is he created a folder for each component and he had a JS file and a CSS file inside of each folder so it kept the JavaScript files and the CSS files right next to another. I have since learned that using styled-components will even reduce the need for two files.
18m: Created new CountriesTable component file and CSS file in its own folder. Takes in countries and returns headings of Name and Population. Add this comp to index.js homepage. In this new component also return via a map function all of the countries in their own rows.
22m: Style the CountriesTable component.
27m: Sorting by population. In CountriesTable component file create a constant named orderBy and set to an arrow function that takes in a country and uses .sort() method. If (direction === “asc”) have on sort and If (direction === “desc”) have a slightly different function. Create a const named CountriesTable and set to an arrow function that takes in countries and sets another const named orderedCountries and set to orderBy(countries, “desc”). In the sort functions create a new array by using spread notation. Update map function to use orderedCountries
30m: Add a KeyboardArrowDownRounded component to the name and population table headings. Update some more styling.
33m: Create a new component by adding a constant named SortArrow and set to an arrow function that takes in a direction and in the function block, if (!direction) return a react fragment. under this add if (direction === “asc”) return the KeyboardArrowDownRounded wrapped in a div with the className of styles.heading_arrow ELSE return KeyboardArrowUpRounded wrapped in a div with the className of styles.heading_arrow. These divs should be cut from the table headings. Replace with new SortArrow comp and add a prop of direction=””.
Manage the direction via useState for direction, setDirection with empty (undefined) initial value. For the direction create a new constant named switchDirection and set to an arrow function. In the arrow function’s block say if (!direction) {setDirection(‘desc)} else if (direction === “desc”) {setDirection(‘asc’)} else {setDirection(null)}
Now on the button add an onClick prop and set to {switchDirection} AND in the JSX find the SortArrow component and update the prop of direction to {direction}. This works but set the value when you click the heading as well.
Create new constant named setValueAndDirection and set to an arrow function that takes in a value and in the block calls switchDirection() and setValue(value). On the button add a setValueAndDirection prop and set to ‘population’ and do the same for the name button. Not working yet because you haven’t used the state. In the orderBy function pass it in the value as the second parameter and use this value in place of population in the sort functions. Now in orderedCountries pass in the value and the direction as second and third params ()remove the hard-coded “desc”). Now it works.
38m: Searching/Filtering by country. Create a constant named filteredCountries and set to countries.filter() and pass filter an arrow function that takes in country and country.name.toLowerCase().includes(keyword). Now we need to create a new state called [keyword, setKeyword] = useState(“”) with empty string by default. On the input add an onChange and set to onInputChange. create this function above by setting const named onInoutChange to arrow function that takes in an event and prevent default. Then setKeyword(event.target.value.toLowercase()). No ref set here – I didn’t know you could do this? Nevermind, he is using state with two way binding.
41m: Searching/Filtering by country by region and sub-reason. In the filteredCountries’ arrow function block add an || country.region.toLowerCase().includes(keyword) || country.subregion.toLowerCase().includes(keyword)
I love to watch the creation of this functionality because I just did it in the car-cost-app.
42m: Individual country pages. He did this by using getServerSideProps with a dynamic parameter country/ID []. He built up the basic content then the details panel then the neighboring countries panel.
49m: Styling country panel in CSS file
63m: Go back home link.
Get neighbor countries info. Create new const named getCountry set to an async arrow function that takes an id and returns country. We copy fetch code from GSSP function and paste in here and now in GSSP change const country to instead be set to await getCountry(id). Now you can get rid of the fetch in the GSSP function.
In the main Country component set a constant named borders to country.borders.map() and pass map an arrow function that takes border and in block calls getCountry(border). You will get an error because getCountry is a promise. So create a new const getBorders and set it to an async arrow function. In the block cut and paste this line of code with the map and after add “return border”.
We want borders when we open the web page so we bring in useEffect which takes in a function and has a dependency array that is empty at first. Inside the useEffect you have an arrow function that calls getBorders(). Create a new piece of state for [borders, setBorders] equal to useState([]) with default being an empty array.
Now in getBorders function swap “return border” with setBorders(borders)
Now we have an array of Promises so in getBorders update const borders to be equal to await Promise.all() and put the map line inside.
Now add the JSX to output some data related to the borders. Inside a div have {borders.map()} and inside the map have an arrow function that takes in “flag” and “name” and “returns” a div with an image and div with the name inside. Add necessary classes for styling.
68m: styling details panel_borders
72m: Responsive layout -Using single breakpoint @media screen and (min-width: 720px) {}
77m: Making the homepage responsive. Make the logo a link that takes you back to the home page
84m: Test out that on smaller screens he hides Area and Gini Columns with media queries and display none.
86m: Count on the homepage of how many countries are in the search results.
90m: Theme Switcher. In global.css add a [data-theme=”dark”] {dark theme variables defined in here}. Next thing is to add a button to control the toggling. In In Layout component JSX under the Logo add a new button and inside those tags render a BrightnessRounded self-closing component from material ui. Add a className of {styles.themeSwitcher} to the button. Style to make it prettier.
Just inside main Layout function set a new piece of state const [theme, setTheme] = useState(‘light’)
create new constant named switchTheme and set to an arrow function that takes no parameters and inside the block say if (theme === “light”) {setTheme(“dark”)} else {setTheme(“light”)}
add a OnClick to the button and wire to {switchTheme}. Leverage useEffect that takes an arrow function which inside has document.documentElement.setAttribute(‘data-theme’, localStorage.getItem(“theme”)) and under this has setTheme(localStorage.getItem(“theme”)) and has a empty dependency array of.
Refactor this messy code by creating a new constant named saveTheme just above the return of JSX and this is set to an arrow function that takes the theme value and inside the block setTheme(theme); localStorage.setItem(“theme”, theme); document.documentElement.setAttribute(‘data-theme’, theme)
96m: Fix error of no key on map function
97m: Deployment. He then changes getServerSideProps to getStaticProps combined with getStaticPaths since there is a dynamic parameter
99m: GSSP and GSProps/GSPaths
103m: Finished