How to call getServerSideProps() in nested pages in Next.js? - reactjs

I have 2 pages (parent and child) with dynamic routes, and I want to call getServerSideProps() in the child only.
The first page, [Post].js:
import React from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'
import Index from './index'
const Post = ({ children, making }) => {
return (
<Index>
{making
? (
<div>
<p>Making ID is: {making.id}</p>
{children}
</div>
)
: (<p>Item not found</p>)}
</Index>
)
}
Post.propTypes = {
children: PropTypes.node,
making: PropTypes.object,
}
export const getServerSideProps = async ({ query: { makingID } }) => {
const url = `http://localhost:1337/makings/${makingID}`
const res = await axios.get(url)
const making = res.data
return { props: { making } }
}
export default Post
The second page (the parent), [tabs].js:
import React from 'react'
import { useRouter } from 'next/router'
import Post from '../[Post]'
const tabs = () => {
const router = useRouter()
const { tabs } = router.query
return (
<Post>
hello {tabs}
</Post>
)
}
export default tabs
But if I use Post as a component, then getServerSideProps() does not work. I could use the function in tabs, but I want it in the child only.

getServerSideProps function can be used only in a page. You can't use it in components. Instead, you can pass fetched data as props to page components.
Also, pages can't be nested. If pages share same components, then you can include these components in both pages.

Related

mapStateToProps react router dom v6 useParams()

BlogDetailsPage.js
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
const BlogDetailsPage = (props) => {
const { id } = useParams();
return <div>Blog Details: {}</div>;
};
const mapStateToProps = (state, props) => {
const { id } = useParams();
return {
blog: state.blogs.find((blog) => {
return blog.id === id;
}),
};
};
export default connect(mapStateToProps)(BlogDetailsPage);
How to use mapStateToProps in "useParams()" react-router-dom ?
and whatever links that navigate to /slug path are ended up in BlogDetailsPage.js, Since BlogDetailsPage.js is being nested nowhere else so i couldn't get specific props pass down but route params. From my perspective this is completely wrong but i couldn't figure out a better way to do it.
Compiled with problems:X
ERROR
src\components\BlogDetailsPage.js
Line 11:18: React Hook "useParams" is called in function "mapStateToProps" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use" react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.```
Issue
React hooks can only be called from React function components or custom React hooks. Here it is being called in a regular Javascript function that is neither a React component or custom hook.
Solutions
Preferred
The preferred method would be to use the React hooks directly in the component. Instead of using the connect Higher Order Component use the useSelector hook to select/access the state.blogs array.
Example:
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
const BlogDetailsPage = () => {
const { id } = useParams();
const blog = useSelector(state => state.blogs.find(
blog => String(blog.id) === id
));
return <div>Blog Details: {}</div>;
};
export default BlogDetailsPage;
Alternative/Legacy
If you have the need to access path params in any mapStateToProps function, if you are using a lot of oder code for example, then you'll need to create another HOC to access the path params and have them injected as props so they are available in the mapStateToProps function.
Example:
import { useParams, /* other hooks */ } from "react-router-dom";
const withRouter = Component => props => {
const params = useParams();
// other hooks, useLocation, useNavigate, etc..
return <Component {...props} {...{ params, /* other injected props */ }} />;
};
export default withRouter;
...
import { compose } from 'redux';
import { connect } from 'react-redux';
import withRouter from '../path/to/withRouter';
const BlogDetailsPage = ({ blog }) => {
return <div>Blog Details: {}</div>;
};
const mapStateToProps = (state, { params }) => {
const { id } = params || {};
return {
blog: state.blogs.find((blog) => {
return String(blog.id) === id;
}),
};
};
export default compose(
withRouter, // <-- injects a params prop
connect(mapStateToProps) // <-- props.params accessible
)(BlogDetailsPage);
I think, react hook functions are allowed to use inside of react component.
Outside of react components, it's not allowed to use react api hook functions.
Thanks, I'd liked to help you my answer.

How do I pass a URL Param to a selector

I recive a url param from useParams. I want to pass it to a selector using mapStateToProps.
collection.component.jsx
import { useParams } from "react-router-dom";
import { connect } from "react-redux";
import { selectShopCollection } from "../../redux/shop/shop.selectors";
import './collection.styles.scss'
const Collection = ({ collection }) => {
const { collectionId } = useParams();
console.log(collection)
return (
<div>
<h1>{collection}</h1>
</div>
)
}
const mapStateToProps = (state, ownProps) => ({
collection: selectShopCollection(ownProps.match.params.collectionId)(state)
})
export default connect(mapStateToProps)(Collection);
shop.selectors.js
import { createSelector } from "reselect"
const selectShop = state => state.shop
export const selectShopCollections = createSelector([selectShop], shop =>
shop.collections
)
export const selectShopCollection = collectionUrlParam =>
createSelector([selectShopCollections], collections =>
collections.find(collection => collection.id === collectionUrlParam)
)
I guess the problem is that, I cannot pass params using match as react-router-dom v6 does not pass it in props. Is there any other way to pass collectionId to the selector selectShopCollection?
Since Collection is a function component I suggest importing the useSelector hook from react-redux so you can pass the collectionId match param directly. It simplifies the component API. reselect selectors work well with the useSelector hook.
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { selectShopCollection } from "../../redux/shop/shop.selectors";
import './collection.styles.scss'
const Collection = () => {
const { collectionId } = useParams();
const collection = useSelector(selectShopCollection(collectionId));
console.log(collection);
return (
<div>
<h1>{collection}</h1>
</div>
)
};
export default Collection;
Collection component can be given props by withRouter. But it was deprecated with react-router v6. Hence we need to create our own HOC which wrap our component.
I created a HOC like this:
import { useParams } from "react-router-dom"
const withRouter = WrappedComponent => props => {
const params = useParams()
return (
<WrappedComponent {...props} params={params} />
)
}
export default withRouter;
See this answer for How to get parameter value from react-router-dom v6 in class to see why this HOC was made.
And, we can import the withRouter to the component and use with connect inside compose. Read more on compose. It just returns final function obtained by composing the given functions from right to left.
const mapStateToProps = (state, ownProps) => ({
collection: selectShopCollection(ownProps.params.collectionId)(state)
})
export default compose(withRouter, connect(mapStateToProps))(Collection)

React Context not updating value to pass to another page

I am creating an ecommerce app with Nextjs and want to share data between pages. I know that we can't use props to pass data between the pages and so was looking into react context api. This is my first time using react context api. I've researched and found that you should add the Provider in the _app.js page in nextjs.
But this shares the data among all the pages. Plus my data is being retrieved by getStaticProps in the slug page of the app. I want to get this data into the checkout page of my app.
This is the context I have created:
import { createContext, useState, useContext } from 'react';
const productContext = createContext({} as any);
export const ProductProvider = ({ children }) => {
const [productData, setProductData] = useState('');
return <productontext.Provider value={{ productData, setProductData }}>{children}</productContext.Provider>;
};
export const useProduct = () => useContext(productContext);
_app.js
import { ReportProvider } from '../contexts/ReportContext';
export default function CustomApp({ Component, pageProps }) {
return (
<ReportProvider>
<Component {...pageProps} />
</ReportProvider>
);
}
I import this into the slug page and try to update the state from here
// [slug].js
import client from '../../client'
import {useProduct} from './productContext';
const Post = (props) => {
const {setProductData} = useProduct();
const { title = 'Missing title', name = 'Missing name' , price} = props
setProductData(title);
return (
<article>
<h1>{title}</h1>
<span>By {name}</span>
<button>
Buy Now
</button>
</article>
)
}
Post.getInitialProps = async function(context) {
const { slug = "" } = context.query
return await client.fetch(`
*[_type == "post" && slug.current == $slug][0]{title, "name": author->name, price}
`, { slug })
}
export default Post
However this productData is not accessible from another page and the react context state is not getting updated.
Any idea why this could be happening?
Once you've updated your context value. Please make sure you are using next/link to navigate between pages. Here is details about next/link

How to use useParams in mapStateToProps?

I want specific prop from route params and use it to filter data in redux-store.
Product.js
import React from 'react';
import { useParams } from 'react-router-dom';
import { connect } from 'react-redux';
const Product = (props) => {
let { slug } = useParams();
//console.log(props.match)
return (
<div>
<h3>Welcome to <b>{ slug }</b> page</h3>
</div>
)
}
const mapStateToProps = ( state, ownProps ) => {
// let id = slug;
return { item: state.items[0]}
}
export default connect(
mapStateToProps
)(Product);
App.js
function App() {
return (
<Router>
<Navbar/>
<Switch>
<Route exact path="/:slug">
<Product/>
</Route>
<Route exact path="/">
<Home/>
</Route>
</Switch>
</Router>
);
}
and whatever links that navigate to /slug path are ended up in Product.js, Since Product.js is being nested nowhere else so i couldn't get specific props pass down but route params. From my perspective this is completely wrong but i couldn't figure out a better way to do it.
Since you are using the new version of React and Redux. You can try use Hook to get data from redux store.
Better call useSelector instead. Read more here
import React from "react";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
const Product = () => {
let { slug } = useParams();
const item = useSelector((state) => state.items[slug]);
console.log(item);
return (
<div>
<h3>
Welcome to <b>{slug}</b> page
</h3>
</div>
);
};
export default Product;
In your case, you could use the mapDispatchToProps property which is the second argument of connect
Product.js
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { connect } from 'react-redux';
const Product = (props) => {
const { slug } = useParams();
const {
items, // From mapStateToProps
filterItems // From mapDispatchToProps
} = props;
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
setFilteredItems(filterItems(items, slug));
});
return (
<div>
<h3>Welcome to <b>{ slug }</b> page</h3>
<!-- {filteredItems.map(item => { })} -->
</div>
)
}
const mapStateToProps = ( state, ownProps ) => {
return { items: state.items}
}
const mapDispatchToProps = dispatch => {
return {
filterItems: (items, filter) => {
// TODO: Filter logic goes here...
return items;
}
}
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Product);
Another performant solution is to use withRouter
You want to access the state and routing params in one place to select what you want from the state.
One solution to it is using useParams inside the component and while you access the state using connect it's fine.
However, I once found that this solution causes my component to re-render a lot because we don't quietly control how useParams being re-invoked, I prefer using the HOC(higher order component) that react-router-dom offer which is called withRouter (which I found more performant) and here is how to use it
You wrap it around connect
import { withRouter } from "react-router-dom";
...
export const ArticlePageContainer = withRouter(
connect(mapStateToProps, undefined)(ArticlePageUI)
);
then you can access the slug or any params from inside the props in mapStateToProps function
function mapStateToProps(state, props) {
const slug = props.match.params.slug;
return {
targetArticle: state.items.find((item) => item.slug == slug)
};
}
Finally, you use that selected piece of data as in your component, where you get it from the props directly now.
function ArticlePageUI(props) {
return (
<>
<p>{"Article Page"}</p>
<p>{props.targetArticle?.content}</p>
</>
);
}
Here's a code sandbox where you can check the implementation yourself
https://codesandbox.io/s/stackoverflowhow-to-use-useparams-in-mapstatetoprops-qxxdo?file=/src/article-page.js:87-225

pass data to a Layout component gatsbyJS

I want to pass data to layout Component, the data is from an API, cockpitCMS to be exact and the data is slug to be exact too.
io have tried this
import { useStaticQuery, graphql } from "gatsby"
export const slugs = () => {
const { data } = useStaticQuery(
graphql`
query slug{
allCockpitPost {
edges {
node {
title {
slug
}
}
}
}
}
`
)
return data.allCockpitPost.edges.node.title
}
but, I get this instead... React Hook "useStaticQuery" is called in function "slugs" which is neither a React function component or a custom React Hook function
maybe because we can't use usestaticQuery twice, and it is already been used in the SEO component.
Your problem here is the way you use hook. Basically you have some ways to use useStaticQuery or any hook in a function:
That function NEED to return a React Component.
Other wise, that should be a HOC.
Example code for first way:
import AnyComponent from '../some-where';
const MockComponent = (props) => {
const { mockData } = useStaticQuery(anyQuery);
return (
<AnyComponent mockData={mockData}>
)
}
export default MockComponent
Example code for second way:
const withMockHOC = (AnyOtherComponent) => (props) => {
const { mockData } = useStaticQuery(anyQuery);
return <AnyOtherComponent {...props} mockData={mockData} />;
};
Hope this help.

Resources