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 };
Related
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
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.
In my application, I have a child component: Post, which has an input text box to submit some data.
My Parent component Posts should take this input value data from the child and post it to the google Firebase database upon submitting it via the function addAnswerHandler().
How can I pass the value in the input with inputname=Answer from the child component to the parent function postAnswerHandler().
React, { Component } from 'react';
import { render } from 'react-dom';
import { Link, Route, NavLink, Switch, Redirect } from 'react-router-dom';
import Answered from '../../containers/Blog/answered/answered-posts';
import './Post.css';
import RothIRA_1 from '../../containers/Types/RothIRA_1';
function post (props) {
return (
<article key={props.id} className="Post" onClick={props.clicked}>
<h1>{props.key_1}</h1>
<h1>{props.title}</h1>
<p className="Body-Content">{props.body}</p>
<div className="Info">
<div className="Answer-Section">
<input type="text" className="Question-Answer" name="Answer" value={'test'}
placeholder="Your Answer..." ></input>
<li className="Answer-Button"><input type="submit" value="Answer" id="submitButton" /></li>
</div>
<ul>
<li className="Type-Link"><NavLink to={{
pathname: '/'+ props.type,
hash: '#submit',
search: '?quick-submit=true'
}}>{props.type}</NavLink></li>
</ul>
</div>
<Switch>
<Route path="/RothIRA" component={RothIRA_1} />
<Route path="/answered" component={Answered} />
{/* <Route path={'/' + props.type} component={RothIRA_1} /> */}
{/* <Route path= {'/'+ props.type} component={RothIRA} /> */}
</Switch>
</article>
);
}
export default post;
import React, { Component } from 'react';
import axios from '../../../axios';
import { Link, Route, NavLink, Switch, Redirect } from 'react-router-dom';
import Post from '../../../components/Post/Post';
import './Posts.css';
import FullPost from '../FullPost/FullPost';
import Answered from '../answered/answered-posts';
import Unanswered from '../../Unanswered/Unanswered-Posts';
import Myquestions from '../My-Questions/Myquestions';
import RothIRA_1 from '../../Types/RothIRA_1'
class Posts extends Component {
state = {
posts: []
}
componentDidMount () {
axios.get( 'https://blog-6d4da-default-rtdb.firebaseio.com/posts.json' )
.then( response => {
let posts = Object.values(response.data);
let keys = Object.keys(response.data);
const updatedPosts = keys.map( key => {
return {
key, ...response.data[key],
}
} );
this.setState( { posts: updatedPosts } );
} )
.catch( error => {
console.log( error );
} );
}
postAnswerHandler = ( id, question_answer ) => {
const data = {
answer: question_answer
};
const data_1 = JSON.stringify(data);
axios.put('/posts/'+id+'.json', data);
}
render () {
let posts = <p style={{ textAlign: 'center' }}>Something went wrong!</p>;
if ( !this.state.error ) {
posts = this.state.posts.map( (post) => {
return (
<Post
key={post.key}
id={post.key}
title={post.title}
type={post.type}
body={post.body}
answer={post.answer}
clicked={() => this.postAnswerHandler(post.key, post.answer)}
/>
);
} );
}
return (
<div className="Posts-Page">
<section className="Posts">
{posts}
</section>
</div>
);
}
}
export default Posts;
How can I pass the data from one React hooks form (component) to another component. For example if I need player name and photo pass from Profile.js and make it available in Navigation.js, how can i do that ?
Player.js
import React, { useContext , useEffect, useState } from "react";
import { useForm } from 'react-hook-form';
import { useHistory } from "react-router-dom";
import Axios from "axios";
import UserProfileContext from '../context';
const Profile = () => {
const { setData } = useContext(UserProfileContext);
const [email, setEmail] = useState('');
const [picture, setPicture] = useState('');
const [playerProfile, setPlayerProfile] = useState([]);
const loginUserEmail = localStorage.getItem('loginEmail');
const [updateProfile, setUpdateProfile] = useState({ _id: '', photo: '', name: '', email:'', phonenumber: '', position: '', password: '' })
const [isSent, setIsSent] = useState(false);
const [helperText, setHelperText] = useState('');
const [disabled, setDisabled] = useState(true);
const { handleSubmit, register, errors } = useForm();
const history = useHistory();
const onChangePicture = e => {
console.log('picture: ', picture);
if (e.target.files.length) {
setPicture(URL.createObjectURL(e.target.files[0]));
} else {
return false;
}
};
// If no profile image is being uploaded, to avoid the broken display of image, display a default image.
const addDefaultSrc = e => {
e.target.src = '/images/default-icon.png';
}
// Pass the id to the handler so you will know which item id changing.
const handleChange = (e, id) => {
e.persist();
let itemIndex;
const targetPlayer = playerProfile.find((player, index) => {
console.log({ player, id, index });
itemIndex = index; // Track the index so you can use it to update later.
return player.id === id;
});
console.log({ targetPlayer, id, e });
const editedTarget = {
...targetPlayer,
[e.target.name]: e.target.value
};
const tempPlayers = Array.from(playerProfile);
tempPlayers[itemIndex] = editedTarget;
/*
// Alternatively:: you can just map over the array if you dont want to track the index
const tempPlayers = playerProfile.map((profile, index) => {
return profile.id === id ? editedTarget : profile;
});
*/
setPlayerProfile(tempPlayers);
setUpdateProfile({ ...updateProfile, [e.target.name]: e.target.value }); // this is added just to see if its working
};
useEffect(() => {
const fetchData = async () => {
try {
const params = {
email: loginUserEmail,
};
const res = await Axios.get('http://localhost:8000/service/profile', {params});
setPlayerProfile(res.data.playerProfile);
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
const onSubmit = () => {
setDisabled(disabled);
const fetchData = async () => {
try {
const params = {
email: loginUserEmail,
};
const data = {photo: updateProfile.photo, name: updateProfile.name, email: updateProfile.email, phonenumber: updateProfile.phonenumber, position: updateProfile.position, password: updateProfile.password}
const res = await Axios.put('http://localhost:8000/service/profile', data, {params});
console.log("Front End update message:" + res.data.success);
if (res.data.success) {
setIsSent(true);
history.push('/')
}
else {
console.log(res.data.message);
setHelperText(res.data.message);
}
} catch (e) {
setHelperText(e.response.data.message);
}
}
fetchData();
}
return (
<div className="register_wrapper">
<div className="register_player_column_layout_one">
<div className="register_player_Twocolumn_layout_two">
<form onSubmit={handleSubmit(onSubmit)} className="myForm">
{
playerProfile.map(({ id, photo, name, email, phonenumber, position, privilege, password }) => (
<div key={id}>
<div className="formInstructionsDiv formElement">
<h2 className="formTitle">Profile</h2>
<div className="register_profile_image">
<input id="profilePic" name="photo" type="file" onChange={onChangePicture} />
</div>
<div className="previewProfilePic" >
<img alt="" onError={addDefaultSrc} name="previewImage" className="playerProfilePic_home_tile" src={photo} onChange={e => handleChange(e, id)}></img>
</div>
</div>
<div className="fillContentDiv formElement">
<label>
<input className="inputRequest formContentElement" name="name" type="text" value={name}
onChange={e => handleChange(e, id)}
maxLength={30}
ref={register({
required: "Full name is required",
pattern: {
value: /^[a-zA-Z\s]{3,30}$/,
message: "Full name should have minimum of 3 letters"
}
})}
/>
<span className="registerErrorTextFormat">{errors.name && errors.name.message}</span>
</label>
<label>
<input className="inputRequest formContentElement" name="email" type="text" value={email}
onChange={e => handleChange(e, id)}
disabled={disabled}
/>
</label>
<label>
<input className="inputRequest formContentElement" name="phonenumber" type="text" value={phonenumber}
onChange={e => handleChange(e, id)}
maxLength={11}
ref={register({
required: "Phone number is required",
pattern: {
value: /^[0-9\b]+$/,
message: "Invalid phone number"
}
})}
/>
<span className="registerErrorTextFormat">{errors.phonenumber && errors.phonenumber.message}</span>
</label>
<label>
<input className="inputRequest formContentElement" name="position" type="text" value={position}
onChange={e => handleChange(e, id)}
maxLength={30}
ref={register({
pattern: {
value: /^[a-zA-Z\s]{2,30}$/,
message: "Position should have minimum of 2 letters"
}
})}
/>
<span className="registerErrorTextFormat">{errors.position && errors.position.message}</span>
</label>
<label>
<div className="select" >
<select name="privilege" id="select" value={privilege} onChange={e => handleChange(e, id)}>
{/*<option selected disabled>Choose an option</option> */}
<option value="player">PLAYER</option>
{/*<option value="admin">ADMIN</option>*/}
</select>
</div>
</label>
<label>
<input className="inputRequest formContentElement" name="password" type="password" value={password}
onChange={e => handleChange(e, id)}
minLength={4}
maxLength={30}
ref={register({
required: "Password is required",
pattern: {
value: /^(?=.*?\d)(?=.*?[a-zA-Z])[a-zA-Z\d]+$/,
message: "Password begin with a letter and includes number !"
}
})}
/>
<span className="registerErrorTextFormat">{errors.password && errors.password.message}</span>
</label>
</div>
<label>
<span className="profileValidationText">{helperText}</span>
</label>
<div className="submitButtonDiv formElement">
<button type="submit" className="submitButton">Save</button>
</div>
</div>
))
}
</form>
</div>
</div>
</div>
);
}
Navigation.js
import React, { useContext } from 'react';
import { NavLink, useHistory } from 'react-router-dom';
import UserProfileContext from '../context';
const Navigation = () => {
const history = useHistory();
const { data } = useContext(UserProfileContext);
const divStyle = {
float:'left',
color: '#64cad8',
padding: '0px 0px 0px 10px',
font:'Lucida, sans-serif'
};
function logout() {
localStorage.removeItem('loginEmail')
localStorage.removeItem('Privilege')
history.push('/login')
window.location.reload(true);
}
return localStorage.getItem('loginEmail') &&
<div className="App">
<div className="wrapper">
<nav className="siteNavigation_nav_links">
<div className="clubLogo landing"style={divStyle}><b>Southside Soccer</b></div>
<NavLink className="mobile_register_link" to="/">Home</NavLink>
<NavLink className="mobile_register_link" to="/profile">Profile</NavLink>
<NavLink className="mobile_login_link" to="/login" onClick={logout}>Logout</NavLink>
<NavLink className="mobile_login_link" to='/aboutus'>About us</NavLink>
<div className="profileImage nav menu">
<span>{data.name}</span>|<img src=""></img>
</div>
</nav>
</div>
</div>
}
export default Navigation;
App.js
import React, { useState } from 'react';
import "./App.css";
import "./CSSModules/home.css";
import "./CSSModules/register.css";
import "./CSSModules/login.css";
import "./CSSModules/aboutus.css";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./components/Home";
import Register from "./components/Register";
import Login from "./components/Login";
import Aboutus from "./components/Aboutus";
import Navigation from "./components/Navigation";
import Profile from "./components/Profile";
import { ProtectedRoute } from "./components/protected.route";
import UserProfileContext from './context';
var ReactDOM = require("react-dom");
const App = () => {
const [data, setData] = useState({
id: '',
name: '',
email: '',
photo: '',
});
return (
<UserProfileContext.Provider value={{ data, setData }}>
<BrowserRouter>
<>
<Navigation />
<Switch>
<ProtectedRoute exact path="/" component={Home} />
<ProtectedRoute path="/profile" component={Profile} />
<ProtectedRoute path="/aboutus" component={Aboutus} />
<Route path="/register" component={Register} />
<Route path="/login" component={Login} />
</Switch>
</>
</BrowserRouter>
</UserProfileContext.Provider>
);
};
ReactDOM.render(
React.createElement(App, null),
document.getElementById("root")
);
export default App;
context.js
import React from 'react';
export default React.createContext();
Like mentioned in the comments, one option is to left your application's state up (and that should be the preferred option for simple state).
In practice, that would look like:
App.js
import React, { useState } from 'react';
import Navigation from './Navigation';
import Profile from './Profile';
function App() {
const [name, setName] = useState('');
return (
<div className="App">
<Navigation name={name} />
<hr />
<Profile name={name} setName={setName} />
</div>
);
}
export default App;
Profile.js:
import React from 'react';
const Profile = ({ name, setName }) => {
return (
<>
<div>Profile: {name}</div>
<input
type="text"
name="name"
value={name}
onChange={e => setName(e.target.value)}
/>
</>
);
};
export default Profile;
Navigation.js:
import React from 'react';
const Navigation = ({ name }) => {
return <div>Navigation: {name}</div>;
};
export default Navigation;
Edit: After a closer look at your code, I think using context API makes more sense in this case.
Try the following:
context.js
import React from 'react';
export default React.createContext();
App.js
import React, { useState } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import './styles.css';
import Profile from './components/Profile';
import Navigation from './components/Navigation';
import UserProfileContext from './context';
const App = () => {
const [data, setData] = useState({
id: 'player-1',
name: 'Player One',
age: 25,
photo: 'rose.JPG',
});
return (
<UserProfileContext.Provider value={{ data, setData }}>
<BrowserRouter>
<Navigation />
<Switch>
<Route path="/profile" component={Profile} />
</Switch>
</BrowserRouter>
</UserProfileContext.Provider>
);
};
export default App;
components/Navigation.js
import React, { useContext } from 'react';
import { NavLink } from 'react-router-dom';
import UserProfileContext from '../context';
const Navigation = () => {
const { data } = useContext(UserProfileContext);
const divStyle = {
float: 'left',
color: '#64cad8',
padding: '0px 0px 0px 10px',
font: 'Lucida, sans-serif',
};
return (
<div className="App">
<div className="wrapper">
<nav className="siteNavigation_nav_links">
<div className="clubLogo landing" style={divStyle}>
<b>Soccer</b>
</div>
<NavLink className="mobile_register_link" to="/profile">
Profile
</NavLink>
<div className="profileImage nav menu">
<span>{data.name}</span> | <img alt="" src={data.photo} />
</div>
</nav>
</div>
</div>
);
};
export default Navigation;
components/Profile.js
import React, { useContext } from 'react';
import { useForm } from 'react-hook-form';
import UserProfileContext from '../context';
const Profile = () => {
const { setData } = useContext(UserProfileContext);
const { register, handleSubmit } = useForm();
return (
<div>
<form onSubmit={handleSubmit(setData)}>
<b>Profile</b>
<input name="id" ref={register} />
<input name="name" ref={register} />
<input name="age" ref={register} />
<button type="submit" className="submitButton">
Click
</button>
</form>
</div>
);
};
export default Profile;
You can utilize react-redux for global state handling or pass a callback function from Navigation.js to Profile.js.
Use React's built in context API. Wrap your App in context provider, then you can use useContext hook to access state, dispatch state updates between components.
App level setup - one time
// Define Context
const AppContext = React.createContext()
// Define initial state
const initialState = {}
// Define Reducer
const Reducer = (state, dispatch) => {
switch(action.type) {
case "ACTION_TYPE": return {...state, prop: action.payload}
default: return state
}
}
//Wrap main App in context, pass state from React's useReducer hook
const [state, dispatch] = useReducer(Reducer, initialState)
<AppContext.Provider data={{
state,
dispatch
}}>
<ReactAppMainWrapper />
</AppContext.Provider>
Component level
const {state, dispatch} = useContext(AppContext);
// to update state call dispatch from anywhere, all components consuming state will be updated
dispatch({
type: "ACTION_TYPE",
payload: "new_value"
})
Explanation
The state serves as redux like store, available in the entire app.
In any component, import AppContext and use React's builtin useContext hook to interact with store from the component.
Data object passed in Context provider above is available from this.
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