Ive made a search and filtering bar as part of an application im making in React. The current way it works is suggestions appear as you type. However there is no handler for if the user just clicks the submit button. At the moment clicking the submit button will take you to a results page with the query in the page URL. I would like this to be passed as a state when you click the link. This link could then be displayed in the Results component. Ive attempted this but im fairly new to React so any help would be appreciated.
Heres the search component:
import * as React from 'react';
import { useState } from "react";
import { Link } from "react-router-dom";
const content = [
{link: '/review/elden-ring', name: 'Elden\nRing'},
{link: '/review/', name: 'defg'},
{link: '/review/', name: 'ghij'},
{link: '/review/', name: 'jklm'},
]
export default function Search(props) {
//For storing and setting search input
const [query, setQuery] = useState("");
return (
//Search input
<div >
<form >
<input id="searchInput" type="text" placeholder="Search" value={query} onChange={event => {setQuery(event.target.value)}}/>
<div > {/* Flex container to align the icon and bar */}
<Link to={{pathname: "/results/" query, state: {query}}}> {/* Error handler as search is strick */}
<button type="submit" onClick={() => setQuery(() => "")}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> {/* ! Font Awesome Pro 6.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z" />
</svg>
</button>
</Link>
</div>
</form>
{/* Search Suggestions */}
<div >
{/* Query must have length to prevent mapping by default */}
{query.length > 0 && content.filter((content) => {
//If input return object
if (query == "") {
return content
}
//If any input characters much object characters return corresponding object
else if (content.name.toLowerCase().includes(query.toLocaleLowerCase())) {
return content
}
})
//Maps element based on the number of json objects
.map((content) => {
return(
<div >
<Link to={content.link} onClick={() => setQuery(() => "")}><p>{content.name}</p></Link>
</div>
);
})};
</div>
</div>
);
};
Heres the Results component
import * as React from 'react';
export default function Results(props) {
return (
<h1>{props.location.state.query}</h1>
);
};
Routes
import * as React from 'react';
import './app.css';
import { Routes, Route } from "react-router-dom";
import Header from './components/header/header';
import Footer from './components/footer';
import Error from './components/error';
import Results from './components/results';
import Index from './components/index/index';
import ReviewsPage from './components/reviews/reviewsPage';
import Review from './components/reviews/review';
export default function App() {
return (
<>
<Header />
<Routes>
<Route path="/" element={<Index />} />
<Route path="/reviews" element={<ReviewsPage />} />
{/* Render review with ID for switch statment */}
<Route path="/review/:id" element={<Review />} />
<Route path="/results/:id" element={<Results />} />
<Route path="*" element={<Error />} />
</Routes>
<Footer />
</>
);
};
Search component import line 30
import * as React from 'react';
import Search from './search';
import { useState } from 'react';
import { Link } from 'react-router-dom';
export default function Header() {
//State to toggle navlinks on small screens
const [state, setState] = useState(false)
return (
<nav className=" w-full bg-red-500 shadow-lg relative max-h-[4.1rem]"> {/* Max height set to avoid search suggestions increasing header size */}
<div className="flex justify-between py-3.5 w-full px-3 md:w-2/3 md:px-0 m-auto">
{/* Logo */}
<Link className="text-2xl font-semibold text-white hover:animate-pulse whitespace-nowrap" to="/">GAME REVIEWS</Link>
<div className="flex max-h-[3rem]"> {/* Container to prevent flex effecting both parents container */}
{/* Links */}
{!state && (
<ul id="links" className=" h-40 lg:h-auto flex-col flex lg:flex-row absolute lg:relative mt-10 lg:mt-0 right-0 lg:right-auto px-10 lg:px-0 bg-red-500 rounded-lg lg:rounded-none shadow-sm lg:shadow-none">
<li className="m-5 lg:my-0 lg:mx-5">
<Link className="text-2xl text-white border-none hover:border-solid border-b-2 border-white" to="/">Home</Link>
</li>
<li className="m-5 lg:my-0 lg:mx-5">
<Link className="text-2xl text-white border-none hover:border-solid border-b-2 border-white" to="/reviews">Reviews</Link>
</li>
</ul>
)}
{/* Search bar */}
<Search />
{/* Hamburger */}
<div id="hamburger" onClick={() => setState(!state)} className=" space-y-2 ml-5 mt-2 block cursor-pointer lg:hidden">
<div className="w-6 h-0.5 bg-white"></div>
<div className="w-6 h-0.5 bg-white"></div>
<div className="w-6 h-0.5 bg-white"></div>
</div>
</div>
</div>
</nav>
)
}
Heres an example of what I want to achieve User searches 'game'
Upon clicking the icon on the right they should be redirected to my results page. This page should show what they just entered on submit.
CodePudding user response:
You can use dynamic route in the Link
component which passes query
in the URL. And to parse it in Result
component, you can use match
props.
To navigate change your Link
component to
<Link to={"/results/" query} />
And to parse the query
in Result
component, use
<h1>{props.match.params.id}</>
CodePudding user response:
If you want the page results to be shared, you must include on the url the search term something like: www.yourdomain.com/review/elden-ring
Take a look and you will see that I've defined that the review
route now expects a parameter. You should use that parameter to check all the data you need to display on the page.
And had to edit the Search component because you're using class
instead of className
for styling.
On the Results
component I use the useParams
hook to get the url params and show it on the h1
. You should use this param as a key to retrieve the actual details of the review of your API.
This is how I'd do (all the logic):
On the App component I define the routes:
<Routes>
<Route exact path="/" element={<Search />} />
<Route path="/review/:query" element={<Results />} />
</Routes>
On the Search component:
// Router v6 hook to navigate the user
const navigate = useNavigate();
const queryRef = useRef(null) // Reference to the input
// Navigates the user to reviews/what they've written
const queryHandler = () => navigate(`/reviews/${queryRef.current.value}`);
return (
<>
// This is where the user types the query
<input type='text' ref={queryRef} placeholder='Search' />
<Icon onClick={queryHandler} /> // This has the onClick to hndle the click
</>
)
And on the results component:
const params = useParams(); // This retrieves all the params defined for the url
<h1>{params.query}</h1>
CodePudding user response:
The query
you're sending in the history.push()
method must be an object
. Instead, you are sending a string. Change it to object like below
props.history.push({
pathname: '/results',
state: { query }
});
CodePudding user response:
For the following route. localhost:3000/search/?query=ABCD
The following code on load extract the ABCD from query=ABCD and set to the state.
export default function App() {
const [query, setQuery] = useState(() => {
const q = new URLSearchParams(window.location.search);
console.log(q.toString());
return q.get("query") || "";
});
return (
<div className="App">
<h1>Query</h1>
<h2>{query}</h2>
</div>
);
}
So this way you can extract the info from a route.
Now if you want to know how to move from one page to another
assuming you are using some routing library, look at how you can change the route
history.push(`/search/?query={query}`)
is a way to use with react router ( ensure you use the useHistory hook for it )
CodePudding user response:
It was far simpler than I thought and something I had done on a different page
Creating a state for the input. Setting input as the variable in that state (query). Setting the value as the input using an onClick on the button. The link then provided the state variable with the route.
const [query, setQuery] = useState("");
<form
className="text-black ml-5 py-0.5 lg:py-0 flex border-2 border-gray-400 rounded-md bg-white px-1"
>
<input
id="searchInput"
className="focus:outline-none"
type="text"
placeholder="Search"
value={query}
onChange={(event) => {
setQuery(event.target.value);
}}
/>
{/* Flex container to align the icon and bar */}
<div className="flex mt-1.5">
<Link to={{ pathname: "/results/" query }}>
<button type="submit" onClick={() => setQuery(() => "")}>
<svg
className="fill-current h-auto w-4 "
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
{/* ! Font Awesome Pro 6.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z" />
</svg>
</button>
</Link>
</div>
</form>
Then in the route the path is given a variable (id)
<Route path="/results/:id" element={<Results />} />
This could then be pulled in the results page by useParams. And assigned to my h1 tag.
import { useParams } from "react-router-dom";
export default function Results(props) {
const {id} = useParams();
return (
<h1>{id}</h1>
);
};
Thank you for everyone help and guidance.