The title says it all - I'm trying to send a word from a search bar to be consumed and processed in a backend function.
Any ideas on how this can be accomplished?
I'm using react and node/express for this
you also can use jQuery.The purpose of jQuery is to make it much easier to use JavaScript on your website. jQuery takes a lot of common tasks that require many lines of JavaScript code to accomplish, and wraps them into methods that you can call with a single line of code.
You can use the following method. The search term can be accessed in the backend from req.body. I have also used the debounce method so that the api won't be flooded if user continuously types in the search bar
import { useEffect, useState } from "react";
import debounce from "lodash.debounce";
import axios from "axios";
export default function App() {
const [text, setText] = useState("");
useEffect(() => {
if (text.length > 0) {
const payload = {
query: text
};
axios
.post("your_api_url_here", payload)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
}
}, [text]);
const update = (e) => setText(e?.target?.value);
const debouncedOnChange = debounce(update, 500); // delay to prevent bulk api call
return (
<div className="App">
<input placeholder="Search" type="text" onChange={debouncedOnChange} />
</div>
);
}
Whenever you decide it's the moment to send the value (either pressing a button or detecting a specific string) you can get the value of that element accessing document object and send it through a request to backend (maybe using axios)
Related
I got a problem with my dynamic route. It look like this
[lang]/abc
I am trying to get query value from [lang] but when I using useRouter/withRouter i got query during 2-3 render of page ( on first i got query.lang = undefined ). its possible to get in 1 render or use any technique ?
I found something:
isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server.
https://nextjs.org/docs/api-reference/next/router#router-object
And the code would be like:
const router = useRouter();
useEffect(()=>{
if(!router.isReady) return;
// codes using router.query
}, [router.isReady]);
It's impossible to get the query value during the initial render.
Statically optimized pages are hydrated without the route parameters, so the query is an empty object ({}).
Next.js will populate the query after the page has been hydrated.
Next.js 10.0.5 and up
To determine if the route params are ready, you can use router.isReady inside a useEffect hook. For an example, see the answer provided by #doxylee.
Before Next.js 10.0.5
At first render of a dynamic route router.asPath and router.route are equal. Once query object is available, router.asPath reflects it.
You can rely on the query value within a useEffect hook after asPath has been changed.
const router = useRouter();
useEffect(() => {
if (router.asPath !== router.route) {
// router.query.lang is defined
}
}, [router])
GitHub Issue - Add a "ready" to Router returned by "useRouter"
In NextJS 9+, one way to ensure route parameters are immediately available for page components is to get them from the context arg passed to getServerSideProps() and pass to the component as props.
For a page like [id].js,
export function getServerSideProps(context) {
return {
props: {params: context.params}
};
}
export default ({params}) => {
const {id} = params;
return <div>You opened page with {id}</div>;
};
This is a great question and one that took a few days for me to figure out what the best approach is.
I have personally found three viable solutions to the problem of validating dynamic route path params or even just route path params in general.
The three solutions are
SSR (don't recommend) [Next >= 10]
useRouter
Middleware [Next 12 required]
In my examples a will use a route that requires a reset-token or it should be redirected.
SSR
Firstly server side rending with getServerSideProps.
Vercel recommends to use SSR as a last resort and I would highly recommend not using SSR when able (time to byte & cost).
We suggest trying Incremental Static Generation or Client-side Fetching and see if they fit your needs.
https://vercel.com/blog/nextjs-server-side-rendering-vs-static-generation
But in the case that you do, say there is some server side api validation call you require to validate the query param.
export const getServerSideProps = async (context) => {
const { token } = context.query;
if (!token) {
return {
redirect: {
permanent: false,
destination: "/",
}
}
}
return {
props: {}
// props: { token }
// You could do this either with useRouter or passing props
}
}
useRouter Secondly the easiest useRouter. When I first did this I came across the problem when nextjs/react hydrates there will be a point when the query params are null. Luckily useRouter has isReady!
import Router, { useRouter } from "next/router";
const { query, isReady } = useRouter();
useEffect(() => {
if (!isReady) return;
if (!query.token) {
Router.push("/")
}
}, [isReady])
Middleware now this is my personal favourite as it seperates the functionality in a clean way imo.
I found this based of a vercel example. I would highly recommend reading through a bunch of these to find best practices.
https://github.com/vercel/examples/
import { NextResponse, NextRequest } from 'next/server'
export async function middleware(req) {
const { pathname, searchParams } = req.nextUrl
if (pathname == '/reset-token') {
const index = searchParams.findIndex(x => x.key === "token")
// You could also add token validation here.
if (!index) {
return NextResponse.redirect('/')
}
}
return NextResponse.next()
}
Here is the repo which has some cool filtering of query parameters.
This is a more soft approach instead of hard redirecting.
https://github.com/vercel/examples/tree/main/edge-functions/query-params-filter
Nico also has a great answer on this, expect I wouldn't recommend using hooks like in his example, instead use isReady.
https://stackoverflow.com/a/58182678/4918639
For Class Component Lovers
The even better approach is to listen for a dedicated event for this routeChangeComplete using this.props.router.events.on method, inside componentDidMount if you're using class component -
routeChangeComplete = () => {
// this WILL have valid query data not empty {}
console.log(this.props.router.query);
};
componentDidMount() {
this.props.router.events.on("routeChangeComplete", this.routeChangeComplete);
}
componentWillUnmount() {
this.props.router.events.off("routeChangeComplete", this.routeChangeComplete);
}
Ref: https://nextjs.org/docs/api-reference/next/router#routerevents
routeChangeComplete: Fires when a route changed completely.
Practically when isReady has become true or when router.query object has data.
For NextJS version - 12.0.8
"If you export a function called getServerSideProps (Server-Side Rendering) from a page, Next.js will pre-render this page on each request using the data returned by getServerSideProps."
=async functions
refference:https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#getserversideprops
Simply putting that async function on the page notifies NextJS of its presence.During prerendering stage of the component, the query object of the router will be empty.
isReady: boolean - Whether the router fields are updated client-side and ready for use. Should only be used inside of useEffect methods and not for conditionally rendering on the server.
refference: https://nextjs.org/docs/api-reference/next/router
solution:
import { useRouter } from 'next/router';
const Fn = () =>{
const router = useRouter();
const { param } = router.query;
const fetchData = async () => {
await fetch();
}
useEffect(() => {
fetchCat();
}, [router.isReady]);
}
I resolved my problem that I need it in Hoc component.
I wrapped using withRouter(withLocale(Comp)) and create conditional in HOC
export default function withLocale(WrappedPage) {
const WithLocale = ({ router, ...props }) => {
const { lang } = router.query;
if (!lang || !isLocale(lang)) {
return <Error statusCode={404} />;
}
return (
<LocaleProvider lang={lang}>
<WrappedPage {...props} />
</LocaleProvider>
);
};
return WithLocale;
}
Next.js <= 10.0.5
This is a good work around, I found around from this comment
Add useQuery.ts helper file
// useQuery.js
import { useRouter } from 'next/router';
// Resolves query or returns null
export default function useQuery() {
const router = useRouter();
const ready = router.asPath !== router.route;
if (!ready) return null;
return router.query;
}
usage
// In your components (instead of useRouter)
const query = useQuery();
useEffect(() => {
if (!query) {
return;
}
console.log('my query exists!!', query);
}, [query]);
Class Component | 12/16/2022 | React JS 18.2.0 | Next JS 13.0.6
I got the answer for those who want to use Class Component. This was actually nowhere to be found ! Enjoy !
You will add if(this.props.router.isReady) and include return in the condition in render().
.
.
import { withRouter } from 'next/router';
import { Component } from 'react';
class User extends Component {
...
render() {
if(this.props.router.isReady){ // Add this condition and include return ()
// Do anything
console.log(this.props.router.query) // Log 'query' on first render
return (
<div>
<SearchBar pid={this.props.router.query.pid} /> // Pass the query params to another component if needed
</div>
);
}
}
}
export default withRouter(User);
import { useEffect, useState } from "react";
function Popular() {
const [popular, setPopular] = useState([]);
useEffect(() => {
getPopular();
}, []);
const getPopular = async () => {
const api = await fetch(
`https://api.spoonacular.com/recipes/random?apiKey=${process.env.REACT_APP_RECIPE_API_KEY}&number=9`
);
const data = await api.json();
setPopular(data.recipes);
};
return (
<div>
{popular.map((recipe) => {
return (
<div>
<p>{recipe.title}</p>
</div>
);
})}
</div>
);
}
export default Popular;
I am pretty new to React, and I encountered this issue which I have been trying to fix to no avail. The code is a component that is to return a list of recipe title to my app. I am fetching data from an API in the getPopular() function which is set to the setPopular function variable of the useState() method. But when I save my work and return to the browser, the changes does not display. The list does not display, but if I console.log(data.recipes) it displays on the console.
Before now, if I made any change (maybe a text change) the React app renders it without reloading, but now I have to reload the page before I see the change.
Please how do I fix this issue? So that I can see changes without having to reload the page manually.
Not saying that this is the problem, but getPopular() should not be called after its declaration? By this I mean:
const getPopular = async () => {
const api = await fetch(
/...
};
useEffect(() => {
getPopular();
}, []);
Another thing that bugs me is, although JS/React is case sensitive, I really think you should avoid having a const called popular, since your functions is Popular.
Please, let me know if the order did matter for your problem. I will review some react classes soon, if i get another inside, i'll let you know.
I have been trying to add "no records found" message after running a search for worker names. But I have not been successful. I either get 20 "no records found" messages or none at all. I am not sure what I am doing wrong, but I have been trying for last 4 hours various methods and work arounds.
I know that this should be simple to implement, but it has been difficult.
Here is a link to my code on codesandbox: https://codesandbox.io/s/fe-hatc-ass-search-n62kw?file=/src/App.js
Any insights would be helpful....
Things I tried were, if else statements, logical operators... etc...
In my opinion the first thing you need to think about is what data do you need and when do you need it. To display no results like you want you are going to need the workers name in the component that is doing the filtering. So you would need it in the orders component. I would merge the worker data with the order data and then you can just filter and manipulate the data after that. That would also stop you from making an api request every time someone changes the input and all you need to do is filter the already fetched data. Then you can check the array length and if it is greater than 0 you can display results else display a no results statement.
So something like the following:
Orders component
import React, { useEffect, useState } from "react";
import "./Orders.css";
import Order from "./Worker";
import axios from "axios";
const Orders = () => {
const [orders, setOrders] = useState([]);
const [results, setResults] = useState([]);
const [searchedWorker, setSearchedWorker] = useState("");
const getOrders = async () => {
const workOrders = await axios.get(
"https://api.hatchways.io/assessment/work_orders"
);
const mappedOrders = await Promise.all(workOrders.data.orders.map(async order => {
const worker = await axios.get(
`https://api.hatchways.io/assessment/workers/${order.workerId}`
);
return {...order, worker: worker.data.worker}
}))
setOrders(mappedOrders);
};
useEffect(() => {
getOrders();
}, []);
useEffect(() => {
const filtered = orders.filter(order => order.worker.name.toLowerCase().includes(searchedWorker));
setResults(filtered)
}, [searchedWorker, orders])
return (
<div>
<h1>Orders</h1>
<input
type="text"
name="workerName"
id="workerName"
placeholder="Filter by workername..."
value={searchedWorker} //property specifies the value associated with the input
onChange={(e) => setSearchedWorker(e.target.value.toLowerCase())}
//onChange captures the entered values and stores it inside our state hooks
//then we pass the searched values as props into the component
/>
<p>Results: {results.length}</p>
{results.length > 0 ? results.map((order) => (
<Order key={order.id} lemon={order} />
)) : <p>No results found</p> }
</div>
);
};
//(if this part is true) && (this part will execute)
//is short for: if(condition){(this part will execute)}
export default Orders;
Then you can simplify your single order component
import React from "react";
const Order = ({ lemon }) => {
return (
<div>
<div className="order">
<p>Work order {lemon.id}</p>
<p>{lemon.description}</p>
<img src={`${lemon.worker.image}`} alt="worker" />
<p>{lemon.worker.name}</p>
<p>{lemon.worker.company}</p>
<p>{lemon.worker.email}</p>
<p>{new Date(lemon.deadline).toLocaleString()}</p>
</div>
</
div>
);
};
export default Order;
Looking at your code, the problem is because you're doing the filtering in each individual <Order> component. The filtering should be done in the parent Orders component and you should only render an <Order> component if a match is found.
Currently, your <Order> component is rendering, even if there's no match.
You could add an state in the Orders.js to count how many items are being presented. However, since each Worker depends on an api call, you would need to have the response (getWorker, in Workers.js) wait for the response in order to make the count. Every time the input value changes, you should reset the counter to 0.
https://codesandbox.io/s/fe-hatc-ass-search-forked-elyjz?file=/src/Worker.js:267-276
Also, as a comment, it is safer to put the functions that are run in useEffect, inside the useEffect, this way it is easier to see if you are missing a dependency.
I have been trying to learn React-query but can't seem to trigger requests with my onSubmit event. Right now the code is sending the request with "washington" as the default parameter and printing it to the screen, and a new request also triggers with the onBlur event, and fetch the data if the city typed is valid.
The thing is that wish I could move this logic to the submit() function, treat the data on the input and only if the data is valid, proceed to make the request. This is the stackblitz where I reproduced the problem with a free apiKey: StackBlitz
This is the code:
import React, { useState } from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';
const Fetch = async city => {
let apiKey = '91b5ff77e9e7d1985a6c80bbbb3b2034';
const { data } = await axios.get(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`
);
return data;
};
const Weather = () => {
const [city, setCity] = useState('washington');
const { data, error } = useQuery(['temperature', city], () => Fetch(city));
const submit = () => {};
return (
<div>
<form onSubmit={submit}>
<input onBlur={e => setCity(e.target.value)} type="text" />
<button type="submit">send</button>
</form>
{!data ? null : <div>{data.main.temp}</div>}
</div>
);
};
export default Weather;
You can also call setCity in the onSubmit event of the form, as the onSubmit event gets the complete submitted form in the submit event:
<form
onSubmit={(event) => {
event.preventDefault();
const city = new FormData(event.currentTarget).get("city");
// do validation here
if (isValid(city)) {
setCity(city)
}
>
<input name="city" type="text" />
<button type="submit">send</button>
</form>
make sure to give your input a name so that you can grab it from the form submit event.
You can use useMutation hooks. As what the documentation said mutations are typically used to create/update/delete data or perform server side-effects. For this purpose, React Query exports a useMutation hook.. This hooks will return an object that gives you a mutation function that you can use to trigger request based on user interactions.
const { mutate: renamedMutationFunction } = useMutation(newTodo => axios.post('/todos', newTodo)).
Then somewhere in your code, you can do:
const handleClick = () => { renamedMutationFunction(); //invoking the mutation }
EDIT
see #TkDodo answer for better solution. You can basically just re-set the city, and react-query will automatically refetch the data.
I have been learning js and then React.js over the last few weeks, following tutorials on Codecademy and then Educative.io (to learn with the new hooks, rather than the class-based approach). In an attempt to apply what I have learned I have been messing around creating a number of common website features as React components on a hello-world project.
Most recently I have been trying to make a search component, which uses the Spotify API to search for a track, but have been running into synchronisation issues which I can't quite figure out how to solve using the js synchronisation tools that I know of. I come from a Java background so am more familiar with mutexes/semaphores/reader-writer locks/monitors so it may be that I am missing something obvious. I have been basing the code on this blog post.
In my implementation, I currently have a SongSearch component, which is passed its initial search text as a property, as well as a callback function which is called when the input value is changed. It also contains searchText as state, which is used to change the value of the input.
import * as React from 'react';
interface Props {
initialSearchText: string,
onSearchTextUpdated: (newSearchText: string) => void;
}
export const SongSearch = (props: Props) => {
const [searchText, setSearchText] = React.useState(props.initialSearchText);
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newSearchText = e.target.value;
setSearchText(newSearchText);
props.onSearchTextUpdated(newSearchText);
}
return <input value={searchText} onChange={onChange}/>;
};
The results are currently just displayed a list in the SearchResults component, the values of which are passed as an array of songs.
import * as React from 'react';
import { SongInfo } from './index';
interface Props {
songs: SongInfo[]
}
export const SearchResults = (props: Props) => {
return (
<ul>
{props.songs.map((song) => {
return <li key={song.uri}>{song.name}</li>
})}
</ul>
);
}
In the App component, I pass a callback function which sets the state attribute searchText to the new value. This then triggers the effect which calls updateSongs(). If we have an auth token, and the search text isn't empty we return the results of the API call, otherwise we return an empty list of songs. The result is used to update the tracks attribute of the state using setTracks().
I have cutdown the code in App.tsx to only the relevant parts:
import SpotifyWebApi from 'spotify-web-api-js';
import React from "react";
// ... (removed irrelevant code)
async function updateSongs(searchText: string): Promise<SongInfo[]>{
if (spotify.getAccessToken()) {
if (searchText === '') {
console.log('Empty search text.');
return [];
} else {
// if access token has been set
const res = await spotify.searchTracks(searchText, {limit: 10});
const tracks = res.tracks.items.map((trackInfo) => {
return {name: trackInfo.name, uri: trackInfo.uri};
});
console.log(tracks);
return tracks;
}
} else {
console.log('Not sending as access token has not yet');
return [];
}
}
function App() {
// ... (removed irrelevant code)
const initialSearchText = 'Search...';
const [tracks, setTracks] = React.useState([] as SongInfo[]);
const [searchText, setSearchText] = React.useState(initialSearchText);
React.useEffect(() => {
updateSongs(searchText)
.then((newSongs) => setTracks(newSongs))
}, [searchText]);
const content = <SearchResults songs={tracks}/>;
return (
<ThemeProvider theme={theme}>
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
<Root config={mui_config}>
<Header
renderMenuIcon={(open: boolean) => (open ? <ChevronLeft /> : <MenuRounded />)}
>
<SongSearch initialSearchText={initialSearchText} onSearchTextUpdated={(newSearchText) => {
console.log(`New Search Text: ${newSearchText}`)
setSearchText(newSearchText);
}}/>
</Header>
<Nav
renderIcon={(collapsed: boolean)=>
collapsed ? <ChevronRight /> : <ChevronLeft />
}
classes={drawerStyles}
>
Nav
</Nav>
<StickyFooter contentBody={content} footerHeight={100} footer={footerContent}/>
</Root>
</div>
</ThemeProvider>
);
}
export default App;
The issue that I am having is that when I type in the name of a long song and then hold down backspace sometimes songs remain displayed in the list even when the search text is empty. From inspection of the console logs in the code I can see that the issue arises because the setTracks() is sometimes called out of order, in particular when deleting 'abcdef' quickly setTracks() the result of updateTracks('a') will be called after the result of updateTracks(''). This makes sense as '' does not require any network traffic, but I have spent hours trying to work out how I can synchronise this in javascript with no avail.
Any help on the matter would be greatly appreciated!
In your case the results are coming back differently because you send multiple events, and the ones that come first - fire a response and then you display it.
My solution would be to use a debounce function on the onChange event of the input field. So that the user will first finish typing and then it should start the search. Although there still might be some problems, if one search has started and the user started typing something else then the first one has finished and the second one has started and finished. In this you might find that cancelling a request helpful. Unfortunately you can't cancel a Promise, so you would have to read about RxJS.
Here's a working example using debounce
P.S.
You might find this conference talk helpful to understand how the event loop is working in JS.