NextJS: Push new query string value to an existing asPath or update the existing query string - reactjs

I am kinda lost on how the NextJS router system works:
I have articles by categories that can be:
Medical
Charity
Wedding
Funeral
I am having a navbar where the user can display articles by category, and can also search by a keyword. The category feature is working perfectly.
This is how my navbar looks like:
That means if the user clicks on All, we are displaying all articles:
Here is the scenario:
When we click for example on Medical, the URL looks like this: http://localhost:3000/articles?category=medical
When we click in all, the URL looks like http://localhost:3000/articles
When we click in all and search the keyword COVID, the URL looks like this: http://localhost:3000/articles?search=covid
PROBLEM
I now want to combine categories and search:
If the user clicks in medical and searches for the keyword COVID, the URL must look like this: http://localhost:3000/articles?category=medical&search=covid.
After getting the search result, when the user changes the keyword, I want to replace the search query string with the new value.
HOW I TRIED TO DO?
I know NextJS router object has:
pathname: Current route. That is the path of the page in /pages
and asPath: Actual path (including the query) shown in the browser
const { push, asPath } = useRouter();
const onSearchKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
const keyword = e.currentTarget.value;
if (e.key === "Enter") {
url = !isEmpty(keyword.trim())
? {
pathname: asPath,
query: { search: keyword },
}
: {
pathname,
};
push(url);
}
};
But doing so is encoding the asPath and resulting in this URL: http://localhost:3000/article%3Fcategory=covid&search=covid which is displaying the 404 page not found.
So, I don't even know if I can replace the search with a new value as the user wants to perform a new search.

I solved the issue by not using the asPath: I instead used the pathname alongside the spread operator as follow:
url = !isEmpty(keyword.trim())
? {
pathname,
query: { ...query, search: keyword },
}: {
pathname: page,
};

Related

How do I replace queries in the URL when on the same path? | Without useHistory() if possible

I have a page that displays products returned from my API based on the URL's search parameters. For example, '/products?search=socks' would return products with the word socks in the title. The search bar is located in the header. If I search for a product on the home page (a different path: '/'), it navigates to the product list page and displays the products. However, if I search for a product on the product list page (the same path: '/products'), it re-renders and removes the search parameters, leading to no products being displayed. I prefer to use current hooks as it appears useHistory is outdated.
I have tried to navigate to the URL directly:
navigate(`/products?search=${search}`);
And I have tried to set the parameters as one source suggested:
const params = new URLSearchParams();
params.set('search', search);
navigate(`/products?search=${search}`);
I expected the query/search parameter to replace the search value and re-render with the new results.
I would use the new React Router V6 useSearchParams hook:
import { useSearchParams } from 'react-router-dom'
Then instantiate the hook at the top of your component:
let [searchParams, setSearchParams] = useSearchParams();
Then you can manipulate the search params anywhere, in your case:
setSearchParams({ search: '...' );
Doing this along-side the navigate() call will basicallly lead the user to the page as the search param is being changed.
In your situation, I would detect if the user is already on the product list page before always running navigate(). You can use the useLocation() hook to detect what pathname you currently are on.
If you're on the product list page already, don't run navigate(). Your useEffect would be responsible for re-rendering the page when search params change using a dependency array [searchParams.search]:
useEffect(() => {
// do stuff when search params "search" variable changes
}, [searchParams.search])

Breadcrumbs nav with a page state react

I'm trying to implement stated linked breadcrumbs so in case the user will use a filter on a page and then will move to a new page the new breadcrumb will link to the previous page with a URL including hash filters.
For example:
first page with filters - URL: "localhost:3000/shop#cars"
second page - URL: "settings"
when the user will navigate from the first page to the second page a new breadcrumb will be created with a link to: "localhost:3000/shop#cars" (and not to localhost:3000/shop)
my code for now:
const breadcrumbs = useMemo(() => {
const pages: string[] = location.pathname.split("/")
return pages.map((p: string, index: number) => {
return {
page: p,
pathname: pages.slice(index).join("/"),
filters: pages.at(-1) === p ? location.hash : "",
}
})
}, [location.pathname, location.hash])
the issue here is with the filters every new render will override all except the last one
I'm using react-router V6

Call lightning Web Component from URL and capture parameter from URL

I want to redirect user from one LWC to another by clicking on URL in experience cloud. Basically, on my first LWC I am showing the list of records retrieved from Apex and then when any row is clicked, I want to pass recordId of that row and redirect to second LWC which will show the record details page. How can I achieve this?
Do you have a Community... sorry, Experience Builder page with that target LWC dropped?
You could use NavigationMixin although documentation says communities don't support the state property.
On source side try something like
/* Assumes the link/button clicked looks like
<button data-id={acc.Id} onclick={handleClick}>Get details</button>
*/
handleClick(event){
if(event.currentTarget.dataset.id){
this[NavigationMixin.Navigate]({
type: 'comm__namedPage',
attributes: {
name: 'MyTargetCommunityPage__c'
},
state: {
id: event.currentTarget.dataset.id
}
});
}
}
And then on target page's LWC it should be accessible as
connectedCallback() {
if(window.location.href){
try{
let url = new URL(window.location.href);
var id = url.searchParams.get('id');
if(id){
this.myId = id;
}
} catch(e){
if(console){
console.log(JSON.stringify(e));
}
}
}
}
If Navigation gives you hard time you can ditch it and go straight for window.open('/pagename?id=123', _self); or something. It's bit annoying that you have 1 set of rules for core Salesforce, 1 for community.
(if you have a namespace - you might need to use myns__id in the state instead of id)

React-admin: How to create user profile form in react-admin v3.2.*?

I've noticed that the official article on how to create user settings (profile) in React-Admin is outdated (https://marmelab.com/blog/2019/03/07/react-admin-advanced-recipes-user-profile.html).
I followed the example and tried to use a new DataProvider, but couldn't get Edit view working (it just showed blank Card component with no fields even though I've set them in a way that's described in the example).
I was searching for several days on how to implement it in a simplest/clean way, but there's a very small amount of information about it.
Does somebody know how to do it in react-admin 3.2.*?
It might be helpful for others who have the same issue.
Any help will be very appreciated! Thanks!
I had the same problem. Looking at the props passed toreact-admin's Edit, I saw the record prop was undefined. It was because the id field inside the record returned by the data provider's getOne method was different than the id prop hardcoded on the Edit component. Once that was set to match, both reading/editing works.
My working code:
// remove staticContext to avoid React 'unknown prop on DOM element' error
export const PrincipalEdit = ({ staticContext, ...props }: { staticContext: any; props: any }) => {
return (
// `id` has to match with `id` field on record returned by data provider's `getOne`
// `basePath` is used for re - direction
// but we specify no redirection since there is no list component
<Edit {...props} title="My Profile" id="my-profile" resource={b.PRINCIPAL} basePath="my-profile" redirect={false}>
<SimpleForm>
<TextInput source="firstName" validate={required()} />
</SimpleForm>
</Edit>
);
};
The issue is how the data is stored by react-admin at the moment (haven't checked how it was before). Now each data entry is saved by its id in the store object (the reasons are obvious). I think the best approach is to modify the data provider.
if (resource === 'profile') {
const { id, ...p } = params; // removes the id from the params
if (p.data)
delete p.data.id; // when updates there is data object that is passed to the backend
return dataProvider(type, resource, { ...p, id: '' }) // set the id just empty string to hack undefined as http param
.then(({ data }) => {
return Promise.resolve({
data: {
...data,
id // return the data with the initial id
}
});
});
}
This way the backend endpoint could return just the object at the main endpoint of the entity /profile. If you do not set the id prop to '' it will try to fetch /profile/undefined because the id prop was deleted from the params object. If you do not delete the id prop from the data object when updating, depending on the backend sql query (assuming you are using some kind of db) for updating a record, it may try to set or search by this id.
In the Edit component you can pass whatever id you want but something must be passed.
Additional:
If you are using NestJS as backend with the Crud package, these #Crud params may be helpful
...
params: {
id: { // the routes won't have params, very helpful for me/profile controller
primary: true,
disabled: true,
},
},
routes: {
only: ["getOneBase", "updateOneBase"],
},
...

React Router: Query Param Match?

According to the accepted answer to this question, React Router 4 doesn't match query parameters anymore. If I go from a URL matched by one of my <Route>s to the same URL with a different query string, the content doesn't seem to change. I believe this is because navigating between URLs that match the same <Route> doesn't change the content, but please correct me if I'm wrong. Given this, how do I use React Router for a set of URL's that need to differ only by query parameter?
For example, many search engines and other sites that use search bars, including the site I am working on, use a query parameter, commonly q or query. The user may search for one thing, then decide that is not what he/she wants and search for another thing. The user may type in the second URL or search with the search bar again. There isn't really a place for the search term in the URL path, so it kind of needs to go in the query string. How do we handle this situation?
Is there a way, with React Router, to link to a URL that only differs in the query string and change the content, without refreshing the entire page? Preferably, this wouldn't require any external library besides React and React Router.
Try the render function prop instead of component prop of Route. Something like this:
<Route render={props => {
// look for some param in the query string...
const useComponentA = queryStringContains('A');
if(useComponentA) {
return <ComponentA {...props}/>;
} else {
return <ComponentB {...props}/>;
}
}}/>
There are 2 ways to do that:
1) Use location.search in react component to get the query string, then pass it to child component to prevent re-rendering the whole component. React-router has the official example about this.
2) Define a regex path of router to catch the query string, then pass it to react component. Take pagination as an example:
routes.js, for router config you can refer this
const routerConfig = [
{
path: '/foo',
component: 'Foo',
},
{
path: '/student/listing:pageNumber(\\?page=.*)?',
component: 'Student'
},
Student.js
render() {
// get the page number from react router's match params
let currentPageNumber = 1;
// Defensive checking, if the query param is missing, use default number.
if (this.props.match.params.pageNumber) {
// the match param will return the whole query string,
// so we can get the number from the string before using it.
currentPageNumber = this.props.match.params.pageNumber.split('?page=').pop();
}
return <div>
student listing content ...
<Pagination pageNumber = {currentPageNumber}>
</div>
}
Pagination.js
render() {
return <div> current page number is {this.props.pageNumber} </div>
}
The 2nd solution is longer but more flexible. One of the use cases is server sider rendering:
Apart from the react components, the rest of the application (e.g. preloaded saga) need to know the url including query string to make API call.

Resources