I had some alert components when each clicked; it will get redirected to a page
<div className="question11">
{data.map((itm) => (
<Link
key={itm._id}
href={{
pathname: "/[itm]",
query: { id: itm._id },
}}
as={`/${encodeURIComponent(
itm.Name.replace(/[^a-zA-Z0-9 - _ . ~]/g, "").replace(
/ /g,
"-"
)
)}`}
>
<Alert className="question13">{itm.Name}</Alert>
</Link>
))}
</div>
The redirected page has a URL in the following pattern
http://localhost:3000/itm.Name. Example: http://localhost:3000/spiderman-no-way-home-release-date-in-india. I am passing itm._id for accessing the corresponding data on the redirected page
export async function getServerSideProps(context) {
var id1 = context.query.id;
// console.log(context.query.id);
const queryRequest = fetch("https://askover.wixten.com/questone/" + id1).then(
async (res) => await res.json()
);
When I click on alert components, I can pass the itm._id, and the page is redirected properly. The issue occurs when I manually enter the URL in the browser.The issue here is not getting the itm._id from the alert component. The answer that I came up with here is to create an API to access the API by passing the itm.Name, but that will require deconstructing the itm.Name to its original form, and itm.Name might not be unique every time is there another method by which I can access itm._id itself also, if I can use the URL in http://localhost:3000/itm._id/itm.Name
this format also, I think it will be okay just as StackOverflow does it.
When you refresh the page you will lose the context, even if you use some store(local, session, etc) that will not work for the user visiting your app for the first time.
One thing always remains is URL, neither storage nor context.
To solve this kind of issue, what you can do is pass the id and slug parameters to the URL and read whenever requires.
Check more details here
Next.js Directory
pages
index.js
[id]
[slug].js
The URL will look something like this: https://localhost:3000/123/my-post-slug
, Slug is optional, It'll help for SEO purposes.
[slug].js
const Component = (props) => (
<div>
<h1>{props.title}</h1>
<p>{props.content}</p>
</div>
);
export async function getServerSideProps(context) {
const id = context.params.id;
const data = fetch(`https://askover.wixten.com/questone/${id}`).then((res) => await res.json());
return {
props: data,
}
}
Related
I am stuck on an issue. Let's say I have a home page. From this home page, I want to route to either page A or page B.
I have a list of items on the home page and what I need to do is when I click on any item in the list, it makes a GET API call, and based on one field in the response which is a boolean, I either need to redirect to page A or page B.
Basically, I need to call an API on the click of the item and get the response before it is routed to either Page A or Page B.
Many thanks in advance
if you're using Next.JS, use useRouter prop to achieve this.
for example
import {useRouter} from "next/router";
export default function Page() {
const router = useRouter()
async function route() {
let res = await apiFunctionCall();
if (res) {
await router.replace({
pathname: '/page1'
})
} else {
await router.replace({
pathname: 'page2'
})
}
}
}
The closest solution I could get is to create a Lazy loaded component which calls the API and then based on the response, routes to either page A or Page B.
Before routing to the correct page based on the data that has been returned, the data first needs to be stored in a variable. The data will be stored in the res variable. The if else statement will then route to either page A or B based on the data.
<code>const Response = res.data;
if(Response.type === 'A'){
this.props.history.push('/page-a');
} else {
this.props.history.push('/page-b');
}
</code>
I am using the Next.js Router to enable different dashboards depending on the URL slug. This works when a button with the link is clicked, as the link passes the information to the Next.js Router, but it does not work when a URL is inputted directly into the browser (i.e. it does not work if the user refreshes the page, or just types the URL with the slug directly). So, while I am able to use dynamic routes when links are pressed, how can I enable dynamic routes when a link is not pressed?
The relevant code is below. Thank you very much
const dashboard = () => {
const router = useRouter();
const {dashboardID} = router.query;
return (
<Dashboard dashboardID = {dashboardID}/>
);
}
export default dashboard
On pagination the query already loaded on the context/custom hook.
You need to wait until router fully loads
const dashboard = () => {
const router = useRouter();
const {dashboardID} = router.query;
if(!dashboardID) return null //Wait until query with dashboardID loads to avoid undefined errors
return (
<Dashboard dashboardID = {dashboardID}/>
);
}
export default dashboard
I've got a list of names fetch from a external API called SWAPI and I would like to get a single detail character. I'got to connect the data and the list is working perfectly but when I click and sends me to the single detail character, doesn't recognise which character id, it says undefined. I can get the page with the character but not with the corresponding id.
<ul>
{data.results.map((result) => (
<div key={result.episode_id} className="column">
<Link
href={`/character/?id=http://${result.id}`}
>
<h4 className="card-title">
<a>{result.name}</a>
</h4>
</Link>
</div>
))}
</ul>
export async function getServerSideProps() {
const res = await fetch(`https://swapi.dev/api/people/`)
const data = await res.json()
return { props : {data} }
}
I tried to route as a Link with href to send me to the single character with the ${result.id} to fetch only the id but it doesn't like the solution.
It sends me to the page character but doesn't recognise the id, it says undefined.
I read the docs of dynamic routes but they didn't explain much about how dynamic routes will work with "catching all routes".
My folder structure for this route is:
└──pages
└──catalog
└──[[...slug]].js
Here's my code:
export default function Catalog(props) {
return (
<Product product={props.product} />
)
}
export async function getStaticProps({ params }) {
const productSlug = params.slug[params.slug.length-1];
const data = await getSingleProduct(productSlug)
return {
props: {
product: data.product,
},
revalidate: 30
}
}
My API is WP and I have product pages URI like this /catalog/category/sub-category/product/
So if I go to the URL /catalog/category/sub-category/product/ it works fine with the code I shared below because I have const productSlug = params.slug[params.slug.length-1]; which will get my slug which I can pass to the API and use the product data just fine.
But I want to work with categories too, so if I go to /catalog/category/sub-category/ it should load the category page, and if I go to /catalog/category/ it should load up that category page.
Even this will work with the code I have because I'm getting the last element of params array which is the product slug, but that's NOT always the case. Sometimes the product is without any sub-category so the URI would be /catalog/category/product which means I can't fix it to the third element of the array and use the other two as category slugs.
The params gives me an array without any key or anything and I can't seem to figure out how to achieve this in next.js
Any help is appreciated!
I have this scenario.
A user types in /company/<company-id> in the address bar.
Since the app is totally separate from the backend, it needs to prefetch the companies.
Normal user flow is /login -> /company/. I handle this case pretty well and just navigate to /company/<whatever id is first in the prefetch> with no problems.
But what if you load WITH the id? I have solution but I think I have a feeling that I'm misunderstanding something in routing.
You may assume that my prefetching works and the code snippet below will only trigger if companyState.success is true. Like i said, it is working.
I handled this manually, by
// pretty sure i can handle this with regex better to capture other cases
// but that's beside the point for the scope of this question
const urlId = +location.pathname.replace("/company/", "")
const checkCompany = !!companyState.data.find(d => d.id === urlId)
if(checkCompany){
company.set(urlId)
}
else{
navigate("/404")
}
I have hooks in place where in if company.set(<company:id>) does update, it will pre-fetch everything else needed for the view. And, company is a custom context hook so that it's present everywhere in my application.
Is there a better way in handling this? It seems hack-y to manually check the path name.
You can assume that my gatsby_node.js has the right definitions to allow the client side routing.
Here's my routing definitions: (this is what i put in the pages folder)
const DashboardPage = () => (
<ProtectedRoute>
<Router>
<Company path="/company/*" />
</Router>
</ProtectedRoute>
)
Finally in the components folder,
const Company = ({location}) => (
<Router>
<Main path="/:companyId">
<Summary path="/" />
.... other dashboard routes
</Main>
</Router>
)
You have to assume that client-side code can always be changed by a malicious actor. Ultimately, you have to make sure on the backend that a user can only request ressources he is supposed to see.
Your solution for the client-side seems fine to me. I don't see another way than checking the URL path manually and then redirecting.
By logging in, your user needs to be assigned to a cryptographically safe cookie or token (such as JSON web tokens) so you can always be sure of their identity. Everytime a company id is routed to, your frontend needs to send the user identity to your backend. Only there you can be safe from code manipulations. Your backend needs to check if the user can look at this page.
If the user can: your backend sends the page data
If the user can't: your backend sends "not authorized" message and your frontend redirects
This way even if someone manipulates your client-side code and cancels the redirect, the hacker will stare at a useless blank page.
In summary:
Your approach on the client-side is fine. Make sure your backend checks the identity of your user before sending the company data.
So instead of me manually handling the routing, I solely used ReachRouter's navigate to simulate history.push() to avoid re-rendering of the whole page. (similarly just a state change with the benefit of keeping track of history ). Backend is fully protected with auth tokens so no need to worry about that.
My strategy below will handle these cases:
User types in or app navigates to /company ( app will pre fetch > get first company by default > navigate to /company/{id}
User types in or app navigates to /company/{id} ( app will pre-fetch > navigate to /company/{id} > component triggered by that route will check validity of id else navigate to 404 )
The strategy I created was,
Create a component that will load up a loading screen by default and prefetch the said companies.
Call the said component in the pages folder as the default, in my case, /company should be pages > company.js or pages > company > index.js
If the prefetch is successful, navigate to /company/{id} which is the child of the said component, which is a purely client route.
make sure gatsby-node.js have the necessary createPages definition to allow client routing for everything /company/*
manually check if the current location is /company to deny redirection to capture the second case when user types in /company/{id}
Better if I show the code, ignore my custom built in hooks.
useRequest just gives an axios class, with the true parameter telling it's an authenticated required request.
useApi just gives a handy (async-await) function that includes dispatch for the redux states and calling the api itself. I'm using thunk, so success and error are standard.
const CompanyDefault = ({location}) => {
const [loading, toggle] = useState(true)
const request = useRequest(true)
const companyApi = useApi(request, getCompanies)
const companyState = useSelector(({company}) => company)
const redirectToCompany = async () => await navigate(clientRoutes.COMPANY(companyState.data[0].id))
const fetchCompanies = async () => {
await companyApi()
toggle(false)
}
useEffect(() => {
if(companyState.data.length === 0){
fetchCompanies()
}
}, [])
if(loading){
return <Loading />
}
else if(companyState.error || companyState.data.length === 0){
return <Error callApi={companyApi} />
}
else if(companyState.success && (location.pathname === "/company" || location.pathname === "/company/")){
redirectToCompany()
}
return (
<Router>
<Company path="company/:companyId/*" />
</Router>
)
}
export default CompanyDefault
Thus, the company module will be just
const Company = ({companyId}) => (
// you now have companyId!
// do magic here, and check if that company id is valid.
<Router>
<Main path="/">
<Summary path="/" />
.... other dashboard routes
</Main>
</Router>
)
Hopefully this is cleaner. If there's a better way do let me know! :D