How to prevent duplicate initialState from client response in next-redux-wrapper? - next.js

I am using next redux wrapper in App.getInitialProps but in client response I get duplicated initialState are: initialState outside pageProps and inside pageProps, this happens everytime when navigate to another page. How can I fix it?
You can see the image below:
My code below:
function App({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
App.getInitialProps = wrapper.getInitialPageProps((store) => async (context) => {
if (context.req) {
const dataAuth = await fetchAuth(context);
// dispatch action auth to update data initialState
// this only running on the server-side
if (dataAuth) store.dispatch(authSlice.actions.setAuth(dataAuth));
else store.dispatch(authSlice.actions.resetAuth());
}
});

Related

Next.js global css is not loading when getServerSideProps is used

I have global styles defined in my nextjs application inside /styles/globals.css and imported in _app.tsx
// import default style
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;
and page in in pages directory (pages/index.tsx)
export async function getServerSideProps({req, res }) {
// Some fetch here
return {
props: {
someProps: objectFromFetchThere,
},
};
}
const Home: NextPage<{ someProps: SomeProps[] }> = ({ someProps }) => {
return (
<>
<Head>
<title>Home page</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Layout>
// More code here
</Layout>
</>
);
};
When I'm running an application with next dev then everything is working fine (styles are loaded, getServerSideProps is called etc.), but when I'm running in production (next build && NODE_ENV=production ts-node src/server.ts) then global styles are not loaded (and _app file is also not used). Does it mean that I can't use global styles in pages with getServerSideProps exported? I didn't find anything related to that in NextJS documentation. Am I missing something here?
My custom server:
(async () => {
try {
const expressServer = express();
await app.prepare();
// Some custom routes defined over there like /facility - nothing that can overlap with styles.
// Error middleware has to be used after other custom routes
expressServer.use(
(err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).send("Unexpected error occurred.");
}
);
expressServer.all("*", async (req: Request, res: Response) => {
try {
await handle(req, res);
} catch (e) {
console.error("Error occurred handling", req.url, e);
res.statusCode = 500;
res.end("internal server error");
}
});
expressServer.listen(port, (err?: any) => {
if (err) throw err;
console.log(
`> Ready on localhost:${port} - env ${process.env.NODE_ENV}`
);
});
} catch (e) {
console.error(e);
process.exit(1);
}
})();
This issue is related to the custom server and typescript used at the same time. Changing "target": "es5" to "target": "es6" fixed the issue. A solution is taken from https://stackoverflow.com/a/67520930/7932025.

next-redux-wrapper: after hydration useSelector returns initial value (null), but getServerSideProps passes the correct value to the page

I got getServerSideProps like this, which gets token from cookie and gets uses it to fetch user data with RTKQ endpoint. Then dispatches that data to authSlice.
So far it's good.
const getServerSideProps = wrapper.getServerSideProps(
(store) =>
async ({ req, res }: GetServerSidePropsContext) => {
let result: AuthState = null;
const data = getCookie('device_access_token', { req, res });
if (data?.toString()) {
result = await store
.dispatch(
usersApi.endpoints.getUserByToken.initiate(data?.toString())
)
.unwrap();
}
if (result) store.dispatch(setUser(result));
return { props: { auth: result } };
}
);
Then I merge this auth data in the store like this:
const reducer = (state: ReturnType<typeof rootReducer>, action: AnyAction) => {
if (action.type === HYDRATE) {
console.log('payload#HYDRATE', action.payload);
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
if (state.auth.user) {
nextState.auth.user = state.auth.user;
nextState.auth.token = state.auth.token;
} // preserve auth value on client side navigation
return nextState;
} else {
return rootReducer(state, action);
}
};
console.log('payload#HYDRATE', action.payload); also shows correct data.
The problem is in a page where I export getServerSideProps,
const IndexPage: NextPage = ({ auth }: any) => {
console.log('user#index', auth);
console.log('userSelect#index', useSelector(selectCurrentUser));
return auth ? <Home /> : <NoAuthHome />;
};
auth shows correct value, but useSelector(selectCurrentUser) shows null
Can someone tell me if this is how it is intended to be, or I'm doing something wrong?
Because I don't want prop-drilling auth on countless pages, just use useSelector(selectCurrentUser) wherever necessary.
Finally found the problem!
problem was in _app.tsx
I wrapped <Component {...pageProps} /> with <Provider store={store} at the same time exporting with wrapper.withRedux(MyApp)

How to combine Component.getServerSideProps props and MyApp.getInitialState props?

Next.js has an App component. There is an App component in Next.js where I can change props for each component when it rendering:
export default function MyApp({ Component, pageProps }) {
// Here some actions with pageProps
return (
<React.Fragment>
<Component {...pageProps} />
</React.Fragment>
)
}
But if i want to use MyApp.getInitialProps:
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext);
return { ...appProps }
}
Then in browser after console.log(props) i see only props, that i have with function Component.getServerSideProps.
But how can I combine in component props the props received via MyApp.getInitialProps and the props received via Component.getServerSideProps ?
P.S. It is not essential for me to use MyApp.getInitialState. GetServerSideProps will do too, the main thing is to combine the props

Next JS fetch data once to display on all pages

This page is the most relevant information I can find but it isn't enough.
I have a generic component that displays an appbar for my site. This appbar displays a user avatar that comes from a separate API which I store in the users session. My problem is that anytime I change pages through next/link the avatar disappears unless I implement getServerSideProps on every single page of my application to access the session which seems wasteful.
I have found that I can implement getInitialProps in _app.js like so to gather information
MyApp.getInitialProps = async ({ Component, ctx }) => {
await applySession(ctx.req, ctx.res);
if(!ctx.req.session.hasOwnProperty('user')) {
return {
user: {
avatar: null,
username: null
}
}
}
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return {
user: {
avatar: `https://cdn.discordapp.com/avatars/${ctx.req.session.user.id}/${ctx.req.session.user.avatar}`,
username: ctx.req.session.user.username
},
pageProps
}
}
I think what's happening is this is being called client side on page changes where the session of course doesn't exist which results in nothing being sent to props and the avatar not being displayed. I thought that maybe I could solve this with local storage if I can differentiate when this is being called on the server or client side but I want to know if there are more elegant solutions.
I managed to solve this by creating a state in my _app.js and then setting the state in a useEffect like this
function MyApp({ Component, pageProps, user }) {
const [userInfo, setUserInfo] = React.useState({});
React.useEffect(() => {
if(user.avatar) {
setUserInfo(user);
}
});
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<NavDrawer user={userInfo} />
<Component {...pageProps} />
</ThemeProvider>
);
}
Now the user variable is only set once and it's sent to my NavDrawer bar on page changes as well.
My solution for this using getServerSideProps() in _app.tsx:
// _app.tsx:
export type AppContextType = {
navigation: NavigationParentCollection
}
export const AppContext = createContext<AppContextType>(null)
function App({ Component, pageProps, navigation }) {
const appData = { navigation }
return (
<>
<AppContext.Provider value={appData}>
<Layout>
<Component {...pageProps} />
</Layout>
</AppContext.Provider>
</>
)
}
App.getInitialProps = async function () {
// Fetch the data and pass it into the App
return {
navigation: await getNavigation()
}
}
export default App
Then anywhere inside the app:
const { navigation } = useContext(AppContext)
To learn more about useContext check out the React docs here.

How to fetch slugs from server to header in Next.js

I try to fetch data (slugs of categories) from my server and use it inside the header navbar that is reusable in all the site.
In _app I have the layout which contains the header component:
function MyApp({ Component, appProps, pageProps, router }) {
return (
<>
<SlugContext.Provider value={{ slugs: pageProps.response }}>
<Layout>
<Component {...pageProps} />
</Layout>
</SlugContext.Provider>
</>
);
}
MyApp.getInitialProps = async ctx => {
const response = await axios
.get(`${API}/categories/`)
.then(res => {
return res.data;
})
.catch(error => {
if (error) {
console.log(error);
}
});
const pageProps = await App.getInitialProps({ response, ...ctx });
return { pageProps: { ...pageProps, response } };
};
In the header navbar I put the context:
const context = useContext(SlugContext);
and inside I have links like:
<NavLink href={`/categories/${context.slugs[0].slug}`} onClick={closeMobileMenu}>
<NavLink href={`/categories/${context.slugs[1].slug}`} onClick={closeMobileMenu}>
THE PROBLEM: When I console log inside the header I can see my slugs. When I click on one link -> it directs me to the slug, like: http://localhost:3000/categories/slugone
Bu the following message appears:
Unhandled Runtime Error
TypeError: Cannot read property '0' of undefined
...........
href={`/categories/${context.slugs[0].slug}`}===undefined
Why is it undefined with 0, if before he have all the slugs data? It has to be in the context, why does it disappear and how can I handle it?

Resources