Pass data from nextjs Link component and use it in getStaticProps - next.js

I have a dynamic route and I am trying to show the title in url and pass (hide) the id to my dynamic route and use id in getStaticProps. I have found that I can't pass data easily in nextjs as we used to pass with react router or other client routing libraries.
I am following this answer but when I console.log(context.params) I can't see the id passed from Link, what am I doing wrong here ?
// index.js
<Link
href={{
pathname: `/movie/[title]`,
query: {
id: movie.id, // pass the id
},
}}
as={`/movie/${movie.original_title}`}
>
<a>...</a>
</Link>
// [movieId].js
export async function getStaticProps(context) {
console.log(context.params); // return { movieId: 'Mortal Kombat' }
return {
props: { data: "" },
};
}

The context parameter is an object containing the following keys:
params contains the route parameters for pages using dynamic routes. For example, if the page name is [id].js , then params will look like { id: ... }. - Docs
export async function getStaticProps(context) {
console.log(context.params); // return { movieId: 'Mortal Kombat' }
return {
props: {}, // will be passed to the page component as props
}
}
If the dynamic page looks like /pages/[movieId].js you can access pid in context.params.movieId .
You can't access "Query String" in getStaticProps because as the docs state,
Because getStaticProps runs at build time, it does not receive data that’s only available during request time, such as query parameters or HTTP headers as it generates static HTML.
If you need "Query String", you can make use of getServerSideProps,
export async function getServerSideProps(context) {
console.log(context.query);
return {
props: {}, // will be passed to the page component as props
}
}
Edit:
About that Link, you should pass the same value as a key for query which you use for dynamic pathname:
<Link
href={{
pathname: `/movie/[title]`,
query: {
title: movie.id, // should be `title` not `id`
},
}}
as={`/movie/${movie.original_title}`}
>
</Link>;
And then in /pages/[title].js,
export async function getStaticProps(context) {
console.log(context.params); // return { title: 'Mortal Kombat' }
return {
props: {}, // will be passed to the page component as props
}
}
Note how title is being used as the key, both in filename and the query object in Link.

Related

Next.js: undefined props in URLs set in the rewrites of next.config.js

Hello very good morning,
I'm building an App with Next.js and I'm facing the following problem.
I want to get the data rendered from the client on a page and I have the following:
// pages/news.jsx
export default function News ({ title }) {
console.log('title: ', title)
return <div>{title}</div>
}
export async function getServerSideProps () {
return {
props: {
title: 'hello!'
}
}
On the other hand, in the next.config.js, I have a 'rewrites', so that when using the URL 'es/noticias' it goes to '/news':
i18n: {
locales,
defaultLocale: 'en',
localeDetection: false
},
async rewrites () {
return [{"source":"/noticias", "destination":"/news"}]
}
NOTE: only he put the most important of the code.
The problem I find is that using the URL '/news' the 'title' props is always printed by console, both on the server and client side, but from the URL 'es/noticias' on the client side it always outputs 'undefined'.
This happens when I use the Link component, if I use an 'a' (anchor) it works, but I want to use the Link component.
Any ideas? Thank you!

RTK-Query meta response always empty object

When using RTK-Query to call an API, I need to access a custom header value. I've tried various options for accessing the response headers from the documentation, but all attempts result in an empty object returned:
meta: {
"request": {},
"response":{}
}
I can see in the network tab, that the headers are provided in the response. This is part of a refactor from using raw Axios calls where the header was available from the response object as the headers property.
I've tried accessing the headers through the meta parameter on the transformResponse function of the createApi
transformResponse: (response, meta, arg) => {
console.log(`Transform -> meta: ${JSON.stringify(meta)}`)
// dataKey: meta.response.headers['x-company-data-key'],
return {
...response,
// dataKey
}
}
I've also tried accessing the headers via the meta property of the action parameter from the extraReducers function in the feature slice:
extraReducers: (builder) => {
builder.addMatcher(companyApi.endpoints.getSomeData.matchFulfilled, (state, action) => {
console.log(`action meta: ${JSON.stringify(action.meta)}`)
state.result = action.payload.result
// state.dataKey = action.meta.response.headers['x-company-data-key']
})
}
Both instances result in the meta object that looks like this:
meta: {
"request": {},
"response": {}
}
The API's base query looks like this:
baseQuery: fetchBaseQuery({
baseUrl: process.env.REACT_APP_API_ENDPOINT,
prepareHeaders: ( (headers, { getState }) => {
const realm = getState().companyAuth.realm
if (realm) {
const token = readToken(realm)
if (token)
headers.set('Authorization', `Bearer ${token.access_token}`)
}
return headers
})
}),
and finally, the endpoints definition for this API endpoint:
getCompanyData: builder.query({
query: (params) => {
const { location, page, pageSize } = params
return {
url: `/companies/${location}`,
params: { page, pageSize }
}
}
})
Based on what I could find in the documentation, GitHub issues, and PRs, it seems that the meta property should automatically add the headers, status, and other additional HTTP properties. I'm not sure what I've missed where the response object is completely empty. Is there an additional setting I need to add to the baseQuery?
I should note, that the response data is working properly. I just need the additional information that is returned in the custom header.

NextJS case insensitive route for SSG pages

I am using NextJS to translate a CSV of data into static pages. Each page is pages/[slug].jsx. I'm calling toLowerCase() on the slug value inside [slug].jsx getStaticPaths() and getStaticProps() functions. The generated pages are lowercase. e.g. /ab101, /ab102, /cc500 all resolve to the pages/[slug].jsx page.
Unfortunately, people might hand type the url and may use caps or mixed case for the slug value, currently resulting in a 404.
QUESTION: How can I make routing case insensitive with respect to the slug value?
UPDATE
When I return fallback: true from getStaticPaths(), my [slug].jsx file is hit even when there is not an exact path match. I can then check isFallback as illustrated by Anish Antony below.
Additionally, the items param that is passed to my page will be undefined when the page wasn't found. The router's pathname value is "/[slug]" and not the value of "slug". However, there is an asPath value which contains useful data, e.g. /mixedCaseSlug?param=value&foo=bar.
When the page renders, I check if it's a fallback. If it is, show a LOADING... message. Next will display that and call getStaticProps() to generate the "missing" page. You'll then re-render with the page data. In the event that getStaticProps couldn't get page data, I push a path that will lead to the built-in 404 page.
export default function Page({ item }) {
const { isFallback, push } = useRouter()
const hasPageData = item && Object.keys(item).length > 0
useEffect(() => {
if (!isFallback && !hasPageData) {
push('/page-not-found/error')
}
}, [isFallback, hasPageData])
const loadingMsg = <div>Loading...</div>
const notFoundMsg = <div>Page not found</div>
return isFallback ? loadingMsg : hasPageData ? <Item item={item} /> : notFoundMsg
}
I needed to update getStaticProps() to lowercase the slug param, as it may now be mixed case, but we want to find our page data. And I needed to allow for the case when there really is no data for the slug.
export async function getStaticProps({ params }) {
const { slug } = params
const item = data.find(o => o.Practice_Code.trim().toLowerCase() === slug.toLowerCase())
return {
props: {
item: item ? item : {}
}
}
}
This all seems very kludgy, so I'm still wondering if there is a better way.
NextJS routes are case sensitive.You can use fallback property in getStaticPaths to catch the routes which aren't in the same case as the one provided by default in getStaticPaths.
Edit: I have updated the answer based on the discussion with Dave.
We can give fallback:true or fallback:"blocking" , if we give fallback:true we we can show a custom component which will be displayed till the time page is loaded.For fallback:"blocking" new paths not returned by getStaticPaths will wait for the HTML to be generated,
When we give fallback:true or "blocking" static page will be generated when the user first access the site and the generated page will be served for further visits.
Sample code
export async function getStaticPaths() {
const idList = await fetchAllIds();
const paths = [
];
idList.forEach((id) => {paths.push(`/posts/${id}`)})
return { paths, fallback: true };
}
What we have note is our code in getStaticProps should be case insensitive to get the data irrespective of the one provided in url.
export async function getStaticProps({ params }) {
const { slug } = params;
try {
/// This fetch api should be able to fetch the data irrespective of the case of the slug
or we should convert it to the required case before passing it as a parameter to API
const data= await fetchSlugDetails(slug);
//Same logic should be performed if you are getting data filtered based on slug from an existing array.
return data? { props: { data} } : { notFound: true };
} catch (error) {
console.error(error);
return { notFound: true };
}
}
Note: You have to handle the notfound case and fallback case if you are using fallback:true. For fallback you can get the value isFallback from next/router while the page is being static generated

React and Redux: redirect after action

I develop a website with React/Redux and I use a thunk middleware to call my API. My problem concerns redirections after actions.
I really do not know how and where I can do the redirection: in my action, in the reducer, in my component, … ?
My action looks like this:
export function deleteItem(id) {
return {
[CALL_API]: {
endpoint: `item/${id}`,
method: 'DELETE',
types: [DELETE_ITEM_REQUEST, DELETE_ITEM_SUCCESS, DELETE_ITEM_FAILURE]
},
id
};
}
react-redux is already implemented on my website and I know that I can do as below, but I do not want to redirect the use if the request failed:
router.push('/items');
Thanks!
Definitely do not redirect from your reducers since they should be side effect free.
It looks like you're using api-redux-middleware, which I believe does not have a success/failure/completion callback, which I think would be a pretty useful feature for the library.
In this question from the middleware's repo, the repo owner suggests something like this:
// Assuming you are using react-router version < 4.0
import { browserHistory } from 'react-router';
export function deleteItem(id) {
return {
[CALL_API]: {
endpoint: `item/${id}`,
method: 'DELETE',
types: [
DELETE_ITEM_REQUEST,
{
type: DELETE_ITEM_SUCCESS,
payload: (action, state, res) => {
return res.json().then(json => {
browserHistory.push('/your-route');
return json;
});
},
},
DELETE_ITEM_FAILURE
]
},
id
}
};
I personally prefer to have a flag in my connected component's props that if true, would route to the page that I want. I would set up the componentWillReceiveProps like so:
componentWillReceiveProps(nextProps) {
if (nextProps.foo.isDeleted) {
this.props.router.push('/your-route');
}
}
The simplest solution
You can use react-router-dom version *5+ it is actually built on top of react-router core.
Usage:
You need to import useHistory hook from react-router-dom and directly pass it in your actions creator function call.
Import and Creating object
import { useHistory } from "react-router-dom";
const history = useHistory();
Action Call in Component:
dispatch(actionName(data, history));
Action Creator
Now, you can access history object as a function argument in action creator.
function actionName(data, history) {}
Usually the better practice is to redirect in the component like this:
render(){
if(requestFullfilled){
router.push('/item')
}
else{
return(
<MyComponent />
)
}
}
In the Redux scope must be used react-redux-router push action, instead of browserHistory.push
import { push } from 'react-router-redux'
store.dispatch(push('/your-route'))
I would love not to redirect but just change the state. You may just omit the result of deleted item id:
// dispatch an action after item is deleted
dispatch({ type: ITEM_DELETED, payload: id })
// reducer
case ITEM_DELETED:
return { items: state.items.filter((_, i) => i !== action.payload) }
Another way is to create a redirect function that takes in your redirect Url:
const redirect = redirectUrl => {
window.location = redirectUrl;
};
Then import it into your action after dispatch and return it;
return redirect('/the-url-you-want-to-redirect-to');
import { useHistory } from "react-router-dom";
import {useEffect} from 'react';
const history = useHistory();
useEffect(()=>{
// change if the user has successfully logged in and redirect to home
//screen
if(state.isLoggedIn){
history.push('/home');
}
// add this in order to listen to state/store changes in the UI
}, [state.isLoggedIn])
A simple solution would be to check for a state change in componentDidUpdate. Once your state has updated as a result of a successful redux action, you can compare the new props to the old and redirect if needed.
For example, you dispatch an action changing the value of itemOfInterest in your redux state. Your component receives the new state in its props, and you compare the new value to the old. If they are not equal, push the new route.
componentDidUpdate(prevProps: ComponentProps) {
if (this.props.itemOfInterest !== prevProps.itemOfInterest) {
this.props.history.push('/');
}
}
In your case of deleting an item, if the items are stored in an array, you can compare the new array to the old.

How can I add data to the pending action?

I am using Axios, Redux and Redux Promise Middleware.
I have the following action creator:
return {
type: 'FETCH_USERS',
payload: axios.get('http://localhost:8080/users',
{
params: {
page
}
}
)
}
In my reducer for the FETCH_USERS_PENDING action, I would like to save the requested url in my state. How can I do this?
You want to use redux-promise-middleware's "meta" variable. Like so:
return {
type: 'FETCH_USERS',
meta: { url: 'http://localhost:8080/users' },
payload: axios.get('http://localhost:8080/users', config)
}
You could pass it through in your params, but that won't be returned until the page is fetched. Which means it won't be passed back during FETCH_USERS_PENDING.
And I'm pretty sure if you include directly in the return object (like how Lucas suggested), it will be stripped out of the FETCH_USERS_PENDING stage.
Here's the FETCH_USERS_PENDING stage from redux-promise-middleware:
/**
* First, dispatch the pending action. This flux standard action object
* describes the pending state of a promise and will include any data
* (for optimistic updates) and/or meta from the original action.
*/
next({
type: `${type}_${PENDING}`,
...(data !== undefined ? { payload: data } : {}),
...(meta !== undefined ? { meta } : {})
});
As you can see during this stage, the middleware returns the appended "type" attribute and it checks for "data" & "meta" attributes. If present, they are passed along within the action.
Here's the redux-promise-middleware source code if you want to look into it further.
Simply pass the URL in the action as well:
return {
type: 'FETCH_USERS',
url: 'http://localhost:8080/users',
payload: axios.get('http://localhost:8080/users', {
params: {
page
}
}
}
and access it in the reducer with action.url. Or you can leave the action as it is, and access it in the promise resolution:
action.payload.then(function(response) {
var url = response.config.url;
});

Resources