I have a react project where a user is able to input a value within the Searchbar.js file, which is then return the data from an api call on the Shop.js file.
What I am trying to do is get the user input from a different search bar within the navbar to then navigate to the shop page, able to return the data from the api so that it can be shown in the shop.js page.
Unfortunately my pages are not technically parent and child components as I am showing the NavSearch.js file in my Navbar.js file and not the Shop.js.
I’ve tried to store the user input from the navbar using my context (useAxios.js) page however it also returns undefined each time.
Does anyone know a way to get around this?
SearchBar.js
import React from 'react'
import { useState } from 'react'
import useAxios from './Hooks/useAxios.js'
const Searchbar = ({ onSearch, onFilter, onInput}) => {
// state for the search input
const [input, setInput] = useState('')
const [filtered, setFiltered] = useState('')
const [navbarInput, setNavbarInput]= useAxios()
const searchProducts = () => {
if (input === ''){
alert('Please input something')
}
else{
if (filtered==='Brand'){
onSearch(input)
}
else if(filtered==='Product'){
onFilter(input)
}
else{
onInput(input)
onInput(navbarInput)
}
}
}
return (
<div>
<input type='text'
placeholder='Search'
value={input}
onChange={e => setInput(e.target.value)}
/>
<select placeholder='Search by'
onChange={e => setFiltered(e.target.value)}>
<option defaultValue>Search by...</option>
<option
value="Brand">Brand</option>
<option
value="Product">Product Type</option>
</select>
<button
onClick={() => searchProducts()}
>Submit</button>
</div>
)
}
export default Searchbar
Shop.js
import { useContext, useState } from 'react';
import Searchbar from '../Components/Searchbar';
import OptionButtons from '../Components/OptionButtons';
import { AppContext } from '../Context/Context';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBasketShopping } from '@fortawesome/free-solid-svg-icons';
// import Modal from '../Components/Modal';
// import Product from './Product';
import { Link } from 'react-router-dom';
import useAxios from '../Hooks/useAxios';
import savedHook from '../Hooks/savedHook';
import PriceSlider from '../Components/PriceSlider';
import BrandList from '../Components/BrandList';
// import Sort from '../Components/Sort'
import Pagination from 'react-paginate'
import '../Assets/Styles/Shop.css'
function Shop() {
// States //////////////////////////////////////////////////////////////
const { products, setProducts, isLoading, serverErr, getProductsByBrand, getProductsByType, selectAProduct, error, filterProduct } = useAxios('https://makeup-api.herokuapp.com/api/v1/products.json')
// saved icon to shaded
const { likedIndex, changeIcon } = savedHook()
// useContext for the add to cart
const Cartstate = useContext(AppContext)
const dispatch = Cartstate.dispatch;
// console.log(Cartstate)
// Usecontext for the saved array as it uses a different function
const Savestate = useContext(AppContext)
const saveDispatch = Savestate.saveDispatch
// console.log(Savestate)
//////////////////////////////////////////////////////////////////////////////
// onClick changes the heart from empty to full
// const changeIcon = (index) => {
// setLikedIndex(state => ({
// ...state, [index] // copies previous state
// : !state[index] // updates the state by adding the index key this is how it identifies which index has been clicked
// }))
// console.log(setLikedIndex)
// }
/////////////////////////////////////////////////////////////////////////////////
// const [productOpen, setProductOpen] = useState(false)
// const [showModal, setShowModal] = useState([])
// const openModal = (index) => {
// setProductOpen(true)
// setShowModal(index)
// }
// const closeModal = (index) => {
// setProductOpen(false)
// // setShowModal(index)
// }
// const handleClose = () => setProductOpen(false);
// const handleOpen = (index) => {
// console.log('modal is opened')
// setProductOpen(true);
// }
/////////////////////////////////////
// Randomly go through the array of data, this changes each time the user refreshes the page
// const shuffle = arr => [...arr].sort(() => Math.random() - 0.5);
// const randomProducts = shuffle(products)
// //////////////////////////////////////////
// pagination
// set to 0 because if I set it to 1 then it doesn't show all the data
const [currentPage, setCurrentPage] = useState(0)
const productPerPage = 10
const pagesVisited = currentPage * productPerPage
// How I want the data to be shown on the page
const displayProducts = products.slice(pagesVisited, pagesVisited + productPerPage)
.map((item, index) => {
return (
<div className='singleCard' key={item.id}>
{/* /* onClick={()=>openInformation(index)}>
{showInfo===index && <Product productName={item.brand+item.product_type}/>} */ }
< button value={item.brand + item.product_type} onClick={() => { changeIcon(index); saveDispatch({ type: 'SAVE', saveIt: item }) }}>
{likedIndex[index] ?
(
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6">
<path d="M11.645 20.91l-.007-.003-.022-.012a15.247 15.247 0 01-.383-.218 25.18 25.18 0 01-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0112 5.052 5.5 5.5 0 0116.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 01-4.244 3.17 15.247 15.247 0 01-.383.219l-.022.012-.007.004-.003.001a.752.752 0 01-.704 0l-.003-.001z" />
</svg>
)
: (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6" >
<path strokeLinecap="round" strokeLinejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
</svg>
)
}
</button>
<Link to={`/product/${item.id}`} name={item.brand}>
<img src={item.api_featured_image} alt={item.brand + item.product_type}></img>
{/* To display the brand name with as sentence case */}
<p>
{item?.brand ? item.brand.charAt(0).toUpperCase() + item.brand.slice(1).toLowerCase() : item.brand} {item?.name ? item.name.charAt(0).toUpperCase() + item.name.slice(1).toLowerCase() : item.name}</p>
<p>{item?.product_type ? item.product_type.charAt(0).toUpperCase() + item.product_type.slice(1).toLowerCase().split('_').join(' ') : item.product_type}</p>
<p>£{Number(item.price).toFixed(2)
}</p>
</Link>
{/* Removed modal */}
{/* <button onClick={()=>openModal(index)}>Show modal</button>
{productOpen && index === showModal && <Product close={()=>closeModal()}/>} */}
<div className='addToCart'>
<div className='productQuantity'>
</div>
<button onClick={() => dispatch({ type: 'ADD', payload: item })}>
<FontAwesomeIcon icon={faBasketShopping} />
</button>
</div>
</div>
)
})
// calucates total pages need for all the data with how many items to be shown each page
// Math.ceil rounds the number of pages to a whole integer
const totalPageCount = Math.ceil(products.length / productPerPage);
//callback function invoked with the updated page value when the page is changed.
const changePage = ({ selected }) => {
setCurrentPage(selected)
}
const sortThis = (e) => {
const sorting = e.target.value
const productList = [...products]
// function for ascending in price
const prices = productList.sort((a, b) => {
return sorting==='asc' ? a.price - b.price: b.price-a.price
});
setProducts(prices)
}
return (
<div>
<Searchbar
onSearch={getProductsByBrand}
onFilter={getProductsByType}
onInput={filterProduct}
/>
<PriceSlider ></PriceSlider>
<BrandList brandDropDown={getProductsByBrand}></BrandList>
{/* <Sort onSort={filterProduct}></Sort> */}
<select onChange={sortThis}>Sort it out
<option defaultValue>Sort</option>
<option value={'asc'}>Ascending</option>
<option value={'desc'}>Descending</option>
</select>
<OptionButtons onButton={selectAProduct} />
<button>
Reset filters
</button>
{serverErr && <div>{serverErr}</div>}
{error && <div>{error}</div>}
<div className='shopCards grid grid-cols-2'>
{!isLoading ? <>
{products.length ?
[displayProducts]
: <h1>No results found</h1>}
</> :
<h1>Loading...</h1>
}
</div >
<Pagination
previousLabel={'Previous page'}
nextLabel={'Next page'}
pageCount={totalPageCount}
onPageChange={changePage}
containerClassName={'paginationBtns'}
previousLinkClassName={'previousBtn'}
nextLinkClassName={'nextBtn'}
disabledClassName={'paginationDisbaled'}
activeClassName={'paginationActive'}
/>
</div >
)
}
export default Shop;
NavbarSearch.js
import useAxios from './Hooks/useAxios.js'
const NavSearch = () => {
const {navbarInput, setNavbarInput} = useAxios()
const handleChange = (e) => {
setNavbarInput(e.target.value)
}
return (
<div className='navbarSearch'>
<input className='navSearchBar w-32'
type='text'
placeholder='Search here'
value={navbarInput}
onChange={handleChange} />
</div>
)
}
RootLayout.js (my navbar file)
import React from 'react'
import '../Assets/Styles/Navbar.css';
import { NavLink, Outlet } from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faBars } from '@fortawesome/free-solid-svg-icons'
import { useState } from 'react';
import { useContext } from 'react';
import { AppContext } from '../Context/Context';
import CartPopUp from '../Components/CartPopUp';
import SavedHook from '../Hooks/savedHook';
import NavSearch from '../Components/NavSearch';
export default function RootLayout() {
const [mobileOpen, setMobileOpen] = useState(false);
const{cartDrawer, openDrawer} = SavedHook()
const [searchBarOpen, setSearchBarOpen] = useState(false)
// const [cartDrawer, setCartDrawer] = useState(false);
const CartState = useContext(AppContext);
const state = CartState.state
// to show changing cart amount at shopping cart icon
const quantityOnly = state.map(item => item.quantity)
console.log(quantityOnly);
const totalCartQuantity = quantityOnly.reduce(function(accumulator,currValue)
{
return accumulator + currValue;
},0)
console.log(totalCartQuantity);
const openDropdown = () => {
setMobileOpen(current => !current);
}
const openSearchBar = () => {
setSearchBarOpen(true)
}
return (
<>
<div className='jumbotron flex justify-between'>
<div className='py-2'>
ShopIt
</div>
<nav className='largeMenuCentre hidden justify-center w-full inline-block lg:flex lg:w-auto lg:order-1 md:flex md:w-auto md:order-1'>
<ul className='navOptions flex'>
<div className='flex'>
<li className='block py-2 pl-3 pr-4'>
<NavLink to='/'>Home</NavLink>
</li>
<li className='block py-2 pl-3 pr-4'>
<NavLink to='/about'>About</NavLink>
</li>
<li className='block py-2 pl-3 pr-4'>
<NavLink to='/shop'>Shop</NavLink>
</li>
</div>
</ul>
</nav>
<nav className='hidden justify-end w-full lg:flex lg:w-auto lg:order-1 md:flex md:w-auto md:order-1' >
<ul>
<div className='flex pl-5'>
<li className='block py-2 pl-3 pr-4 rightSideNavbar' id='findIcon'>
<button onClick={openSearchBar}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
</svg>
{
searchBarOpen ?
<NavSearch />: null
}
</button>
</li>
<li className='block py-2 pl-3 pr-4 rightSideNavbar' id='saveIcon'>
<NavLink to='/saved'><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
</svg>
</NavLink >
</li>
<li className='block py-2 pl-3 pr-4 rightSideNavbar' id='shopIcon'>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6" onClick={openDrawer}>
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 00-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 00-16.536-1.84M7.5 14.25L5.106 5.272M6 20.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm12.75 0a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" />
</svg>
<span>{totalCartQuantity<=0?null:totalCartQuantity }</span>
{cartDrawer ?
<CartPopUp /> : null
}
</li>
</div>
</ul>
</nav>
<div className='mobileBarIcon lg:hidden md:hidden sm:block pt-2 pr-3' onClick={openDropdown}>
<FontAwesomeIcon icon={faBars} size="xl" />
</div>
{mobileOpen ?
<ul className='navOptions flex flex-col mt-4 lg:hidden md:hidden'>
<li className='block py-2 pl-3 pr-4'>
<NavLink to='/'>Home</NavLink>
</li>
<li className='block py-2 pl-3 pr-4'>
<NavLink to='/about'>About</NavLink>
</li>
<li className='block py-2 pl-3 pr-4'>
<NavLink to='/shop'>Shop</NavLink>
</li>
<li className='block py-2 pl-3 pr-4 rightSideNavbar' id='findIcon'>
<button>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
</svg>
</button>
</li>
<li className='block py-2 pl-3 pr-4 rightSideNavbar' id='saveIcon'>
<NavLink to='/saved'><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
</svg>
</NavLink>
</li>
<li className='block py-2 pl-3 pr-4 rightSideNavbar' id='shopIcon'>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6" onClick={openDrawer}>
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 00-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 00-16.536-1.84M7.5 14.25L5.106 5.272M6 20.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm12.75 0a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" />
</svg>
<span>{totalCartQuantity<=0?null:totalCartQuantity }</span>
{cartDrawer ?
<CartPopUp /> : null
}
</li>
</ul> : null
}
</div>
<div className='navbarSeperator w-full h-4'></div>
<main>
<Outlet />
</main>
</>
)
}
useAxios.js (context file)
import { useEffect, useState } from "react"
import axios from 'axios'
// custom hooks keeps code from stopping to be repeated, only maintaining the code in one place rather than in different places
const useAxios = (url) => {
const [isLoading, setIsLoading] = useState(false)
// output of search items
const [products, setProducts] = useState([])
// output error code
const [error, setError] = useState(false)
const [serverErr, setServerErr] = useState(false)
const [singleProduct, setSingleProduct] = useState([])
// call when user filters by brand
const getProductsByBrand = async (brandName) => {
setIsLoading(true)
try {
const res = await axios.get(url, {
params: {
brand: brandName
}
});
console.log(res.data)
if (res.status === 200) {
console.log('Success!');
const productsWithQuantity = res.data.map((item)=>{
return(
item.price === '0.0' || item.price === null ? {...item, price:8.50, quantity:1} :{...item, quantity:1}
)})
setProducts(productsWithQuantity);
}
else {
console.log(`Server error: ${res.status}`);
}
}
catch (err) {
console.log(`Fetch error: ${err}`);
setError(err.message)
}
finally {
setIsLoading(false)
}
}
// API call for the filter by button
const getProductsByType = async (productCategory) => {
setIsLoading(true)
try {
const res = await axios.get(url, {
params: {
product_category: productCategory
}
});
console.log(res.data)
if (res.status === 200) {
console.log('Success!');
// adds a quantity of 1 to each object within the array of data from the api
const productsWithQuantity = res.data.map((item)=>{
return(
item.price === '0.0' || item.price === null ? {...item, price:8.50, quantity:1} :{...item, quantity:1}
)})
setProducts(productsWithQuantity);
}
else {
console.log()
setServerErr(`Server error: ${res.message} `);
}
}
catch (err) {
console.log(`Fetch error: ${err}`);
setError(err.message);
}
finally {
setIsLoading(false)
}
}
const selectAProduct = async (productType) => {
setIsLoading(true)
try {
const res = await axios.get(url, {
params: {
product_type: productType
}
});
console.log(res.data)
if (res.status === 200) {
console.log('Success!');
const productsWithQuantity = res.data.map((item)=>{
return(
item.price === '0.0' || item.price === null ? {...item, price:8.50, quantity:1} :{...item, quantity:1}
)})
setProducts(productsWithQuantity);
}
else {
console.log(`Server error: ${res.status}`);
}
}
catch (err) {
console.log(`Fetch error: ${err}`);
setError(err.message)
}
finally {
setIsLoading(false)
}
}
const filterProduct = async (searched) => {
setIsLoading(true)
try {
const res = await axios.get(url)
if (res.status === 200) {
console.log('Success!');
const productsWithQuantity = res.data.map((item) => ({
...item, quantity: 1
}));
const keyWord = searched
let filteredInput = productsWithQuantity.filter(e => Object.values(e).map(e => String(e).toLowerCase()).some(e => e.includes(keyWord)));
console.log(filteredInput);
setProducts(filteredInput)
}
else {
console.log(`Server error: ${res.status}`);
}
} catch (err) {
console.log(`Fetch error: ${err}`);
setError(err.message);
}
finally {
setIsLoading(false)
}
}
useEffect(() => {
const getAPI = async (url) => {
// useCallback means that the API call will not be made everytime we make a change to the page e.g. reviewing a products info
// useCallback means that the API call will only be called when it needs to be e.g. on page refresh. we use useCallback instead of useMemo as we want the function to be returned and not just the value
setIsLoading(true)
try {
const res = await axios.get(url)
if (res.status === 200) {
console.log('Success!');
// converts prices that are set to 0.0 by the API and adds quantity of 1 to eahc object in the data array
const productsWithQuantity = res.data.map((item)=>{
return(
item.price === '0.0' || item.price === null ? {...item, price:8.50, quantity:1} :{...item, quantity:1}
)})
// res.data.map((item) => ({
// ...item, quantity: 1,
// // ...item.price === '0.0' ? '8.50' : item.price
// }));
// converts prices that are set to 0.0 by the API
setProducts(productsWithQuantity);
}
else {
console.log(`Server error: ${res.status}`);
}
} catch (err) {
setError(err.message);
}
finally {
setIsLoading(false)
}
}
getAPI(url)
}, [url]);
useEffect(() => {
const singleAPI = async (url) => {
// useCallback means that the API call will not be made everytime we make a change to the page e.g. reviewing a products info
// useCallback means that the API call will only be called when it needs to be e.g. on page refresh. we use useCallback instead of useMemo as we want the function to be returned and not just the value
setIsLoading(true)
try {
const res = await axios.get(url)
if (res.status === 200) {
console.log('Success!');
const verifiedPrice = res.data.map((item)=>{
return(item.price === '0.0' || item.price === null ? {...item, price:8.50, quantity:1} :{...item, quantity:1}
)})
console.log(verifiedPrice)
setSingleProduct(verifiedPrice);
}
else {
console.log(`Server error: ${res.status}`);
}
} catch (err) {
console.log(`Fetch error: ${err}`);
setError(err.message);
}
finally {
setIsLoading(false)
}
}
singleAPI(url)
}, [url]);
// state management for navbar search engine
const [navbarInput, setNavbarInput]=useState('')
return {
isLoading,
products,
getProductsByBrand,
getProductsByType,
selectAProduct,
error,
singleProduct,
setProducts,
serverErr,
filterProduct,
navbarInput,
setNavbarInput
// sortThis
}
}
2
Answers
concerning Sharing Data between react components
There are 2 broad methods:
1- Data dripping from Parent to nested child (accepted when child is shallow nested).
2- state management Tools (like Context or Redux, ….etc)
If the components you want to share data between has no direct relationship
consider using Context.
https://react.dev/reference/react/createContext
That works for simple use cases.
Other wise, consider integrating ReduxToolKit (better than Redux for avoiding boilerplate code and simplicity of use)
https://redux-toolkit.js.org/rtk-query/overview
Check these articles for how to set each:
https://medium.com/cleverprogrammer/the-react-context
https://medium.com/edonec/redux-can-be-made-easier-with-redux-toolkit-b1d2d17b90ba
share the code where you are saving your user input ?
but you should use context api to solve this issue .