So im trying to get req query aka GET params from the url into server side rendering for basicly validation and / login for authendication for example shopify shop but i cant really verify it or parse the shop before its parsed to the renderes page aka a component
I have tried both geninitialprops and getserverside props but its not returning the values in the url like /app?mydata=thisisworking&hmac=21SA92
My code
// _app.js
export default function InvenGroupApp({ Component, pageProps }) {
const { pathname } = useRouter();
const isApp = pathname.startsWith('/app');
return (
<React.Fragment>
<Head>
<title>InvenGroup</title>
<meta charSet="utf-8" />
</Head>
<PolarisProvider theme={theme}>
{isApp
? <AppHandler Component={Component} {...pageProps} />
: <Component {...pageProps} />
}
</PolarisProvider>
</React.Fragment>
);
}
The direct component that should handle it
// #components/AppHandler.js
import React, { Component } from 'react';
import AppLayout from "#components/AppLayout";
import shopSession from '#lib/shop';
export async function getServerSideProps(context) {
return {
props: { context }
}
}
export default class AppHandler extends Component {
render () {
const { shop } = shopSession();
const AppPage = this.props.Component;
console.log(this.props);
if (shop) {
return (
<AppLayout Component={AppPage}/>
)
}
return (
<AppPage>
<div>Loading</div>
</AppPage>
)
}
}
To get the query parameters, you probably don't want to pass the full context object as props - way too much unclear and unnecessary data.
export async function getServerSideProps(context) {
// The query params are set on `context.query`
const { query } = context
return {
props: { query }
}
}
In your example url of /app?mydata=thisisworking&hmac=21SA92, this would give your class component this.props.mydata and this.props.hmac.
Related
I am try to use apollo-client with nextjs. Here I want to fetch data in getServerSideProps. Suppose I have 2 components and one page-
section.tsx this is component-1
const Section = () => {
return (
<div>
Section
</div>
);
};
export default Section;
mobile.tsx this is component 2
const Mobile = () => {
return (
<div>
Mobile
</div>
);
};
export default Mobile;
Now I call this two component into home page.
index.tsx-
const Home: NextPage = () => {
return (
<Container disableGutters maxWidth="xxl">
<Section />
<Mobile />
</Container>
);
};
export default Home;
export const getServerSideProps: GetServerSideProps = async () => {
const { data } = await client.query({ query: GET_USER_LIST })
return { props: {} }
}
Here you can see that in getServerSideProps I already fetch my data.
My question is How can I directly access this data form Section component and Mobile component without passing props. I don't want to pass props, because if my component tree will be more longer, then it will be difficult to manage props.
From appollo docs, I alreay know that apollo client do the same with redux state manager. So please tell me how can I access this data from any component that already fetched in getServerSideProps. Is it possible?. If not then how can what is the solutions.
How about using context api if you want to avoid prop drilling? By putting data into context, you can access it from any child component. Get the data from the SSR and put it into the context.
Below is the example
import React, {createContext, useContext} from "react";
export default function Home({data}) {
return <DataContext.Provider value={{data}}>
<div>
<Section/>
<Mobile/>
</div>
</DataContext.Provider>
}
export async function getServerSideProps() {
const data = 'hello world' //Get from api
return {
props: {data},
}
}
function Section() {
return <div>
Section
</div>
}
function Mobile() {
const context = useContext(DataContext);
return <div>
Mobile {context.data}
</div>
}
const DataContext = createContext({});
Now, as long as your tree structure grows within the DataContext provider, each child node will have access to data in the context.
Hope this helps.
Can you use useState (and other react hooks?) with Server Side Rendering? Everytime I am trying to run the code below I get the error TypeError: Cannot read property 'useState' of null. However, when I comment out the getServerSideProps function at the very bottom I have no problem running the code as intended. So my questions is can useState be used with Server Side Rendering in nextjs? If the answer is yes, then where am I going wrong in the code below?
import React from "react";
import { useRouter } from "next/router";
import useSelectedGenreInfoExtractor from "../../hooks/useSelectedGenreInfoExtractor";
import { useState } from "react";
import { useEffect } from "react";
import Navbar from "../../components/Navbar";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import Moviegenreresults from "../../components/Moviegenreresults";
export default function genre(props) {
const [myresultsfromhook, setMyresultsfromhook] = useState();
const [myreturnedmovies, setMyreturnedmovies] = useState();
const router = useRouter();
const { genre } = router.query;
if (genre == "Trending") {
let mymovies = useFetchTrendingCatagory();
console.log("This is a log of my props", props);
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
} else if (genre == "Top Rated") {
let mymovies = useFetchTopRatedCatagory();
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
} else {
let mymovies = useFetchMovieGenreResults(genre);
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
}
}
export async function getServerSideProps(context) {
if (context.params.genre == "Trending") {
let mymovies = useFetchTrendingCatagory();
return {
props: {
results: mymovies.results,
},
};
} else if (context.params.genr == "Top Rated") {
let mymovies = useFetchTopRatedCatagory();
return {
props: {
results: mymovies.results,
},
};
} else {
let mymovies = useFetchMovieGenreResults(genre);
return {
props: {
results: mymovies.results,
},
};
}
}
I think fundamentally the problem is the way you are using getServerSideProps.
Even thought the answer is you can not use useState inside getServerSideProps because this function run in the server, it is important to understand what getServerSideProps does and when, I think you can find very clear explanation about that in next docs.
https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props
Inside getServerSideProps use axios or the fetch api to get your data and pass it to the props.
I am not 100% sure but I thinnk inn your case you can also use Promise.all() to get the data from those three api calls.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
useState should be inside the component, it is a React hook. serverside functions are independent of React components.
I think the issue is the name of the component should be with capital letter:
// not genre
export default function Genre(props)
I have an auth context component where I'm wrapping my main app component, but at the same time I'm also trying to do page specific layout component per Next.js documentation here: https://nextjs.org/docs/basic-features/layouts#per-page-layouts
Am I doing this correctly, because I can't seem to be getting the data from my Context provider.
/context/AuthContext.js
const UserContext = createContext({});
export default function AuthContext({children}) {
// .. code
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
export const useUser = () => useContext(UserContext);
/_app.js
function MyApp({ Component, pageProps }) {
const getLayout = Component.getLayout || ((page) => page);
return getLayout(
<div>
<AuthContext>
<Component {...pageProps} />
</AuthContext>
</div>
);
}
export default MyApp;
/components/Project/List.js
import { useUser } from "../../context/AuthContext";
const ProjectList = () => {
const { user } = useUser();
console.log("get user data", user);
return (
<>
test
</>
);
};
export default ProjectList;
I'm trying to console log the user, but it's giving me undefined. I'm thinking it's because the way it's wrapped as a layout component? I could be doing this wrong. But I did console log inside my AuthContext for user, and the information there is correct.
/pages/projects/index.js
const Projects = () => {
// code goes here
return (
<div>
code goes here
</div>
)
}
export default Projects;
Projects.getLayout = function getLayout(page) {
return <ProjectLayout>{page}</ProjectLayout>;
};
When I remove the Projects.getLayout block of code, the data comes back, but when I add this code, data is gone.
/components/Project/Layout.js
const ProjectLayout = ({children}) => {
return (
<>
<ProjectList />
{children}
</>
}
export default ProjectLayout
With your current structure ProjectLayout isn't getting wrapped by the AuthContext, meaning you won't have access to its context.
You can modify your _app's structure and move the getLayout call around so that the context wraps it properly.
function MyApp({ Component, pageProps }) {
const getLayout = Component.getLayout || ((page) => page);
return (
<AuthContext>
{getLayout(<Component {...pageProps} />)}
</AuthContext>
);
}
Note that calling getLayout inside the Context could lead to errors if the getLayout function uses hooks or stuff that depends on a parent element.
It will be calling getLayout first and then the context, so the value of it will be initially the default (fallback) value (it's like doing foo(bar()) and expecting that foo will be called before bar).
To avoid this, return directly the component (using getLayout as a function that generates a component):
// /pages/projects/index.js
Projects.getLayout = (children) => (
// can't use hooks here, return the component immediately
<ProjectLayout>{children}</ProjectLayout>;
);
or use the layout as a component:
// /pages/projects/index.js
Projects.Layout = ({ children }) => {
return <ProjectLayout>{children}</ProjectLayout>;
};
// /pages/_app.js
export default function App({ Component, pageProps }) {
const Layout = Component.Layout || (({ children }) => children);
return (
<AuthContext>
<Layout>
<Component {...pageProps} />
</Layout>
</AuthContext>
);
}
Edit: The difference is more visible without JSX
// incorrect
return React.createElement(AuthContext, null,
// getLayout is called before AuthContext
getLayout(
React.createElement(Component, pageProps)
)
)
// correct
return React.createElement(AuthContext, null,
// Layout is called after AuthContext
React.createElement(Layout, null,
React.createElement(Component, pageProps)
)
)
I am making a simple Next Js application which has only two pages..
index.tsx:
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
As per the above code, on click Go to the about page it goes to about page and using query I also receive the passed query values in about page.
about.tsx
import React from "react";
import Router, { withRouter } from "next/router";
function About({ router: { query } }: any) {
return (
<div>
Candidate Id: <b> {query.candidateId} </b>
</div>
);
}
export default withRouter(About);
This displays the value but on page refresh while we are in /about page, the candidateId received gets disappeared.
Requirement: Kindly help me to retain the query value passed down from one page to another page even on page refresh.
Note: As per my requirement I should not display the canidateId on url while navigating and hence I am using as approach.. I know I can achieve it if I remove as but I cannot remove that here in index page while navigating.. Reason is this will lead to displaying candidateId in the url which is not intended..
Tried this solution: https://stackoverflow.com/a/62974489/7785337 but this gives empty query object on refresh of page.
Stuck for very long time with this please kindly help me.
If you do not want to use the query parameter you may need to create a "store" that saves your variable that persist throughout your pages.
Sample code as follows.
//candidatestore.js
export const CandidateStoreContext = createContext()
export const useCandidateStore = () => {
const context = useContext(CandidateStoreContext)
if (!context) {
throw new Error(`useStore must be used within a CandidateStoreContext`)
}
return context
}
export const CandidateStoreProvider = ({ children }) => {
const [candidateId, setCandidateId] = useState(null);
return (
<CandidateStoreContext.Provider value={{ candidateId, setCandidateId }}>
{children}
</CandidateStoreContext.Provider >
)
}
Then you need to wrap the Provider around your app like
<CandidateStoreProvider><App /></CandidateStoreProvider>
This way you can use anywhere as follows both in your index page and your about page.
const { candidateId, setCandidateId } = useCandidateStore()
UseContext
In your codes, it should probably look something like that.
import React from "react";
import Link from "next/link";
import { useCandidateStore } from './candidatestore'
export default function Index() {
const { candidateId, setCandidateId } = useCandidateStore()
useEffect(() => {
setCandidateId(thecandidateId)
})
return (
<div>
<Link
href={{
pathname: "/about",
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
function About({ router: { query } }: any) {
const { candidateId, setCandidateId } = useCandidateStore()
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
Update to Next.JS 10. It comes with Automatic Resolving of href which fixes your problem.
Try to delete the as="about" and then navigate again to the "about" page, the issue should be gone.
Codesandbox
My best bet would be to store the candidateId in an encrypted session on the client side. You could read/verify cookies in getServerSideProps() and pass their contents to the page component. If this sounds feasible, I'd recommend checking out the next-iron-session.
Another approach would be to check if candidateId exists in the query object in getServerSideProps(). If it does then pass it straight to the page component. If not, either get it elsewhere, redirect, or pass some default value. Append the following starter code to your about.tsx:
/* ... */
export function getServerSideProps({ query }: any) {
// if query object was received, return it as a router prop:
if (query.candidateId) {
return { props: { router: { query } } };
}
// obtain candidateId elsewhere, redirect or fallback to some default value:
/* ... */
return { props: { router: { query: { candidateId: 8432 } } } };
}
index.tsx file
Keep the code same as it is.
import React from "react";
import Link from "next/link";
export default function Index() {
return (
<div>
<Link
href={{
pathname: "/about",
query: { candidateId: 8432 }
}}
as="about"
>
Go to the about page
</Link>
</div>
);
}
AboutUs.tsx
Code starts from here
Adding router as a dependency in the useEffect the issue should get solved.
import Router, { useRouter } from "next/router";
import React, { useState, useEffect } from 'react';
function About({ router: { query } }: any) {
const route = userRouter();
const [candidateId, setCandidateid] = useState();
useEffect(() => {
const {candidateId} = router.query
if(candidateId) {
setCandidateid(candidateid)
}},[router]) //Here goes the dependency
return (
<div>
Candidate Id: <b> {candidateId} </b>
</div>
);
}
export default (About);
I am following this tutorial: https://crypt.codemancers.com/posts/2017-06-03-reactjs-server-side-rendering-with-router-v4-and-redux/ which i think is the 'standard' way of doing server side rendering in react (?).
Basically what happens is i use react router (v4) to make a tree of all the components that are about to get rendered:
const promises = branch.map(({ route }) => {
return route.component.fetchInitialData
? route.component.fetchInitialData(store.dispatch)
: Promise.resolve();
});
Wait for all those promises to resolve and then call renderToString.
In my components i have a static function called fetchInitialData which looks like this:
class Users extends React.Component {
static fetchInitialData(dispatch) {
return dispatch(getUsers());
}
componentDidMount() {
this.props.getUsers();
}
render() {
...
}
}
export default connect((state) => {
return { users: state.users };
}, (dispatch) => {
return bindActionCreators({ getUsers }, dispatch);
})(Users);
And all this works great except that getUsers is called both on the server and the client.
I could of course check if any users are loaded and not call getUsers in componentDidMount but there must be a better, explicit way to not make the async call twice.
After getting more and more familiar with react i feel fairly confident i have a solution.
I pass a browserContext object along all rendered routes, much like staticContext on the server. In the browserContext i set two values; isFirstRender and usingDevServer. isFirstRender is only true while the app is rendered for the first time and usingDevServer is only true when using the webpack-dev-server.
const store = createStore(reducers, initialReduxState, middleware);
The entry file for the browser side:
const browserContext = {
isFirstRender: true,
usingDevServer: !!process.env.USING_DEV_SERVER
};
const BrowserApp = () => {
return (
<Provider store={store}>
<BrowserRouter>
{renderRoutes(routes, { store, browserContext })}
</BrowserRouter>
</Provider>
);
};
hydrate(
<BrowserApp />,
document.getElementById('root')
);
browserContext.isFirstRender = false;
USING_DEV_SERVER is defined in the webpack config file using webpack.DefinePlugin
Then i wrote a HOC component that uses this information to fetch initial data only in situations where it is needed:
function wrapInitialDataComponent(Component) {
class InitialDatacomponent extends React.Component {
componentDidMount() {
const { store, browserContext, match } = this.props;
const fetchRequired = browserContext.usingDevServer || !browserContext.isFirstRender;
if (fetchRequired && Component.fetchInitialData) {
Component.fetchInitialData(store.dispatch, match);
}
}
render() {
return <Component {...this.props} />;
}
}
// Copy any static methods.
hoistNonReactStatics(InitialDatacomponent, Component);
// Set display name for debugging.
InitialDatacomponent.displayName = `InitialDatacomponent(${getDisplayName(Component)})`;
return InitialDatacomponent;
}
And then the last thing to do is wrap any components rendered with react router with this HOC component. I did this by simply iterating over the routes recursively:
function wrapRoutes(routes) {
routes.forEach((route) => {
route.component = wrapInitialDataComponent(route.component);
if (route.routes) {
wrapRoutes(route.routes);
}
});
}
const routes = [ ... ];
wrapRoutes(routes);
And that seems to do the trick :)