Skip to content

NabillaTrisnani/frontendmentor_rest-country

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This is my solution to Frontend Mentor REST Countries API with color theme switcher challenge.


Table of contents

Overview

The challenge is to integrate with the REST Countries API to pull country data and display it like in the designs.

The challenge

User should be able to:

  • See all countries from the API on the homepage
  • Search for a country using an input field
  • Filter countries by region
  • Click on a country to see more detailed information on a separate page
  • Click through to the border countries on the detail page
  • Toggle the color scheme between light and dark mode (optional)

Links

My process

Built with:

What I learned

  • GET data from API with React Hooks.
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);

useEffect(() => {
  getData();
}, [])

const getData = () => {
  fetch("https://restcountries.com/v3.1/all")
    .then(res => res.json())
    .then(
    (results) => {
      setIsLoaded(true);
      setItems(results);
      // console.log(results)
    }
  )
}
  • GET parameter with useParams().
import { useParams } from 'react-router-dom';

const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);

let { name } = useParams();

useEffect(() => {
  fetch(`https://restcountries.com/v3.1/alpha/${name}`)
    .then(res => res.json())
    .then(
    (results) => {
      setIsLoaded(true);
      setItems(results);
      console.log(results);
    }
  )
}, [name])
  • Filtering data.
const [items, setItems] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const [searchInput, setSearchInput] = useState("");
const [selectInput, setSelectInput] = useState("");
const regionList = ["Africa", "Americas", "Asia", "Europe", "Oceania"];

const handleChangeInput = (e) => {
  setSearchInput(e.target.value);
};
const handleChangeSelect = (e) => {
  setSelectInput(e.target.value);
};

useEffect(() => {
  const result = items.filter(
    (item) =>
      (!searchInput ||
        item.name.common.toLowerCase().includes(searchInput.toLowerCase())) &&
      (!selectInput || item.region === selectInput)
  );
  setFilteredItems(result);
  console.log(result);
}, [searchInput, items, selectInput]);

<input type="text" placeholder="Search for a country" disabled={isLoaded ? false : true} value={searchInput} onChange={handleChangeInput} />

<select className="form__select" onChange={handleChangeSelect}>
  <option value="">All</option>
  {regionList.map((region) => (
    <option value={region} key={region}>
      {region}
    </option>
  ))}
</select>

  • Handle theme with localStorage.
const [isActive, setActive] = useState("false");
localStorage.getItem('theme');

const handleToggle = () => {
  setActive(!isActive);
  if (isActive === true) {
    localStorage.setItem('theme', 'dark');
  } else {
    localStorage.setItem('theme', 'light');
  }
}
<div className={localStorage.getItem("theme") === "light" ? "App light-theme" : "App dark-theme"}>
  <Navbar onClick={handleToggle} />
  <Routes />
</div>

For theme style, I use SCSS nesting. If App have class light-theme then, children item such as card, input, and skeleton loading will have light background color and dark font, and if App have class dark-theme then, children item will have dark background and light font.

  • Make skeleton loading. For example I'm going to use card component.
const [isLoaded, setIsLoaded] = useState(false);

useEffect(() => {
  getData();
}, [])

const getData = () => {
  fetch("https://restcountries.com/v3.1/all")
    .then(res => res.json())
    .then(
    (results) => {
      // When results is loaded, we set isLoaded to true
      setIsLoaded(true);
      setItems(results);
      // console.log(results)
    }
  )
}

For the HTML, if isLoaded is false then add class skeleton to form__search and disabled input.

<div className={isLoaded ? 'form__search' : 'form__search skeleton' }>
  <ion-icon name="search"></ion-icon>
  <input type="text" placeholder="Search for a country" disabled={isLoaded ? false : true} value={searchInput} onChange={handleChangeInput} />
</div>

For styling I use SCSS nesting.

// variable
//color for dark theme
$dark-background: #202c37;
$dark-element: #2b3945;
$dark-text: #ffffff;

//color for light theme
$light-background: #fafafa;
$light-element: #ffffff;
$light-text: #111517;

//mixin
@mixin skeleton {
  content: "" !important;
  width: 50px;
  position: absolute;
  height: 100%;
  animation-name: gradient-animation;
  animation-duration: 2s;
  animation-iteration-count: infinite;
  filter: blur(5px);
}

@mixin skeleton-dark {
  background: $dark-element !important;

  &::after {
    background: linear-gradient(
      to right,
      $dark-element 25%,
      $dark-background 50%,
      $dark-element 100%
    );
  }
}

@mixin skeleton-light {
  background-color: #e2e2e2;

  &::after {
    background: linear-gradient(
      to right,
      #e2e2e2 25%,
      #d5d5d5 50%,
      #e2e2e2 100%
    );
  }
}
// input base

.form {
  &__search {
    display: flex;
    align-items: center;
    width: 480px;
    padding: 0 32px;
    border-radius: 8px;

    ion-icon {
      font-size: 18px;
      margin-right: 24px;
    }
    input {
      border: none;
      outline: none;
      padding: 18px 0;
      width: 100%;
      font-weight: 600;
      font-size: 14px;
      letter-spacing: -0.01em;
      background-color: transparent;
    }
  }
  &__search.skeleton {
    color: transparent;
    position: relative;
    overflow: hidden;
    border-radius: 0;

    ion-icon {
      color: transparent !important;
    }
    input {
      color: transparent !important;

      &::placeholder {
        color: transparent !important;
      }
    }
    &::after {
      @include skeleton;
      margin: 0 -32px;
    }
  }
}

@keyframes gradient-animation {
  from {
    left: 0%;
  }
  to {
    left: 100%;
  }
}
// dark theme

.dark-theme {
  .form {
    &__search {
      background: $dark-element;

      ion-icon {
        color: $dark-text;
      }
      input {
        color: $dark-text;

        &::placeholder {
          color: $dark-text;
        }
      }
    }
    &__search.skeleton {
      @include skeleton-dark;
    }
  }
}
// light theme

.light-theme {
  .form {
    &__search {
      background: $light-element;

      ion-icon {
        color: $light-text;
      }
      input {
        color: $light-text;

        &::placeholder {
          color: hsl(0, 0%, 52%);
        }
      }
    }
    &__search.skeleton {
      @include skeleton-light;
    }
  }
}

Author