Проблема REST API стран решена с помощью Chakra UI и React.


Вход в полноэкранный режим Выход из полноэкранного режима

Введение

В этой статье я расскажу, как я решил задачу Frontend mentor «REST Countries API с переключателем цветовой темы» с помощью Chakra UI и Create React App. В конце этого руководства мы должны быть в состоянии

  1. Видеть все страны из API на домашней странице

  2. Искать страну с помощью поля ввода

  3. Фильтровать страны по регионам

  4. Нажимать на страну, чтобы увидеть более подробную информацию на
    отдельная страница

  5. Переход к пограничным странам на странице подробной информации

  6. Переключение цветовой схемы между светлым и темным режимом
    (необязательно)

Необходимые условия

Чтобы следовать этому руководству, вы должны иметь базовое представление о следующем.

  1. Базовые знания синтаксиса и возможностей JavaScript ES6

  2. Основы терминологии ReactJS: JSX, State, Asynchronous
    JavaScript и т.д.

  3. Базовое понимание Restful API.

  4. Базовые знания TypeScript

  5. Базовое понимание Chakra UI

  6. Базовые знания React Router

Демонстрация и ссылки на Github

Решение на Github
Живой сайт

Разбивка по компонентам

  1. Компонент заголовка
  2. Главный компонент
  3. Одностраничный компонент

Установка

Перед сборкой каждого компонента мы начнем с создания нового проекта create-react-app из шаблона с использованием автоматического шаблона typescript Chakra UI, как показано в коде ниже.

# TypeScript using npm
npx create-react-app my-app --template @chakra-ui/typescript
Вход в полноэкранный режим Выйти из полноэкранного режима

Эта команда загрузит готовое к использованию приложение react.
После создания нашего приложения структура папок должна выглядеть так, как показано на рисунке ниже.

Теперь мы переходим в папку my-app и запускаем наше приложение, выполнив команду npm start. У нас должно получиться что-то похожее на изображение ниже.

Удаление ненужного CSS

Теперь, когда мы создали проект create-react-app с помощью шаблона, нам осталось только начать создавать наши компоненты и очистить файлы.
В папке src я создал две новые папки: pages и components.

Пакеты

В дополнение к нашим предустановленным пакетам из шаблонов create-react-app typescript, мы установим еще два дополнительных пакета, а именно

React-Router:

Он поможет нам с маршрутизацией внутри нашего приложения.

ChakraIcons:

Предоставит набор часто используемых иконок интерфейса, которые можно использовать в нашем проекте.

Теперь мы можем перейти в нашу корневую папку и запустить приведенный ниже код, чтобы установить оба пакета вместе.

npm install react-router-dom@6 @chakra-ui/icons

Вход в полноэкранный режим Выход из полноэкранного режима

Здесь мы используем шаблон Chakra UI Navbar с пользовательским выпадающим меню и переключателем тем Dark.

import {
  Box,
  Flex,
  Button,
  useColorModeValue,
  Stack,
  useColorMode,
} from '@chakra-ui/react';
import { MoonIcon, SunIcon } from '@chakra-ui/icons';
import { useNavigate } from 'react-router-dom';
export default function Nav() {
  const { colorMode, toggleColorMode } = useColorMode();
  let navigate = useNavigate();
  return (
    <>
      <Box bg={useColorModeValue('gray.100', 'gray.900')} px={4}>
        <Flex h={16} alignItems={'center'} justifyContent={'space-between'}>
          <Box onClick={()=> navigate('/')}  >Where in the world?</Box>
          <Flex alignItems={'center'}>
            <Stack direction={'row'} spacing={7}>
              <Button onClick={toggleColorMode}>
                {colorMode === 'light' ? <MoonIcon /> : <SunIcon />}
              </Button>
            </Stack>
          </Flex>
        </Flex>
      </Box>
    </>
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Для этого компонента мы используем ReactRouter для простой маршрутизации и chakra-UI/icons для доступа к иконкам Chakra UI.

Маршрутизация:

Здесь мы связываем наши компоненты с соответствующими страницами.

import React from 'react'
import {Route, Routes } from "react-router-dom";
import Home from '../Pages/Home';
import SingleCountry from '../Pages/SingleCountry';


function Routing() {
  return (
    <div>
        <Routes>
        <Route path="/" element={<Home/>} />
        <Route path="/singlecountry/:countryname" element={<SingleCountry/>} />
      </Routes> 
    </div>
  )
}

export default Routing
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, когда у нас есть наша панель навигации и компоненты маршрутизации, мы переходим к созданию файла домашней страницы

Главная страница:

// Importing 
import React from "react";
import { useState, useEffect } from "react";
import {
  Flex,
  GridItem,
  Image,
  Input,
  InputGroup,
  InputLeftElement,
  Select,
  SimpleGrid,
  Spacer,
} from "@chakra-ui/react";
import { Box } from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { Progress } from "@chakra-ui/react";
import { SearchIcon } from "@chakra-ui/icons";
import Nav from "../Components/Navlink";

function Home() {

  //States
  const [data, setData] = useState([]);
  const [data2, setData2] = useState([]);
  const [searchInput, setSearchInput] = useState("");
  const [selectInput, setSelectInput] = useState("all");
  let navigate = useNavigate();


//Calling Apis
  useEffect(() => {
    if (selectInput === "all") {
      fetch(`https://restcountries.com/v3.1/all`)
        .then((res) => res.json())
        .then((data) => {
          return (
            setData(data),
            setData2(data))
        })
        .catch((err) => console.log("Error:", err.message));
    } else {
      fetch(`https://restcountries.com/v3.1/region/${selectInput}`)
        .then((res) => res.json()).then((data)=>{
          return (
            setData(data), 
            setData2(data)
          )
        })
        .catch((err) => console.log("Error:", err.message));
    }
  }, [selectInput]);


  //Handle Region select
  const handleChangeSelect = (e) => {
    setSelectInput(e.target.value);
  };

  //Handle Country Search
  const handleChangeInput = (e) => {
    e.preventDefault();
    setSearchInput(e.target.value);
    setData(
      data2.filter((x) =>
        x?.name?.common
          ?.toLowerCase()
          ?.includes(e?.target?.value?.toLowerCase())
      )
    );
  };

  return (
    <div>
      {/* Navbar */}
      <Nav/>
{/* 
    Country Search and Region Select form */}
      <form>
        <Flex pr="50" pl="50" flexWrap={"wrap"}>
          <Box p="4">
            <InputGroup>
              <InputLeftElement
                pointerEvents="none"
                children={<SearchIcon color="gray.300" />}
              />
              <Input
                value={searchInput}
                onChange={handleChangeInput}
                type="text"
                placeholder="Search for a country "
              />
            </InputGroup>
          </Box>
          <Spacer />
          <Box p="4">
            <Select onChange={handleChangeSelect} placeholder="Select option">
              <option value="all">All</option>
              <option value="africa">Africa</option>
              <option value="americas">Americas</option>
              <option value="asia">Asia</option>
              <option value="europe">Europe</option>
              <option value="oceania">Oceania</option>
            </Select>
          </Box>
        </Flex>
      </form>

      {/* Data Rendering */}

      {data2?.length === 0 ? (
        <Progress colorScheme="pink" size="xs" isIndeterminate />
      ) : (
        <Box w="100%">
          <SimpleGrid
            columns={[1, null, 4]}
            spacing={10}
            pt="100"
            pr="50"
            pl="50"
          >
            {data?.map((x) => (
              <GridItem
                key={x?.name?.common}
                onClick={() =>
                  navigate(`/singlecountry/${x?.cca2?.toLowerCase()}`, {})
                }
              >
                <Box
                  maxW="sm"
                  borderWidth="1px"
                  borderRadius="lg"
                  overflow="hidden"
                >
                  <Image
                    src={x?.flags?.svg}
                    alt={x?.name?.common}
                    height="200px"
                    width="100%"
                  />
                  <Box p="6">
                    <Box
                      mt="1"
                      fontWeight="semibold"
                      as="h4"
                      lineHeight="tight"
                      noOfLines={1}
                    >
                      {x?.name?.common}
                    </Box>

                    <Box
                      mt="1"
                      fontWeight="semibold"
                      as="h4"
                      lineHeight="tight"
                      noOfLines={1}
                    >
                      Population: {x?.population}
                    </Box>

                    <Box
                      mt="1"
                      fontWeight="semibold"
                      as="h4"
                      lineHeight="tight"
                      noOfLines={1}
                    >
                      Region: {x?.region}
                    </Box>

                    <Box
                      mt="1"
                      fontWeight="semibold"
                      as="h4"
                      lineHeight="tight"
                      noOfLines={1}
                    >
                      Capital: {x?.capital}
                    </Box>
                  </Box>
                </Box>
              </GridItem>
            ))}
          </SimpleGrid>
        </Box>
      )}
    </div>
  );
}

export default Home;

Войти в полноэкранный режим Выход из полноэкранного режима

Как вы можете видеть, здесь много строк кода, которые нужно осмыслить сразу, поэтому давайте делать по шагу за раз.

Импортируем все необходимые компоненты

Здесь мы импортируем Flex, grid, item, Image, Input, InputGroup, InputLeftElement, Select, SimpleGrid, Spacer, Box, Progress из Chakra UI, используем navigate из react-router, SearchIcon из ChakraIcons и Nav из нашего компонента Navlinks.

// Importing 
import React from "react";
import { useState, useEffect } from "react";
import {
  Flex,
  GridItem,
  Image,
  Input,
  InputGroup,
  InputLeftElement,
  Select,
  SimpleGrid,
  Spacer,
  Box,
  Progress
} from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { SearchIcon } from "@chakra-ui/icons";
import Nav from "../Components/Navlink";
Войдите в полноэкранный режим Выход из полноэкранного режима

Состояния:

Здесь мы объявили наши состояния

  //States
  const [data, setData] = useState([]);
  const [data2, setData2] = useState([]);
  const [searchInput, setSearchInput] = useState("");
  const [selectInput, setSelectInput] = useState("all");
  let navigate = useNavigate();
Войти в полноэкранный режим Выход из полноэкранного режима

Api:

Вызов Api


//Calling Apis
  useEffect(() => {
    if (selectInput === "all") {
      fetch(`https://restcountries.com/v3.1/all`)
        .then((res) => res.json())
        .then((data) => {
          return (
            setData(data),
            setData2(data))
        })
        .catch((err) => console.log("Error:", err.message));
    } else {
      fetch(`https://restcountries.com/v3.1/region/${selectInput}`)
        .then((res) => res.json()).then((data)=>{
          return (
            setData(data), 
            setData2(data)
          )
        })
        .catch((err) => console.log("Error:", err.message));
    }
  }, [selectInput]);

Вход в полноэкранный режим Выход из полноэкранного режима

Выбор региона и функции поиска страны

  //Handle Region select
  const handleChangeSelect = (e) => {
    setSelectInput(e.target.value);
  };

  //Handle Country Search
  const handleChangeInput = (e) => {
    e.preventDefault();
    setSearchInput(e.target.value);
    setData(
      data2.filter((x) =>
        x?.name?.common
          ?.toLowerCase()
          ?.includes(e?.target?.value?.toLowerCase())
      )
    );
  };

Вход в полноэкранный режим Выход из полноэкранного режима

Импортная панель навигации


      <Nav/>

    Country Search and Region Select form 
      <form>
        <Flex pr="50" pl="50" flexWrap={"wrap"}>
          <Box p="4">
            <InputGroup>
              <InputLeftElement
                pointerEvents="none"
                children={<SearchIcon color="gray.300" />}
              />
              <Input
                value={searchInput}
                onChange={handleChangeInput}
                type="text"
                placeholder="Search for a country "
              />
            </InputGroup>
          </Box>
          <Spacer />
          <Box p="4">
            <Select onChange={handleChangeSelect} placeholder="Select option">
              <option value="all">All</option>
              <option value="africa">Africa</option>
              <option value="americas">Americas</option>
              <option value="asia">Asia</option>
              <option value="europe">Europe</option>
              <option value="oceania">Oceania</option>
            </Select>
          </Box>
        </Flex>
      </form>
Вход в полноэкранный режим Выход из полноэкранного режима

Рендеринг данных


 {data2?.length === 0 ? (
        <Progress colorScheme="pink" size="xs" isIndeterminate />
      ) : (
        <Box w="100%">
          <SimpleGrid
            columns={[1, null, 4]}
            spacing={10}
            pt="100"
            pr="50"
            pl="50"
          >
            {data?.map((x) => (
              <GridItem
                key={x?.name?.common}
                onClick={() =>
                  navigate(`/singlecountry/${x?.cca2?.toLowerCase()}`, {})
                }
              >
                <Box
                  maxW="sm"
                  borderWidth="1px"
                  borderRadius="lg"
                  overflow="hidden"
                >
                  <Image
                    src={x?.flags?.svg}
                    alt={x?.name?.common}
                    height="200px"
                    width="100%"
                  />
                  <Box p="6">
                    <Box
                      mt="1"
                      fontWeight="semibold"
                      as="h4"
                      lineHeight="tight"
                      noOfLines={1}
                    >
                      {x?.name?.common}
                    </Box>

                    <Box
                      mt="1"
                      fontWeight="semibold"
                      as="h4"
                      lineHeight="tight"
                      noOfLines={1}
                    >
                      Population: {x?.population}
                    </Box>

                    <Box
                      mt="1"
                      fontWeight="semibold"
                      as="h4"
                      lineHeight="tight"
                      noOfLines={1}
                    >
                      Region: {x?.region}
                    </Box>

                    <Box
                      mt="1"
                      fontWeight="semibold"
                      as="h4"
                      lineHeight="tight"
                      noOfLines={1}
                    >
                      Capital: {x?.capital}
                    </Box>
                  </Box>
                </Box>
              </GridItem>
            ))}
          </SimpleGrid>
        </Box>
      )}

Войти в полноэкранный режим Выйти из полноэкранного режима

Компонент одной страны

Теперь мы создадим компонент одной страны.

Когда пользователь нажимает на страну, на отдельной странице открывается подробная информация о ней. Все, что мы здесь делаем, — это устанавливаем состояние и затем обновляем состояние с помощью ответа, полученного от API внутри use effect. Затем мы отображаем состояние страны в компонентах Chakra.

import React, { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import {
  Button,
  Center,
  GridItem,
  Image,
  Progress,
  SimpleGrid,
} from "@chakra-ui/react";
import { Box } from "@chakra-ui/react";
import Nav from "../Components/Navlink";


function SingleCountry() {
  let { countryname } = useParams();
  const [data, setData] = useState();
  let navigate = useNavigate();
  useEffect(() => {
    fetch(`https://restcountries.com/v3.1/alpha/${countryname}`)
      .then((res) => res.json())
      .then((data) => setData(data))
      .catch((err) => console.log("Error:", err.message));
  }, [countryname]);
  return (
    <div>
      <Nav />

      <Box onClick={() => navigate(-1)} p={'10'}  >
        <Button size="lg" variant="solid" mr="3">
          Back
        </Button>
      </Box>

      {data === undefined || data === null ? (
        <Progress colorScheme="pink" size="xs" isIndeterminate />
      ) : (
        data?.map((x) => {
          return (
            <Center key={x?.name?.common} >
              <SimpleGrid
                columns={[1, null, 2]}
                spacing={100}
                pt="100"
                pr="50"
                pl="50"
              >
                <GridItem w="100%">
                  <Image src={x?.flags?.svg} alt={x?.Region} height="350" />
                </GridItem>
                <GridItem w="100%">
                  <Box
                    mt="1"
                    fontWeight="semibold"
                    as="h4"
                    lineHeight="tight"
                    noOfLines={1}
                  >
                    {x?.name?.common}
                  </Box>

                  <SimpleGrid columns={2} spacing={10}>
                    <Box>Native Name: {x?.name?.common}</Box>
                    <Box>Top Level Domain: {x?.tld[0]}</Box>
                  </SimpleGrid>

                  <SimpleGrid columns={2} spacing={10}>
                    <Box>Population: {x?.population}</Box>
                    <Box>
                      Currencies:{" "}
                      {x?.currencies[Object?.keys(x?.currencies)[0]]?.name}
                    </Box>
                  </SimpleGrid>

                  <SimpleGrid columns={2} spacing={10}>
                    <Box>Region: {x?.region}</Box>
                    <Box>
                      Language(s): {x?.languages[Object.keys(x?.languages)[0]]}
                    </Box>
                  </SimpleGrid>

                  <SimpleGrid columns={2} spacing={10}>
                    <Box>Subregion: {x?.subregion}</Box>
                  </SimpleGrid>

                  <SimpleGrid columns={2} spacing={10}>
                    <Box>Capital: {x?.capital}</Box>
                  </SimpleGrid>

                  <SimpleGrid mt="50" columns={2} spacing={10}>
                    <Box>Border Countries:</Box>

                    <Box>
                      {x?.borders?.map((x) => (
                        <Button
                          onClick={() => navigate(`/singlecountry/${x}`)}
                          size="lg"
                          key={x}
                          variant="solid"
                          mr="3"
                        >
                          {x}
                        </Button>
                      ))}
                    </Box>
                  </SimpleGrid>
                </GridItem>
              </SimpleGrid>
            </Center>
          );
        })
      )}
    </div>
  );
}

export default SingleCountry;
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте создадим сердце этого проекта.

App.tsx

import * as React from "react"
import { ChakraProvider, theme } from '@chakra-ui/react'

import { BrowserRouter } from "react-router-dom";
import Routing from "./Components/Routing";

export const App = () => (
 <div>
<ChakraProvider theme={theme}>
<BrowserRouter>
<Routing/>
</BrowserRouter>
</ChakraProvider>

 </div>
)
Вход в полноэкранный режим Выйти из полноэкранного режима

Создав все наши компоненты, давайте войдем в каталог нашего приложения и запустим npm start, чтобы запустить приложение. В этот момент мы должны увидеть что-то похожее на изображение ниже.

Если мы нажмем на любую из стран, она должна перенаправить нас на другую страницу, где мы сможем увидеть более подробную информацию о стране, как показано на рисунке ниже.

Что ж, поздравляем вас с этим замечательным успехом! В вашем распоряжении готовое решение для REST API стран с переключателем цветовых тем.

Спасибо за прочтение🌟🎉

Рад видеть, что вам понравилась статья. Пожалуйста, дайте мне знать, что вы думаете в разделе комментариев.

В другой блог, в другой день, до тех пор Femi👋.

Оцените статью
devanswers.ru
Добавить комментарий