ReactJS/MUI (v5.6.2) Dark mode toggle not updating state - javascript

I am trying to wrap my mind around this and find the breaking point for all morning but am not able to. I have only followed the official docs until now.
I need to update the theme from dark to light in a React App but the state wont change (or resets immediately)
Heres my code:
App.tsx
import React, { createContext, useMemo, useState } from 'react';
import styles from './App.module.scss';
import {createTheme,CssBaseline,PaletteMode,responsiveFontSizes,ThemeProvider,
useMediaQuery} from '#mui/material';
import { getDefaultThemeMode } from './shared/theme/default-theme-mode';
import { getDesignTokens } from './shared/theme/mui-design-tokens';
import Main from './main/Main';
import { Navigate, Route, Routes } from 'react-router-dom';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { OPTIONS } from './shared/i18n/translations';
export const ColorModeContext = React.createContext({
toggleColorMode: () => { },
});
function App() {
// THEME
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const [mode, setMode] = React.useState<'light' | 'dark'>('light');
React.useEffect(() => {
setMode(prefersDarkMode ? 'dark' : 'light');
}, [prefersDarkMode]);
const colorMode = React.useMemo(
() => ({
toggleColorMode: () => {
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
},
}),
[]
);
let theme = React.useMemo(
() =>
createTheme(getDesignTokens(mode)),
[mode]
);
theme = responsiveFontSizes(theme);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<div className={styles.app} style={{background: theme.palette.background.default}}>
<CssBaseline/>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/>
<Routes>
<Route path='/home' element={<Main/>}/>
<Route path='*' element={<Navigate to='home'/>}/>
</Routes>
</div>
</ThemeProvider>
</ColorModeContext.Provider>
);
}
export default App;
getDesignTokens Function:
const getDesignTokens = (mode: PaletteMode): ThemeOptions => ({
palette: {
mode,
...(mode === 'light' ? {
primary: {
main: '#3c3c3c',
},
} : {
primary: {
main: '#3c3c3c',
},
})
}});
And finally, I am trying to update the theme from outside the App.tsx component this way:
const ThemeToggle = () => {
const theme = useTheme();
const colorMode = useContext(ColorModeContext);
return (
<>
<label htmlFor='theme-toggle' className={`${styles.toggleBtn}
${theme.palette.mode === 'dark' ? styles.dark : ''}`}
onClick={colorMode.toggleColorMode}>
<input type='checkbox' id='theme-toggle' className={styles.input}/>
</label>
</>
)
}
export default ThemeToggle;
I must be missing something obvious but have tried everything. Anyone has done something similar?

Related

Updated component based on state from other component

I have a hamburger component that has no className prop, only color, and using the ternary operator its color changes only after page reload, but for example FontAwesomeIcon using dark:text-gray-50 changes its color.
This is my TodoHeader.jsx component
import Search from "./Search";
import Switcher from "../Switcher";
import {faBars} from "#fortawesome/free-solid-svg-icons";
import {FontAwesomeIcon} from "#fortawesome/react-fontawesome";
import {Fade as Hamburger} from "hamburger-react";
import {useProSidebar} from "react-pro-sidebar";
import useDarkSide from "../../hooks/useDarkSide";
const TodoHeader = () => {
const {collapseSidebar} = useProSidebar();
const [colorTheme, setTheme] = useDarkSide();
const toggle = () => {
collapseSidebar()
}
return (
<div className={"flex items-center justify-between border-b-2 dark:border-gray-600"}>
<div className={"flex items-center ml-8"}>
<Hamburger direction={"right"} onToggle={toggle} color={ colorTheme === 'light' ? "#f3f4f6" : "#1f2937"}/>
<FontAwesomeIcon icon={faBars} onClick={toggle} className={"text-center dark:text-gray-50"}/>
<h1 className={"p-4 text-2xl text-gray-800 dark:text-gray-100"}>Todo List</h1>
</div>
<Search/>
<Switcher/>
</div>
);
};
export default TodoHeader;
This is my customhook
import {useEffect, useMemo, useState} from "react";
const useDarkSide = () => {
const [theme, setTheme] = useState(localStorage.theme);
const colorTheme = theme === "dark" ? "light" : "dark";
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove(colorTheme);
root.classList.add(theme);
localStorage.setItem('theme', theme);
}, [theme, colorTheme]);
return [colorTheme, setTheme]
}
export default useDarkSide
And this is Switcher.jsx component that handles dark/light mode changes
import {useState} from "react";
import { DarkModeSwitch} from "react-toggle-dark-mode";
import useDarkSide from "../hooks/useDarkSide";
const Switcher = () => {
const [colorTheme, setTheme] = useDarkSide();
const [darkSide, setDarkSide] = useState(
colorTheme === "light" ? true : false
);
const toggleDarkMode = (checked) => {
setTheme(colorTheme);
setDarkSide(checked);
};
return (
<>
<DarkModeSwitch
style={{ marginRight: '2rem' }}
checked={darkSide}
onChange={toggleDarkMode}
size={30}
/>
</>
);
}
export default Switcher
I think the problem is that when I use the useDarkSide hook on the header it doesn't render the header component, but I don't know how to make it work
So, the problem was with me trying to access theme state that was inside switcher component through useDarkSide custom hook, but it's create another separated state. So the solution was using global state, in my case i used redux.

Issue with React state-management

I am using the Google Books API to retrieve book details in my application. I have two routes: one with a form and the search results, and the other with the full details of each search result. Currently,
I have a link on the details page that takes me back to the form page, but I want to retain the state of the form page and display the search results when I click the link instead of just the form. How can I do this?
You can check my code below.
BookDetails.jsx
import { useParams } from "react-router-dom";
import fetchBookDetails from "../hooks/fetchBookDetails";
import { useQuery } from "#tanstack/react-query";
import { Link } from "react-router-dom";
import notFoundImage from "../assets/react.svg";
import Header from "./Header";
const BookDetails = () => {
const { id } = useParams();
const results = useQuery(["book", id], fetchBookDetails);
if (results.isLoading) {
return <div>Loading...</div>;
}
if (results.isError) {
return <div>Book with id {id} not found.</div>;
}
if (results.data) {
return (
<div>
<Header />
<Link to="/">
<button className="back">Back</button>
</Link>
<div className="book-details">
<img
src={
results.data.volumeInfo.imageLinks
? results.data.volumeInfo.imageLinks.thumbnail
: notFoundImage
}
alt={results.data.volumeInfo.title}
/>
<h1>{results.data.volumeInfo.title}</h1>
<p>{results.data.volumeInfo.authors}</p>
</div>
</div>
);
}
};
export default BookDetails;
SearchForm.jsx
const SearchForm = ({ onSubmit }) => {
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(e.target.elements.book.value);
};
return (
<>
{" "}
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="book">
Book Name:
<input type="text" name="book" />
</label>
<button type="submit">Submit</button>
</form>
</div>
</>
);
};
export default SearchForm;
Seachresult.jsx
import React from "react";
import { Link } from "react-router-dom";
import notFoundImage from "../assets/react.svg";
const SearchResults = ({ results }) => {
return results.isLoading ? (
<div>Loading...</div>
) : results.isError ? (
<div>{results.error.message}</div>
) : (
<div className="books">
{results.data.items.map((item, i) => {
return (
<Link to={`/book/${item.id}`} key={item.id}>
<div key={item.id} className="book border-2-red-500">
<p>{i + 1}</p>
<img
src={
item.volumeInfo.imageLinks
? item.volumeInfo.imageLinks.thumbnail
: notFoundImage
}
alt={item.volumeInfo.title}
/>
<h3>{item.volumeInfo.title}</h3>
<p>{item.volumeInfo.authors}</p>
</div>
</Link>
);
})}
</div>
);
};
export default SearchResults;
Home.jsx
import { useState } from "react";
import { useQuery } from "#tanstack/react-query";
import fetchBooks from "../hooks/fetchBooks";
import SearchResults from "./SearchResults";
import SearchForm from "./SearchForm";
import Header from "./Header";
const Home = () => {
const [book, setBook] = useState(null);
const results = useQuery(["search", book], fetchBooks);
return (
<main>
<Header />
<SearchForm onSubmit={setBook} />
<SearchResults results={results} />
</main>
);
};
export default Home;
And lastly, App.jsx
import "./App.css";
import { QueryClient, QueryClientProvider } from "#tanstack/react-query";
import Home from "./components/Home";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import BookDetails from "./components/BookDetails";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
cacheTime: Infinity,
},
},
});
function App() {
return (
<BrowserRouter>
<QueryClientProvider client={queryClient}>
<Routes>
<Route path="/book/:id" element={<BookDetails />} />
<Route path="/" element={<Home />} />
</Routes>
</QueryClientProvider>
</BrowserRouter>
);
}
export default App;
You can use React Context API, create a Context that wraps the components you want to grant access to that states, and then you can manage the state in a "global" way in order to keep it intact when going back and forth.
You can check the example bellow:
import React, { useState, createContext, useCallback, useContext } from 'react';
import { uuid } from 'uuidv4';
import ToastContainer from '../components/ToastContainer';
interface ToastContextData {
addToast(messages: Omit<ToastMessage, 'id'>): void;
}
export interface ToastMessage {
id: string;
type?: 'success' | 'error' | 'info';
title: string;
description: string;
}
const ToastContext = createContext<ToastContextData>({} as ToastContextData);
const ToastProvider: React.FC = ({ children }) => {
const [messages, setMessages] = useState<ToastMessage[]>([]);
const addToast = useCallback(
({ type, title, description }: Omit<ToastMessage, 'id'>) => {
const id = uuid();
const toast = {
id,
type,
title,
description,
};
setMessages((state) => [...state, toast]);
},
[]
);
}, []);
return (
<ToastContext.Provider value={{ addToast }}>
{children}
<ToastContainer messages={messages} />
</ToastContext.Provider>
);
};
function useToast(): ToastContextData {
const context = useContext(ToastContext);
if (!context) {
throw new Error('useToast must be used within a ToastProvider');
}
return context;
}
export { ToastProvider, useToast };

React search filter form

I have been trying to set a search filter form. I am getting data from API (an array of cake objects with "id", "cake_name", "category" etc properties), these get displayed properly. But somehow my search function is not working? It should allow the user to input a name of a cake which then would be filtered through the cakes available and only the searched one(s) would be displayed.
I am getting this error:
error
Here is my code:
context.js:
import React, { useState, useContext, useEffect } from "react";
import { useCallback } from "react";
const url = "https://cakeaddicts-api.herokuapp.com/cakes";
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [loading, setLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const [cakes, setCakes] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const fetchCakes = async () => {
setLoading(true);
try {
const response = await fetch(url);
const cakes = await response.json();
if (cakes) {
const newCakes = cakes.map((cake) => {
const {
id,
image,
cake_name,
category,
type,
ingredients,
instructions,
} = cake;
return {
id,
image,
cake_name,
category,
type,
ingredients,
instructions,
};
});
setCakes(newCakes);
console.log(newCakes);
} else {
setCakes([]);
}
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
}
};
useEffect(() => {
fetchCakes();
}, []);
return (
<AppContext.Provider
value={{
loading,
cakes,
setSearchTerm,
searchTerm,
filteredData,
setFilteredData,
}}
>
{children}
</AppContext.Provider>
);
};
// make sure use
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
SearchForm.js
import React from "react";
import { useGlobalContext } from "../context";
import CakeList from "./CakeList";
const SearchForm = () => {
const { cakes, setSearchTerm, searchTerm, setFilteredData } =
useGlobalContext;
const searchCakes = () => {
if (searchTerm !== "") {
const filteredData = cakes.filter((item) => {
return Object.values(item)
.join("")
.toLowerCase()
.includes(searchTerm.toLowerCase());
});
setFilteredData(filteredData);
} else {
setFilteredData(cakes);
}
};
return (
<section className="section search">
<form className="search-form">
<div className="form-control">
<label htmlFor="name">Search Your Favourite Cake</label>
<input
type="text"
id="name"
onChange={(e) => searchCakes(e.target.value)}
/>
</div>
</form>
</section>
);
};
export default SearchForm;
CakeList.js:
import React from "react";
import Cake from "./Cake";
import Loading from "./Loading";
import { useGlobalContext } from "../context.js";
const CakeList = () => {
const { cakes, loading, searchTerm, filteredResults } = useGlobalContext();
if (loading) {
return <Loading />;
}
return (
<section className="section">
<h2 className="section-title">Cakes</h2>
<div className="cakes-center">
{searchTerm.length > 1
? filteredResults.map((cake) => {
return <Cake key={cake.id} {...cake} />;
})
: cakes.map((item) => {
return <Cake key={item.id} {...item} />;
})}
</div>
</section>
);
};
export default CakeList;
App.js:
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
// import pages
import Home from "./pages/Home";
import About from "./pages/About";
import SingleCake from "./pages/SingleCake";
import Error from "./pages/Error";
// import components
import Navbar from "./components/Navbar";
function App() {
return (
<Router>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/cake/:id" element={<SingleCake />} />
<Route path="*" element={<Error />} />
</Routes>
</Router>
);
}
export default App;
Can someone please help me with this search form? I have tried so many things and nothing is working :( Anyone?
On line 11 of SearchForm.js, there is a part that reads cakes.filter(. To resolve the typeError, change this to cakes?.filter(. This will only execute the filter if cakes is defined. It's a feature in javascript called Optional Chaining, introduced in ES2020.
Learn about it more here

useContext() second argument is reserved for future use in React. Passing it is not supported

I have a lot of globally declared states in my application. I used useContext for implementing it, however, I keep getting a bunch of errors in the console regarding useContext.
Error:
Warning: useContext() second argument is reserved for future use in React. Passing it is not supported. You passed: false.
in Navbar (at Home.js:11)
in Home (created by Context.Consumer)
in Route (at App.js:96)
in Switch (at App.js:76)
in Router (created by BrowserRouter)
in BrowserRouter (at App.js:74)
in App (at src/index.js:7)
in StrictMode (at src/index.js:6)
App.js:
import React, { useState, useEffect } from 'react';
import { Redirect } from 'react-router-dom';
import './App.css';
import Home from '../pages/Home';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Restaurants from '../pages/Restaurants';
import ScrollToTop from '../components/ScrollToTop';
import UserAccount from '../pages/UserAccount'
import Footer from '../components/footer/Footer';
import { UserContext } from '../UserContext';
function App() {
const [clickedDistrict, setClickedDistrict] = useState(false);
const [clickedSuggestion, setClickedSuggestion] = useState(false);
const [checkedDistance, setCheckedDistance] = useState("1000")
const [restaurants, setRestaurants] = useState([]);
const [clickedUserMenuItem, setClickedUserMenuItem]
= useState("saved")
const [goodPassword, setGoodPassword] = useState(false)
const [chosenRestaurant, setChosenRestaurant] = useState(false);
const [generalSearchPath, setGeneralSearchPath] = useState(false);
const [incorrectPassword, setIncorrectPassword] = useState(false);
const [successfullLogin, setSuccessfullLogin] = useState(false)
const [username, setUsername] = useState(false)
const [incorrectOldPassword, setIncorrectOldPassword] = useState(false)
const [logout, setLogout] = useState(false);
const [newUsername, setNewUsername] = useState(false)
const [incorrectPasswordOnDelete, setIncorrectPasswordOnDelete] = useState(false)
const [deleteAccount, setDeleteAccount ] = useState(false)
return (
<>
<Router>
<ScrollToTop />
<Switch>
<UserContext.Provider value={{
clickedDistrict, setClickedDistrict,
clickedSuggestion, setClickedSuggestion,
checkedDistance, setCheckedDistance,
restaurants, setRestaurants,
clickedUserMenuItem, setClickedUserMenuItem,
goodPassword, setGoodPassword,
chosenRestaurant, setChosenRestaurant,
generalSearchPath, setGeneralSearchPath,
incorrectPassword, setIncorrectPassword,
successfullLogin, setSuccessfullLogin,
username, setUsername,
incorrectOldPassword, setIncorrectOldPassword,
logout, setLogout,
newUsername, setNewUsername,
incorrectPasswordOnDelete, setIncorrectPasswordOnDelete,
deleteAccount, setDeleteAccount
}}>
<Route path='/' exact component={Home} />
<Route path='/restaurants' component={Restaurants} />
{ (successfullLogin && !logout && !deleteAccount) ?
<Route path='/user' component={UserAccount} />
:
<>
<Route path='/user' component={UserAccount} />
<Redirect to='/' /></>
}
</UserContext.Provider>
</Switch>
<Footer />
</Router>
</>
);
}
export default App;
UserContext.js:
import { createContext } from "react";
export const UserContext = createContext();
I tried searching about this warning but nothing helped. I don't even understand the reason for this error. Which second argument does it imply?
Also, the weird thing is that I have theese states used throughout the application, but the error shows that I have a problem in the navbar on the Home page.
Home:
import React from 'react';
import Cards from '../components/cards/Cards';
import HeroSection from '../components/hero/HeroSection';
import Districts from '../components/districts/Districts';
import Navbar from '../components/navbar/Navbar';
import CollegeSection from '../components/college/CollegeSection';
function Home() {
return (
<>
<Navbar />
<HeroSection />
<Cards />
<CollegeSection />
<Districts />
</>
);
}
export default Home;
import React, { useEffect, useContext } from 'react';
import { Button } from '../button/Button';
import MobileNavbar from './MobileNavbar'
import { Link } from 'react-router-dom';
import './Navbar.css';
import { Modal } from '../forms/Modal'
import Searchbox from '../search/Searchbox';
import NavbarLogic from './NavbarLogic';
import { UserContext } from '../../UserContext';
import UserNavbar from '../user/UserNavbar';
function Navbar() {
const { click, button, showButton,
handleClick, closeMenuDiscardChanges, closeMenuOpenPCRestaurants }
= MobileNavbar();
const { navMenuClassName, searchbox, showLogInModal,
showSignUpModal, openLogInModal, openSignUpModal,
setShowLogInModal, setShowSignUpModal }
= NavbarLogic();
const { successfullLogin, setSuccessfullLogin, logout }
= useContext(UserContext);
useEffect(() => {
showButton();
}, [showButton]);
useEffect(() => {
if (logout) {
setShowLogInModal(false);
setSuccessfullLogin(false)
}
}, [logout,setSuccessfullLogin,setShowLogInModal])
window.addEventListener('resize', showButton);
return (
<>
<nav className={click ? 'navbar active' : 'navbar'}>
<div className='navbar-container'>
<Link to='/' className='navbar-logo' onClick={closeMenuDiscardChanges}>
Restaurateur<i class="fas fa-utensils" />
</Link>
<div className={click ? 'hidden' : searchbox}>
<Searchbox />
</div>
<div className='menu-icon' onClick={handleClick}>
<i className={click ? 'fas fa-times' : 'fas fa-bars'} />
</div>
<ul className={click ? 'nav-menu active' : navMenuClassName}>
<li className='nav-item'>
<Link to='/' className='nav-links' onClick={closeMenuDiscardChanges}>
Home
</Link>
</li>
<li className='nav-item'>
<Link
to='/restaurants'
className='nav-links'
onClick={setRestaurantsNavLink() === "All Restaurants" ?
closeMenuDiscardChanges
:
closeMenuOpenPCRestaurants}
>
{setRestaurantsNavLink()}
</Link>
</li>
<li>
<Link
className='nav-links-mobile'
onClick={openLogInModal}
>
Log In
</Link>
<Link
className='nav-links-mobile'
onClick={openSignUpModal}
>
Sign Up
</Link>
</li>
</ul>
{successfullLogin === true ?
<UserNavbar />
:
<>
{button &&
<Button
buttonStyle='btn--outline'
buttonSize='btn--medium'
onClick={openLogInModal}
id="login">
LOG IN
</Button>}
<Modal
showLogInModal={showLogInModal}
setShowLogInModal={setShowLogInModal}
/>
{button &&
<Button
id="signup"
buttonSize='btn--medium'
onClick={openSignUpModal}>
SIGN UP
</Button>}
<Modal
showSignUpModal={showSignUpModal}
setShowSignUpModal={setShowSignUpModal}
/>
</>}
</div>
</nav>
</>
);
}
export default Navbar;
How can I get rid of this useContext warning?
Edit:
This is how I use useContext in Mobilenavbar:
import { useState, useContext } from 'react';
import { UserContext } from "../../UserContext"
const MobileNavbar = () => {
const [click, setClick] = useState(false);
const [button, setButton] = useState(true);
const { setPragueCollegePath } = useContext(UserContext)
const { setClickedDistrict, setClickedSuggestion } = useContext(UserContext)
const { setChosenRestaurant, setGeneralSearchPath } = useContext(UserContext);
const handleClick = () => setClick(!click);
const closeMenuOpenRestaurants = () => {
setClick(false);
setClickedDistrict(false);
setClickedSuggestion(false);
setChosenRestaurant(false);
setGeneralSearchPath(false)
}
const closeMenuOpenPCRestaurants = () => {
setClick(false);
setClickedDistrict(false);
setClickedSuggestion(false);
setChosenRestaurant(false);
setGeneralSearchPath(false);
}
const closeMenuDiscardChanges = () => {
setClick(false);
setClickedDistrict(false)
setClickedSuggestion(false)
setChosenRestaurant(false)
setGeneralSearchPath(false);
}
const showButton = () => {
if (window.innerWidth <= 1120) {
setButton(false);
} else {
setButton(true);
}
};
const showSearch = () => {
if (window.innerWidth <= 820) {
setButton(false);
} else {
setButton(true);
}
};
return {
click, button, showButton, handleClick,
showSearch, closeMenuOpenRestaurants,
closeMenuOpenPCRestaurants, closeMenuDiscardChanges
}
}
export default MobileNavbar;
And here is NavbarLogic:
const {successfullLogin, setSuccessfullLogin, logout }
= useContext(UserContext);
I usually import all the variables together.

React-router not rendering dynamic component- When clicked nothing happens

I am doing a small project and have a list of components that display information about countries. Now I have added react router so that when I click on a card it displays more information about that country. Now when I click on the card nothing happens! Below is the code for the Countries.
import React, { Component } from 'react';
import { CountryList } from './Components/Card-List/CountryList';
import { SearchBox } from './Components/Search-box/Search-Box';
import './Countries.styles.css';
import { DetailCountryCard } from './Components/DetailCountryCard/DetailCountryCard';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
class Countries extends Component {
constructor() {
super();
this.state = {
countries:[],
searchField:"",
regionField:"",
darkMode: false
}
this.setDarkMode = this.setDarkMode.bind(this);
};
componentDidMount() {
fetch("https://restcountries.eu/rest/v2/all")
.then(response => response.json())
.then(all => this.setState({ countries: all,
regions: all}))
.catch(error => console.log("I have errored" + error));
}
setDarkMode(e){
this.setState((prevState) => ({ darkMode: !prevState.darkMode }));
}
render() {
const { countries, searchField, regionField, darkMode } = this.state;
const filterCountries = countries.filter((country) => country.name.toLowerCase().includes(searchField.toLowerCase()) &&
country.region.toLowerCase().includes(regionField.toLowerCase()));
return(
<Router>
<div className={darkMode ? "dark-mode" : "light-mode" }>
<nav className="navbar-items">
<h1 className="header">Where in the World</h1>
<div className="moon-end">
<button onClick={this.setDarkMode}>
<i className={darkMode ? "moon fas fa-moon" : "moon far fa-moon" }></i>
</button>
<h2>{darkMode ? "Dark Mode" : "Light Mode" }</h2>
</div>
</nav>
<div className="Input">
< SearchBox type="search" placeholder="Search a Country" handlechange={e=> this.setState({
searchField: e.target.value })}
/>
< SearchBox type="regions" placeholder="Filter by Regions" handlechange={e=> this.setState({
regionField: e.target.value })}
/>
</div>
<CountryList countries={filterCountries} />
{/* <Route path="/" exact component={Countries} /> */}
<Switch>
<Route path="/card-detail/:name" component={ DetailCountryCard } exact/>
</Switch>
</div>
</Router>
);
}
}
export default Countries
The link for each card is in the following component:
import React from 'react';
import './CountryList.styles.css';
import {Link} from 'react-router-dom'
import { CountryCard } from '../Card/CountryCard';
export const CountryList = (props) => (
<div className='card-list'>
{props.countries.map(country => (
<Link to={`/card-detail/${country.name}`} >
<CountryCard key={country.alpha2Code} country={country} />
</Link>
))}
</div>
);
This should go to the following component:
import React from 'react';
import { useEffect } from 'react';
import { useState } from 'react';
export const DetailCountryCard = ({match}) => {
useEffect(() => {
fetchItem();
console.log(match);
},[])
const [country, setCountry] = useState([])
const fetchItem = async ()=> {
const fetchCountry = await fetch(`https://restcountries.eu/rest/v2/name/${match.params.name}`);
const countries = await fetchCountry.json();
setCountry(countries);
console.log(country);
}
return (
<div>
{country.map(town => (
<div>
<h1 key={town.alpha2Code}>{town.name}</h1>
<p>Native Name{town.nativeName}</p>
<p>Region: {town.region}</p>
<p>Languages: {town.languages[0].name}</p>
</div>
))}
</div>
);
}
Not sure what I am missing. I don't think I have done a typo on the component. So not sure why it is not rendering? Any help would be appreciated.
You just need add dependency of match in useEffect in DetailCountryCard. Because [] its similar in Class ComponentcomponentDidMount()` and you need to listen when match it's changed.
This is final code to DetailCountryCard:
import React from "react";
import { useEffect } from "react";
import { useState } from "react";
export const DetailCountryCard = ({ match }) => {
useEffect(() => {
fetchItem();
console.log(match);
}, [match]);
const [country, setCountry] = useState([]);
const fetchItem = async () => {
const fetchCountry = await fetch(
`https://restcountries.eu/rest/v2/name/${match.params.name}`
);
const countries = await fetchCountry.json();
setCountry(countries);
console.log(country);
};
return (
<div>
{country.map(town => (
<div>
<h1 key={town.alpha2Code}>{town.name}</h1>
<p>Native Name{town.nativeName}</p>
<p>Region: {town.region}</p>
<p>Languages: {town.languages[0].name}</p>
</div>
))}
</div>
);
};
I tested in CodeSandBox and it works!
Link

Categories