How to update array of a component from another component's function? - javascript

I am rebuilding a static website in react https://movie-list-website-wt.netlify.app/ I am trying to transfer the search function to react. My current search function works as intended, it returns an array of movies that is being searched, I want it to update the data fetched by the movie cards so that when I search a query, the movie cards use the returned search data instead of the original one
this is my App.js file
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Movies from './components/Movies';
import Topnav from './components/Topnav';
import './App.css';
import './components/Movies.css';
export const API_KEY = 'api_key=247b6aa143f3f2c0b100c0cbdfb1ac99';
export const BASE_URL = 'https://api.themoviedb.org/3';
export const API_URL = BASE_URL + '/discover/movie?sort_by=popularity.desc&' + API_KEY;
export const IMG_URL = 'https://image.tmdb.org/t/p/w500';
export const searchURL = BASE_URL + '/search/movie?'+ API_KEY;
function App() {
const [movies, setMovies] = useState ([]);
useEffect(() => {
fetch (API_URL)
.then(res => res.json())
.then(data => {
console.log(data);
setMovies(data.results);
});
}, [])
return (
<>
<Router>
<Topnav />
<div className="movie-container">
{movies.length > 0 &&
movies.map((movie) => (
<Movies key={movie.id} {...movie} />))}
</div>
<Routes>
<Route exact path='/' />
</Routes>
</Router>
</>
);
}
export default App;
and this is part of my Top navigation component that includes the search bar and function
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import './Topnav.css';
import { searchURL } from '../App';
function Topnav() {
const [click, setClick] = useState(false)
const handleClick = () => setClick(!click)
const modeToggle = () => {
document.body.classList.toggle('dark')
}
const [query, setQuery] = useState("")
const onChange = (e) => {
e.preventDefault();
setQuery(e.target.value)
fetch(searchURL+`&language=en-US&page=1&include_adult=false&query=${e.target.value}`)
.then((res) => res.json())
.then((data) => {
console.log(data.results);
})
}
return (
<>
<nav className="topnav">
<div className="topnav-container">
<Link to='/' className='topnav-logo'>
<img src={require('../img/logo.png').default} alt="logo" />
</Link>
<div className="wrapper">
<form id='search-bar'>
<input
type="text"
placeholder="Search"
className="search"
id="search"
value={query}
onChange={onChange}
/>
</form>
<label className="switch">
<input type="checkbox" id="mode-toggle" onChange={modeToggle}/>
</label>
</div>

If you need to filter the movies array in the Topnav component you can just pass the setMovies state function as prop to the component.
All you need to do is update the data in the onChange method:
App.js
return (
<>
<Router>
<!-- Pass the prop to the component -->
<Topnav setMovies={setMovies}/>
<div className="movie-container">
{movies.length > 0 &&
movies.map((movie) => (
<Movies key={movie.id} {...movie} />))}
</div>
<Routes>
<Route exact path='/' />
</Routes>
</Router>
</>
);
Topnav.js
function Topnav({ setMovies }) {
...
const onChange = (e) => {
e.preventDefault();
setQuery(e.target.value)
fetch(searchURL+`&language=en-US&page=1&include_adult=false&query=${e.target.value}`)
.then((res) => res.json())
.then((data) => {
console.log(data.results);
// Update the data once fetched
setMovies(data.results)
})
}
...

It is strait forward just move the method onChange to the parent component and pass it as a props
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Movies from './components/Movies';
import Topnav from './components/Topnav';
import './App.css';
import './components/Movies.css';
export const API_KEY = 'api_key=247b6aa143f3f2c0b100c0cbdfb1ac99';
export const BASE_URL = 'https://api.themoviedb.org/3';
export const API_URL = BASE_URL + '/discover/movie?sort_by=popularity.desc&' + API_KEY;
export const IMG_URL = 'https://image.tmdb.org/t/p/w500';
export const searchURL = BASE_URL + '/search/movie?'+ API_KEY;
function App() {
const [movies, setMovies] = useState ([]);
const [, setQuery] = useState("")
useEffect(() => {
fetch (API_URL)
.then(res => res.json())
.then(data => {
console.log(data);
setMovies(data.results);
});
}, [])
const onChange = (e) => {
e.preventDefault();
setQuery(e.target.value)
fetch(searchURL+`&language=en-US&page=1&include_adult=false&query=${e.target.value}`)
.then((res) => res.json())
.then((data) => {
console.log(data.results);
// DO what ever you want with the move array
})
}
return (
<>
<Router>
<Topnav onChange={onChange} query={query}/>
<div className="movie-container">
{movies.length > 0 &&
movies.map((movie) => (
<Movies key={movie.id} {...movie} />))}
</div>
<Routes>
<Route exact path='/' />
</Routes>
</Router>
</>
);
}
in this was way can edit the movie array in the APP Componenet
then your child component will be something like
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import './Topnav.css';
import { searchURL } from '../App';
function Topnav({onChange, query}) {
const [click, setClick] = useState(false)
const handleClick = () => setClick(!click)
const modeToggle = () => {
document.body.classList.toggle('dark')
}
return (
<>
<nav className="topnav">
<div className="topnav-container">
<Link to='/' className='topnav-logo'>
<img src={require('../img/logo.png').default} alt="logo" />
</Link>
<div className="wrapper">
<form id='search-bar'>
<input
type="text"
placeholder="Search"
className="search"
id="search"
value={query}
onChange={onChange}
/>
</form>
<label className="switch">
<input type="checkbox" id="mode-toggle" onChange={modeToggle}/>
</label>
</div>
...

Related

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

My .filter in react lost when refresh page

I'm trying create a search bar, when user want to search a product.
Here is my Search Input:
const [searchTerm, setSearchTerm] = useState("");
const onSubmit = (e) => {
e.preventDefault();
navigate(`/search/${searchTerm}`);
setIsShowing(false);
setOpacity(1);
};
<FormSearch onSubmit={onSubmit}>
<SearchInput type="text"
placeholder="Type something to search"
onChange={(e)=> setSearchTerm(e.target.value)}
defaultValue={searchTerm} />
<SearchButton type="submit" value="Search" />
</FormSearch>
and here is the router when click search and take user to another page:
<Router>
<SearchInfo
path="/search/:title "
searchTerm={searchTerm}
/>
</Router>
and here is my react function for the page after search:
import React, { useEffect, useState } from "react";
import styled from "styled-components";
const SearchInfo = (props) => {
const [products, setProducts] = useState([]);
const getProductsAPI = () => {
axios
.get("http://localhost:8000/api/products")
.then((res) => {
setProducts(res.data);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
getProductsAPI();
}, [props]);
const InfoWrapper = styled.div`
text-align: center;
`;
return (
<div>
<InfoWrapper>
{products
.filter((product) =>
product.title.includes(props.searchTerm.toUpperCase())
)
.map((filteredItem, i) => (
<Item key={i}>
<ItemTitle> {filteredItem.title} </ItemTitle>
</Item>
))}
</InfoWrapper>
</div>
);
};
export default SearchInfo;
if I refresh the page it will show all my products instead of just props.searchTerm.
How can I fix this? Seems like the props I passed from route didn't session
The searchTerm comes from the state and props you pass, not from the url. Youll need to get the param from the Router and use that instead, see https://reactrouter.com/web/api/Hooks/useparams
Something like:
<Router>
<SearchInfo path="/search/:searchterm"/>
</Router>
import { useParams } from "react-router-dom";
const SearchInfo = (props) => {
let { searchterm } = useParams();
// ...
return (
<div>
<InfoWrapper>
{products.filter((product) => product.title.includes(searchterm))
.map((filteredItem, i) => (
<Item key={i}>
<ItemTitle> {filteredItem.title} </ItemTitle>
</Item>
))}
</InfoWrapper>
</div>
);
};
I don't know why your SearchInfo have path as prop but I think path is supposed to be managed by router, so the ideal structure would be:
<Router path="/search/:searchterm" component={SearchInfo} />
Then you can easily access to location info:
const SearchInfo = (props) => {
// Here is what you need
const {
match: { params },
} = props;
}

How to assign value onClick from different component to another component in React

What i want to do :
When i click my button i.e Search in Navbar.js i want to assign the search text in the variable urlQuery so i can pass it as props in Episodes.js component
End goal is to pass the urlQuery from Navbar.js somehow to Episodes.js component so i can query the REST api
How do i achieve the desired behaviour pls help
App.js
import React, { useState } from 'react';
import './App.css'
import Episodes from './components/Episodes/Episodes'
import CustomNavbar from './components/Navbar/Navbar'
import Pagination from './components/Pagination/Pagination'
function App() {
const [postsPerPage] = useState(20);
const [currentPage, setCurrentPage] = useState(1);
const url=`https://rickandmortyapi.com/api/episode?page=${currentPage}`
let urlQuery = `https://rickandmortyapi.com/api/episode?name=${SEARCH TEXT HERE}`
const paginate = pageNumber => setCurrentPage(pageNumber);
return (
<div>
<CustomNavbar />
<Episodes
urlQuery={urlQuery}
url={url}
/>
<Pagination
postsPerPage={postsPerPage}
totalPosts={36}
paginate={paginate}
/>
</div>
);
}
export default App;
Navbar.js
import React from 'react';
import Navbar from 'react-bootstrap/Navbar';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
import FormControl from 'react-bootstrap/FormControl';
const customNavbar = () => {
return (
<Navbar bg="light" expand="lg">
<Navbar.Brand href="#home">Rick And Morty</Navbar.Brand>
<Form inline>
<FormControl type="text" placeholder="Search" />
<Button>Search</Button>
</Form>
</Navbar>
);
}
export default customNavbar
Edit
On Zohaib's suggestion this error is thrown
Failed to compile.
./src/components/Navbar/Navbar.js
Line 14:48: Unexpected use of 'event' no-restricted-globals
Search for the keywords to learn more about each error.
App.js
import React, { useState, useEffect } from 'react';
import './App.css'
import Episodes from './components/Episodes/Episodes'
import CustomNavbar from './components/Navbar/Navbar'
import Pagination from './components/Pagination/Pagination'
function App() {
const [postsPerPage] = useState(20);
const [currentPage, setCurrentPage] = useState(1);
const [userSearchValue, setUserSearchValue] = useState('');
const [url, setUrl] = useState(``);
const [urlQuery, setUrlQuery] = useState(``)
useEffect(() => {
setUrl(`https://rickandmortyapi.com/api/episode?page=${currentPage}`)
}, [currentPage]);
useEffect(() => {
setUrlQuery(`https://rickandmortyapi.com/api/episode?name=${userSearchValue}`)
}, [userSearchValue])
const paginate = pageNumber => setCurrentPage(pageNumber);
const handleButtonClick = (searchValue) => {
setUserSearchValue(searchValue);
}
return (
<div>
<CustomNavbar
onButtonClick={handleButtonClick}
/>
<Episodes
urlQuery={urlQuery}
url={url}
/>
<Pagination
postsPerPage={postsPerPage}
totalPosts={36}
paginate={paginate}
/>
</div>
);
}
export default App;
Navbar.js
import React, { useState } from 'react';
import Navbar from 'react-bootstrap/Navbar';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
import FormControl from 'react-bootstrap/FormControl';
const customNavbar = ({ onButtonClick }) => {
const [searchValue, setSearchValue] = useState('');
return (
<Navbar bg="light" expand="lg">
<Navbar.Brand href="#home">Rick And Morty</Navbar.Brand>
<Form inline>
<FormControl type="text" placeholder="Search" value={searchValue} onChange={(e) => setSearchValue(e.target.value)} />
<Button onClick={() => onButtonClick(searchValue)}>Search</Button>
</Form>
</Navbar>
);
}
export default customNavbar
The important part here is you're passing down the handleButtonClick function to the child component (Navbar). This way you can call that parent function in the child component whenever you want (ie. when the user clicks the submit button).
Do you mean something like this?
There is a React guide about this specific problem: Lifting State Up.
Normally what you do is you manage the state in the parent. In this case App where you manage the search text state. You pass down a function to components to change this state. The components that depend upon this state are passed the value through the properties.
Here is an example:
const {useEffect, useState} = React;
function App() {
const episodesURL = "https://rickandmortyapi.com/api/episode";
const [page, setPage] = useState(1);
const [pageInfo, setPageInfo] = useState({});
const [searchText, setSearchText] = useState("");
const [episodes, setEpisodes] = useState([]);
useEffect(() => {
const url = new URL(episodesURL);
url.searchParams.set("page", page);
if (searchText) url.searchParams.set("name", searchText);
fetch(url)
.then(response => response.json())
.then(response => {
if (response.error) {
setPageInfo({});
setEpisodes([]);
} else {
setPageInfo(response.info);
setEpisodes(response.results);
}
});
}, [page, searchText]);
const search = searchText => {
setSearchText(searchText);
setPage(1);
};
return (
<div>
<CustomNavbar search={search} />
<Episodes episodes={episodes} />
<Pagination setPage={setPage} info={pageInfo} />
</div>
);
}
function CustomNavbar({search}) {
const [searchText, setSearchText] = useState("");
const handleFormSubmit = event => {
event.preventDefault();
search(searchText);
};
return (
<form onSubmit={handleFormSubmit}>
<input
type="text"
placeholder="search"
value={searchText}
onChange={event => setSearchText(event.target.value)}
/>
<button type="submit">Search</button>
</form>
);
}
function Episodes({episodes}) {
return (
<table>
<thead>
<tr>
<th>episode</th>
<th>name</th>
<th>air date</th>
</tr>
</thead>
<tbody>
{episodes.map(episode => (
<tr key={episode.id}>
<td>{episode.episode}</td>
<td>{episode.name}</td>
<td>{episode.air_date}</td>
</tr>
))}
</tbody>
</table>
);
}
function Pagination({setPage, info}) {
return (
<div>
{info.prev && <a onClick={() => setPage(page => page - 1)}>previous</a>}
{info.next && <a onClick={() => setPage(page => page + 1)}>next</a>}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
th { text-align: left; }
a { cursor: pointer; }
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Change urlQuery to state variable. Then, pass setUrlQuery to NavBar as a prop and on search button clickEvent call setUrlQuery function.

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