I have created a global provider with a reducer that is holding the user state.
When the user login successfully I dispatch and update the state, setting login=true. It is working perfectly in all components except for the top component where is not updating. I don't see any problem. Any idea what might be the problem?
// index.js
import './styles/tailwind.css';
import './styles/index.css';
import React from 'react';
import ReactDOM from 'react-dom';
import {StateProvider } from "./store";
import App from './App';
import * as serviceWorker from './serviceWorker';
const app = (
<StateProvider>
<App />
</StateProvider>
);
ReactDOM.render(app, document.getElementById('root'));
//store.js - the provider that update the state
import React, {createContext, useReducer} from 'react';
const initialState = {
login:false,
token:'',
refreshToken:'',
user:{}
};
const store = createContext(initialState);
const { Provider } = store;
const StateProvider = ( { children } ) => {
const [state, dispatch] = useReducer((state, action) => {
switch(action.type) {
case 'login user':
state.login = true;
state.token = action.token;
state.refreshToken = action.refreshToken;
return state;
default:
throw new Error();
};
}, initialState);
return <Provider value={{ state, dispatch }}>{children}</Provider>;
};
export { store, StateProvider }
//App.js ---- in this page is not working, the rerendering doesn't happend and login remain false only in this component
import React, {useState, useContext, useEffect} from 'react';
import { store } from "./store";
import logo from './svg/book.svg';
import { BrowserRouter, Route, NavLink } from "react-router-dom";
import Classes from './components/school/Classes.js';
import Students from './components/school/Students.js';
import Home from './components/school/Home.js';
import Cards from './components/Cards.js';
import Login from "./components/Login";
function App() {
const globalState = useContext(store);
const login = globalState.state.login;
console.log(globalState);
const message = (login) ? "Welcome user" : "You are not loged in";
return (
<div>
<p>{message}</p>
<BrowserRouter>
<header className="grid grid-cols-1 sm:grid-cols-2">
<div className="flex align-middle"><img src={logo} className="h-12 p-1"></img>Bucharest Hi School
</div>
<div className="flex flex-wrap mr-5">
<NavLink to="/" className="w-full sm:w-1/4 text-center sm:text-right p-2" href="#" >Home</NavLink>
<NavLink to="/classes" className="w-full sm:w-1/4 text-center sm:text-right p-2" href="#" >Classes </NavLink>
<NavLink to="/students" className="w-full sm:w-1/4 text-center sm:text-right p-2" href="#">Students</NavLink>
<NavLink to="/statistics" className="w-full sm:w-1/4 text-center sm:text-right p-2" href="#">Statistics</NavLink>
<NavLink to="/logout" className="w-full sm:w-1/4 text-center sm:text-right bg-black text-white p-2" href="#">Logout</NavLink>
<NavLink to="/login" className="w-full sm:w-1/4 text-center sm:text-center bg-white rounded text-center p-2" href="#">Login</NavLink>
</div>
</header>
<div className="flex w-full flex-wrap justify-center my-10">
<Route exact path="/" component={Home} />
<Route path="/classes" component={Classes} />
<Route path="/students" component={Students} />
<Route path="/login" component={Login} />
</div>
</BrowserRouter>
</div>
);
}
export default App;
Thanks
You are mutating the state and returning same object in the reducer function, due to which your component assumes the data has never been changed.
Please make sure you return new object whenever you update your state, in your case you could use return { ...state }; instead of return state; in your reducer.
Related
I am trying to authenticate my react app using keycloak. The app is not able to successfully logout. I have setup the admin console to contain the post logout redirect url to be http://localhost:7171. The URL that shows on clicking logout button
http://localhost:8080/realms/hype/protocol/openid-connect/logout?client_id=hypeclient&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A7171%2F&id_token_hint=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZRHVhc0pTT0dIOUh2ZnRvZGNhRktmRkxuOEZuWmkzSVN3eVBQenlzazFVIn0.eyJleHAiOjE2NzU1NjU1ODUsImlhdCI6MTY3NTU2NTI4NSwiYXV0aF90aW1lIjoxNjc1NTY1MjcyLCJqdGkiOiI1MjEyODZlZS1hNThiLTRhMTUtYTgyZS1jODU2ZmE4NWVkZTEiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL2h5cGUiLCJhdWQiOiJoeXBlY2xpZW50Iiwic3ViIjoiYzJhZTJiZWYtMjg2ZC00MDgzLTk3YTktMWEyM2YxNzQ4YzE0IiwidHlwIjoiSUQiLCJhenAiOiJoeXBlY2xpZW50Iiwibm9uY2UiOiI3NGUyMTk3Ni0zNzcwLTQzNWEtOTEzMC1lNGU2YWUyY2I4ZTIiLCJzZXNzaW9uX3N0YXRlIjoiYzAxZWJlYTEtMjAwYS00YzU3LTg2NTgtNjYyYTdjOTA5OThkIiwiYXRfaGFzaCI6IjNPczlxc0NucFdmYnFuRkNlZ1docmciLCJhY3IiOiIwIiwic2lkIjoiYzAxZWJlYTEtMjAwYS00YzU3LTg2NTgtNjYyYTdjOTA5OThkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoia2FyYW4gc2hhaCIsInByZWZlcnJlZF91c2VybmFtZSI6ImthcmFuIiwiZ2l2ZW5fbmFtZSI6ImthcmFuIiwiZmFtaWx5X25hbWUiOiJzaGFoIn0.TFCh-dPE4h8HKpAapNv4socR7uAsKDnuVpqLiaZCguu1Vyztn_sUkrbaqFKJDeoIZv3XDzBDM5MezMvyRRkmiRIQTIbiTlYGgcNA5UkcwbeKioaowCWqYqyjG7A3D0lKNjf0MAF6PmWVrPJVbm8hYtQMVDHwWQ2vFTShnmMRJ2iTf4IeT1DDLANrLh3tMXeYMF2f9ZmvtNS72O-BICkD4uXPSyMiqnDgj_52UAdkEaLgkuv31TAYDAeVef-Rly8n7K5LG1MWytB2_-WrhwXwfxjymc9RH55Iy-1tvLUFceigO7XwQzffZYK5uJ9PXkbg3hlkr6QLVSXsiu8RYKQu9w
My Setup including the version looks like
Keycloak 20.0.3
Keycloak.js 20.0.3 via npm
react 18.2
App.JS
import React from "react";
import { ReactKeycloakProvider } from "#react-keycloak/web";
import keycloak from "./Keycloak";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Nav from "./components/Nav";
import WelcomePage from "./pages/Homepage";
import SecuredPage from "./pages/Securedpage";
import PrivateRoute from "./helpers/PrivateRoute";
function App() {
return (
<div>
<ReactKeycloakProvider authClient={keycloak}>
<Nav />
<BrowserRouter>
<Routes>
<Route exact path="/" element={<WelcomePage />} />
<Route path="/secured"
element={<PrivateRoute>
<SecuredPage />
</PrivateRoute>}/>
</Routes>
</BrowserRouter>
</ReactKeycloakProvider>
</div>
);
}
export default App;
Nav.JS
import React from "react";
import { useKeycloak } from "#react-keycloak/web";
const Nav = () => {
const { keycloak, initialized } = useKeycloak();
function loginHelper(){
console.log("login clicked");
keycloak.login();
};
function logoutHelper(){
console.log("login clicked");
keycloak.logout();
};
return (
<div>
<div className="top-0 w-full flex flex-wrap">
<section className="x-auto">
<nav className="flex justify-between bg-gray-200 text-blue-800 w-screen">
<div className="px-5 xl:px-12 py-6 flex w-full items-center">
<h1 className="text-3xl font-bold font-heading">
Keycloak React AUTH.
</h1>
<ul className="hidden md:flex px-4 mx-auto font-semibold font-heading space-x-12">
<li>
<a className="hover:text-blue-800" href="/">
Home
</a>
</li>
<li>
<a className="hover:text-blue-800" href="/secured">
Secured Page
</a>
</li>
</ul>
<div className="hidden xl:flex items-center space-x-5">
<div className="hover:text-gray-200">
{!keycloak.authenticated && (
<button
type="button"
className="text-blue-800"
onClick={() => loginHelper()}
>
Login
</button>
)}
{!!keycloak.authenticated && (
<button
type="button"
className="text-blue-800"
onClick={() => logoutHelper()}
>
Logout ({keycloak.tokenParsed.preferred_username})
</button>
)}
</div>
</div>
</div>
</nav>
</section>
</div>
</div>
);
};
export default Nav;
Keycloak.JS
import Keycloak from "keycloak-js";
const keycloak = new Keycloak({
url: "http://localhost:8080",
realm: "hype",
clientId: "hypeclient",
});
export default keycloak;
PrivateRoute.JS
import { useKeycloak } from "#react-keycloak/web";
const PrivateRoute = ({ children }) => {
const { keycloak } = useKeycloak();
const isLoggedIn = keycloak.authenticated;
console.log("checking auth access " + isLoggedIn);
return isLoggedIn ? children : null;
};
export default PrivateRoute;
I have tried following this link but it does not work. Admin console looks like
Try changing
valid post logout urls to +
valid redirect urls to http://localhost:7171*
I have a Headercomponent as follows:
const Header = () => {
const data = useDataContext().header;
return <div data-testid="header" className="w-full h-[80px] bg-white">
<div className="h-1 bg-green-400 w-full"/>
<div className="flex items-center w-full h-[80px] shadow-sm">
<div className="flex items-center flex-1 h-full">
<Navigation applications={data.apps}/>
</div>
<div className="flex items-center pl-4 h-full">
<Account />
</div>
</div>
</div>
}
The Navigation component is as follows:
import React, {useState} from 'react';
import {App} from "../../../../interfaces/interfaces";
import {map, find, filter} from "lodash";
import NavigationItem from "./NavigationItem";
import { useLocation, useNavigate } from 'react-router-dom';
interface NavigationProps {
applications: App[]
}
const Navigation:React.FC<NavigationProps> = ({applications}: NavigationProps) => {
const [expanded, setExpanded] = useState<boolean>(false)
const location = useLocation();
const navigate = useNavigate();
const activeApplication = find(applications, application => application.url === location.pathname);
const inactiveApplications = filter(applications, application => application.url !== location.pathname)
return (
<div className="flex h-full">
<div className="z-[2]">
<NavigationItem application={activeApplication}
handleClick={() => setExpanded(expanded => !expanded)}
expanded={expanded}
/>
</div>
<div className={`flex transform transition transition-transform ${expanded ? 'translate-x-0' : '-translate-x-full'}`}>
{map(inactiveApplications, application => {
return <NavigationItem application={application}
handleClick={() => {
setExpanded(false);
navigate(application.url);
}}
/>
})}
</div>
</div>
);
};
export default Navigation;
Thus the Navigation component uses the useLocation hook from react-router-dom.
I want to write some tests for Header component and the header.test.tsx is as follows:
import Header from "./Header";
import {MemoryRouter} from "react-router-dom";
import {describe, it} from "#jest/globals";
import {render, screen} from "#testing-library/react";
describe("<Header/>", () => {
it("renders the header component with Home link", () => {
render(<MemoryRouter initialEntries={["/home"]}>
<Header/>
</MemoryRouter>);
const textElement = screen.getByText(/Home/i);
expect(textElement).toBeInTheDocument();
const headerElement = screen.getByTestId("header");
expect(headerElement).toBeInTheDocument()
// expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.test.com');
});
});
Thus I wrap the header component with MemoryRouter but it still gives me an error as follows:
useLocation() may be used only in the context of a <Router> component.
What do I do wrong?
Thanks in advance.
Your render should look like this
render(
<MemoryRouter initialEntries={["/home"]}>
<Routes>
<Route path="/home" element={<Header/>} />
</Routes>
</MemoryRouter>
)
I am building a mock e-commerce store using react and after laying the groundwork for the website, I installed the package react-use-cart (https://www.npmjs.com/package/react-use-cart) to handle the items being added to the cart and to be used in a checkout page. I wrapped my app in the Cart Provider that was given by the react-use-cart package and set up the cart functions on my product pages but now my app does not load anything on startup and gives me an invalid hook call error. I have tried wrapping the app in the index.js file with the provider as well and that gives the same error.
import React, { useState } from "react";
import './css/App.css';
import {
BrowserRouter,
Routes,
Route,
} from "react-router-dom";
import NavBar from "./components/NavBar"
import ProductsScreen from "./screens/ProductsScreen"
import ProductDetailScreen from "./screens/ProductDetailScreen"
import HomeScreen from "./screens/HomeScreen"
import LoginScreen from "./screens/LoginScreen"
import RegisterScreen from "./screens/RegisterScreen";
import CartScreen from "./screens/CartScreen"
import { CartProvider } from "react-use-cart";
const App = () => {
return (
<>
<BrowserRouter>
<CartProvider>
<NavBar />
<Routes>
<Route path='/' element={<HomeScreen/>}/>
<Route path='/products' element={<ProductsScreen/>}/>
<Route path='/product/:id' element={<ProductDetailScreen/>}/>
<Route path='/login' element={<LoginScreen/>}/>
<Route path='/register' element={<RegisterScreen/>}/>
<Route path='/cart' element={<CartScreen/>}/>
</Routes>
</CartProvider>
</BrowserRouter>
</>
)
}
export default App;
import { useState, useEffect }from 'react'
import { Link, useParams } from 'react-router-dom'
import axios from 'axios'
import "../css/ProductDetailScreen.css"
import { useCart } from "react-use-cart";
// import Slideshow from '../components/SlideShow'
const { addItem, updateItemQuantity } = useCart();
const ProductDetailScreen = ( {match} ) => {
const [product, setProduct] = useState({})
const { id } = useParams()
useEffect(() => {
const getProduct = async () => {
const { data } = await axios.get(`/api/products/${id}`)
setProduct(data)
}
getProduct()
}, [])
return (
<>
<Link to ='/products'>Go Back</Link>
<div className="wrapper">
<div className="detail-grid">
<div className='imgContainer'>
<img className='cover_img1' src={product.cover_img} alt='Videogame Cover Art'/>
<div className='carousel'>
{product.images?.map((image, index) => (
<img key={index} src={image} alt="Videogame Gameplay"></img>
))}
</div>
{/*Replace carousel div with actual image slideshow*/}
</div>
<div>
<div className='textContainer'>
<h1 className='title1'>{product.title}</h1>
<h3 className='descriptionHeader'>Description:</h3>
<p className='description1'>{product.description}</p>
<h3 className='developer1'>Developer: {product.developer}</h3>
<h3 className='publisher1'>Publisher: {product.publisher}</h3>
<h3 className='releaseDate1'>Release Date: {product.releaseDate}</h3>
<h3 className='genre1'>Genre: {product.genre}</h3>
</div>
<div className='checkOutContainer'>
<h3 className='price1'>Price: ${product.price}</h3>
<button onClick={() => updateItemQuantity(product.id, product.quantity - 1)}> - </button>
<span>{product.quantity}</span>
<button onClick={() => updateItemQuantity(product.id, product.quantity + 1)}> + </button>
<button onClick={() => addItem(product)} className='addCart' type='button'>Add to Cart</button>
</div>
</div>
</div>
</div>
</>
)
}
export default ProductDetailScreen
I am trying to toggle sidebar from navbar toggle button but unfortunately my custom react hook or react context is not triggering re render but the state is updating and also logging to the console but the it is not re rendering. I know this question is been asked so many times but I tried many things but couldn't able to work as expected.
Please help thankyou!
sidebar component -
import React from 'react';
import {useAuth} from '../contexts/auth';
import {useSider} from '../contexts/sider';
import Avatar from './Avatar';
import SideItem from './SideItem';
const Sider: React.FC = (): JSX.Element => {
const {auth} = useAuth();
const {expanded} = useSider();
return (
<>
<aside className='h-full border-r border-violet-200 bg-white'>
<ul className='p-1'>
<SideItem
to='#!'
key='avatar'
element={
<Avatar
username={auth?.user.username ?? 'username'}
src={auth?.user.avatar ?? undefined}
expanded={expanded}
/>
}
/>
<SideItem
to='/'
icon='fa-tachometer-alt'
title='Dashboard'
key='dashboard'
/>
<SideItem
to='/subscriptions'
icon='fa-file-export'
title='Subscriptions'
key='subscriptions'
/>
<SideItem
to='/sheets'
icon='fa-file-lines'
title='Sheets'
key='sheets'
/>
</ul>
</aside>
</>
);
};
export default React.memo(Sider);
Navbar component -
import React from 'react';
import {NavLink} from 'react-router-dom';
import {useAuth} from '../contexts/auth';
import {useSider} from '../contexts/sider';
import Brand from './Brand';
import NavItem from './NavItem';
const Nav: React.FC = (): JSX.Element => {
const auth = useAuth();
const {toggleSider} = useSider();
function signOut() {
// TODO : Sign out logic
}
function subscribe() {
// TODO : subscribe logic
}
return (
<>
<nav className='flex items-center justify-between px-6 py-1 bg-blue-50 border-b border-violet-200 sticky top-0'>
<button
className='border flex aspect-square p-1 text-violet-500'
onClick={() => toggleSider()}
>
<i className='fa-solid fa-bars' />
</button>
<NavLink to='/'>
<Brand />
</NavLink>
<ul className='w-full flex-grow justify-end flex items-center'>
{!auth ? (
<>
<NavItem to='/signin' title='Sign In' icon='fa-sign-in' />
<NavItem
to='/register'
variant='warning'
title='Register'
icon='fa-edit'
/>
</>
) : (
<>
<NavItem
to='#new-subscription'
onClick={subscribe}
variant='success'
title='New Subscription'
icon='fa-user-plus'
/>
<NavItem
to='#sign-out'
onClick={signOut}
variant='danger'
title='Sign Out'
icon='fa-sign-out'
/>
</>
)}
</ul>
</nav>
</>
);
};
export default React.memo(Nav);
SideItem component -
import React from 'react';
import {NavLink, NavLinkProps} from 'react-router-dom';
import {useSider} from '../contexts/sider';
interface SideItemProps extends NavLinkProps {
icon?: string;
element?: JSX.Element;
}
const SideItem: React.ExoticComponent<SideItemProps> = React.memo(
({to, title, icon, element, ...rest}) => {
const {expanded} = useSider();
return (
<li className='border-b border-violet-200 block text-violet-500 hover:text-violet-800 active:text-violet-500 p-1 lg:pl-0'>
{element ?? (
<NavLink
to={to}
{...rest}
className='flex p-2 lg:p-1 lg:pl-0 text-base lg:text-lg font-medium justify-center lg:justify-start items-center hover:bg-blue-50 active:bg-inherit lg:rounded-r-full rounded-md flex-col lg:flex-row'
>
<i className={'lg:mx-2 lg:w-5 fa-solid ' + icon}></i>
{expanded && <span className='hidden md:block'>{title}</span>}
</NavLink>
)}
</li>
);
}
);
export default SideItem;
App component -
import React from 'react';
import {Routes, Route} from 'react-router-dom';
import Nav from './components/Nav';
import Sider from './components/Sider';
import PrivateOutlet from './hoc/PrivateOutlet';
import Dashboard from './pages/dashboard';
import Register from './pages/register';
import Signin from './pages/signin';
import Subscriptions from './pages/subscriptions';
import Sheets from './pages/sheets';
import useSiderVisibility from './hooks/useSiderVisibility';
import {SiderProvider} from './contexts/sider';
const App: React.FC = (): JSX.Element => {
const showSider = useSiderVisibility();
return (
<>
<div className='App bg-zinc-100 overflow-hidden'>
<SiderProvider>
<Nav />
</SiderProvider>
<main className='flex h-full'>
{showSider && (
<div className='hidden sm:block relative p-2 pt-0 pl-0 lg:w-1/5 sm:w-1/6'>
<SiderProvider>
<Sider />
</SiderProvider>
</div>
)}
<div className='overflow-y-auto p-2 relative w-full'>
<Routes>
<Route path='/signin' element={<Signin />} />
<Route path='/register' element={<Register />} />
<Route path='/' element={<PrivateOutlet />}>
<Route index element={<Dashboard />} />
<Route path='subscriptions' element={<Subscriptions />} />
<Route path='sheets' element={<Sheets />} />
</Route>
<Route element={'404 Not Found'} />
</Routes>
</div>
</main>
</div>
</>
);
};
export default React.memo(App);
Sider context -
import React, {
createContext,
PropsWithChildren,
useCallback,
useContext,
useState,
} from 'react';
interface ISiderContext {
expanded: boolean;
toggleSider: () => void;
}
const initialValue = {
expanded: true,
toggleSider: () => {},
};
const SiderContext = createContext<ISiderContext>(initialValue);
/**
* A hook to get the information about whether the sider component is expanded or collapsed stored in Sider Context.
* #returns expanded: true if sider is expanded and toggleSider: a function toggle between expanded and collapsed.
*/
const useSider = () => {
return useContext(SiderContext);
};
const SiderProvider: React.ExoticComponent<PropsWithChildren> = React.memo(
({children}): JSX.Element => {
const [expanded, setExpanded] = useState<ISiderContext['expanded']>(
initialValue.expanded
);
const toggleSider = useCallback(() => {
setExpanded((prev) => !prev);
}, []);
return (
<SiderContext.Provider value={{expanded, toggleSider}}>
{children}
</SiderContext.Provider>
);
}
);
export {SiderProvider, useSider};
This was the original code in which i had tried different things but didn't work.
Thankyou!
Can someone please help me? I'm trying to pass data through context from one component to another (from Search.js to Container.js). However, I'm getting a type error. I searched many questions in Web, but haven't found an answer. Sorry, if it's childish problem, I'm new.
Search.js:
import React, { Component } from 'react'
import { StyledFormSearchBar } from '../styles'
import { data } from '../data'
const SearchContext = React.createContext()
export default class Search extends Component {
constructor() {
super()
this.state = {
value: '',
results: [],
areResultsVisible: false
}
this.handleSearch = this.handleSearch.bind(this)
}
handleSearch(e) {
this.setState = {
value: e.target.value,
results: data.filter(item => {
return item.title.toLowerCase().includes(e.target.value.toLowerCase())
}),
areResultsVisible: true
}
}
render() {
return (
<StyledFormSearchBar>
<input type="search" name="search" className="border border-dark rounded" onSubmit={this.handleSearch}/>
<button type="submit" value="submit" className="bg-warning border-0 text-danger rounded-right position-relative">
<i className="fas fa-search"></i>
</button>
<SearchContext.Provider value = {{
...this.state,
handleSearch: this.handleSearch
}}>
{this.props.children}
</SearchContext.Provider>
</StyledFormSearchBar>
)
}
}
const SearchContextConsumer = SearchContext.Consumer
export { SearchContextConsumer }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Container.js:
import React from 'react'
import { Route, Switch } from "react-router-dom"
import { StyledDivGridContainer } from './styles'
import { SearchContextConsumer } from './header/Search'
import Carousel from './container/Carousel'
import MobilePhonesDiscount from './container/products/carousel/MobilePhonesDiscount'
import LaptopsDiscount from './container/products/carousel/LaptopsDiscount'
import TabletsDiscount from './container/products/carousel/TabletsDiscount'
import Products from './container/Products'
import SearchResults from './container/SearchResults'
import MobilePhones from './container/products/MobilePhones'
import Laptops from './container/products/Laptops'
import Tablets from './container/products/Tablets'
import ProductPage from './container/ProductPage'
import About from './container/About'
import ContactUs from './container/ContactUs'
export default function Container() {
return (
<StyledDivGridContainer>
<div className="no-gutters justify-content-between">
<SearchContextConsumer>
{
value => {
return (
!value.areResultsVisible
? <Switch>
<Route exact path="/" component={Carousel}/>
<Route exact path="/" component={Products}/>
</Switch>
: <Route exact path="/" component={SearchResults}/>
)
}
}
</SearchContextConsumer>
<Route path="/mobile_phones_discount" component={MobilePhonesDiscount}/>
<Route path="/laptops_discount" component={LaptopsDiscount}/>
<Route path="/tablets_discount" component={TabletsDiscount}/>
<Route path="/mobile_phones" component={MobilePhones}/>
<Route path="/laptops" component={Laptops}/>
<Route path="/tablets" component={Tablets}/>
<Route path="/product_page" component={ProductPage}/>
<Route path="/about" component={About}/>
<Route path="/contact_us" component={ContactUs}/>
</div>
</StyledDivGridContainer>
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Search.js is in Header.js:
import React, { Component } from 'react'
import { StyledHeader,
StyledSpanAccount } from './styles'
import { Link } from "react-router-dom"
import Catalogue from "./header/Catalogue"
import Search from "./header/Search"
export default class Header extends Component {
render() {
return (
<StyledHeader className="d-flex w-100 bg-light shadow justify-content-center">
<div className="d-flex flex-wrap justify-content-around align-items-center">
<div className="my-1 mr-3">
<Link to="/" className="logo">
<img src={require('../img/logo.webp')} alt="logo" className="img-tumbnail"/>
</Link>
</div>
<Catalogue/>
<Search/>
<div className="d-flex my-3">
<i className="fas fa-shopping-cart"></i>
<i className="fas fa-user-alt ml-3"></i>
<a href="#" className="d-flex flex-nowrap">
<StyledSpanAccount className="ml-2">Log in</StyledSpanAccount>
</a>
<a href="#" className="d-flex flex-nowrap">
<StyledSpanAccount className="ml-2">Sing up</StyledSpanAccount>
</a>
</div>
</div>
</StyledHeader>
)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Container.js is in App.js near Header.js:
import React from 'react';
import { Route } from "react-router-dom";
import { StyledDivWrapper } from './components/styles';
import Header from './components/Header';
import Container from './components/Container';
import Footer from './components/Footer';
export default function App() {
return (
<StyledDivWrapper className="d-flex flex-column">
<Route path="/" component={Header}/>
<Route path="/" component={Container}/>
<Route path="/" component={Footer}/>
</StyledDivWrapper>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Error screenshot
My folder tree
The problem is that your Container component is not within your SearchContext.Provider. To make sure your code works correctly you must have your SearchContext Provider directly inside you App Component so that it is a common parent of both Search and Container component
Firstly, create a context in a different file
// SearchContext.js
export const SearchContext = React.createContext();
const SearchContextConsumer = SearchContext.Consumer
export { SearchContextConsumer }
Secondly, create a component which acts as a provider
// SearchProvider.js
class SearchProvider extends React.Component {
constructor() {
super()
this.state = {
value: '',
results: [],
areResultsVisible: false
}
this.handleSearch = this.handleSearch.bind(this)
}
handleSearch(e) {
this.setState = {
value: e.target.value,
results: data.filter(item => {
return item.title.toLowerCase().includes(e.target.value.toLowerCase())
}),
areResultsVisible: true
}
}
render() {
return (
<SearchContext.Provider value = {{
...this.state,
handleSearch: this.handleSearch
}}>
{this.props.children}
</SearchContext.Provider>
)
}
}
Now you Search Component would simply be
// Search.js
export default class Search extends Component {
render() {
return (
<SearchContext.Consumer>
{({handleSearch}) => (
<StyledFormSearchBar>
<input type="search" name="search" className="border border-dark rounded" onSubmit={handleSearch}/>
<button type="submit" value="submit" className="bg-warning border-0 text-danger rounded-right position-relative">
<i className="fas fa-search"></i>
</button>
</StyledFormSearchBar>
)}
</SearchContext.Consumer>
)
}
}
Also no you can use your SearchProvider in App.js like
export default function App() {
return (
<SearchProvider>
<StyledDivWrapper className="d-flex flex-column">
<Route path="/" component={Header}/>
<Route path="/" component={Container}/>
<Route path="/" component={Footer}/>
</StyledDivWrapper>
</SearchProvider>
);
}
You first need to wrap your top level component with the context provider (Search), because the point of the context api is to pass the data top-down without having to explicitly pass it through every level, otherwise the context consumer won't have any context to consume in first place:
<Search>
<App />
</Search>
Also, you need to use the prop value to pass the context from the provider, and not any prop (like searchValue in your code):
<SearchContext.Provider value = {{
...this.state,
handleSearch: this.handleSearch
}}>
{this.props.children}
</SearchContext.Provider>
Import createContext like
import { React, createContext } from 'react';
export const MyContext = createContext();
this helped me.