Reputation: 597
With the new NextJS 13 introducing the app
directory, would redux still make sense?
It's already possible to wrap redux providers around a Client Component as per next docs. But how would sharing states with redux impact Next performance and optmization?
Next docs ask to fetch data where the data is needed instead of passing down the component tree. Request will be automatically deduped.
My usage for Redux would be to control certain pieces of the application that I want to be consistent.
E.g: If I change an user name I want that reflected in the whole application where that data is needed.
Upvotes: 33
Views: 22669
Reputation: 49661
The main focus of server components is to send minimum javascript code to the browser so the browser will have less code to parse and it will display the content quicker. In the case of redux state management, we were populating the state on the server, sending the store and related code to the browser and the browser was hydrating in other words it was synchronizing the server store with the client code. Hydration is a very expensive operation. we were technically recreating a new store on the client. Every time user visits a page with server-side functions, HYDRATE
action was triggered and a new store was created. Imagine you have a very large application and applying hydration would slow down your app drastically.
With the app directory redux store, less code will be sent to the browser and I believe only the store that needed use client
components will be hydrated (as of now we cannot set next-redux-wrapper
in app directory. I think the default redux package will be redux-toolkit). this will increase the performance. Although marking some components as clients or some as servers does not seem nice, but the overall goal is to increase performance. And app directory is still in beta, as time goes by, they will make it better.
Upvotes: 5
Reputation: 21
There is much easier solution than writing tons of lines, just import the store directly from the store.tsx file into the rootlayout:
in my projects I use it like this:
store/Store.tsx:
import { configureStore } from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';
import { useDispatch } from 'react-redux';
import musicSlice from './musicSlice';
const store = configureStore({
reducer: {
music: musicSlice,
},
});
export const wrapper = createWrapper(() => store);
export const store_0001 = store;
export type AppState = ReturnType<typeof store.getState>;
export const useAppDispatch = () => useDispatch();
App/layout.tsx:
// redux
import { store_0001 } from '../store/store';
import { Provider } from 'react-redux';
export default function RootLayout({children, ...rest}: { children: React.ReactNode }) {
return (
<html lang="en">
<head />
<body>
<Provider store={store_0001}>
<Header/>
<NavMenu/>
<div>
{children}
</div>
<Footer/>
</Provider>
</body>
</html>
)
}
Good luck!
Upvotes: 1
Reputation: 467
Adding to Konrad's answer, if you would want to share state between server components - which is the new default w/o the "use client" directive - you would need to implement a native solution, like a singleton.
Say you want to share database connection across multiple component - access to the database is shared by importing the respective database module.
Set up the database module first:
utils/database.js
export const db = new DatabaseConnection(...);
Import into depending modules
app/users/layout.js
import { db } from "@utils/database";
export async function UsersLayout() {
let users = await db.query(...);
// ...
}
app/users/[id]/page.js
import { db } from "@utils/database";
export async function DashboardPage() {
let user = await db.query(...);
// ...
}
See here for reference.
Upvotes: -1
Reputation: 231
I recently started using NextJS and I had the same problem, after a bit of a research I have managed to get it working without wrapping each client component with a ReduxProvider.
Following the documentation given by the NextJS team it is suggested to a Providers client component which will give context to any other client components.
More details here: NextJS Context documentation
step 1: Create a Provider component in app/providers (make sure this is a client component)
"use client";
import { useServerInsertedHTML } from "next/navigation";
import { CssBaseline, NextUIProvider } from "@nextui-org/react";
import { PropsWithChildren } from "react";
import ReduxProvider from "./redux-provider";
type P = PropsWithChildren;
export default function Providers({ children }: P) {
useServerInsertedHTML(() => {
return <>{CssBaseline.flush()}</>;
});
return ( // you can have multiple client side providers wrapped, in this case I am also using NextUIProvider
<>
<ReduxProvider>
<NextUIProvider>{children}</NextUIProvider>
</ReduxProvider>
</>
);
}
create a redux provider component.
"use client";
import { PropsWithChildren } from "react";
import { Provider } from "react-redux";
import store from "../redux/store/store";
export default function ReduxProvider({ children }: PropsWithChildren) {
return <Provider store={store}>{children}</Provider>;
}
Use your provider component within your RootLayout component. (app/layout.tsx)
import Header from "./components/organisms/Header/Header";
import { PropsWithChildren } from "react";
import Footer from "./components/molecules/Footer/Footer";
import Providers from "./providers/Providers";
import MyBasketTab from "./components/organisms/MyBasketTab/MyBasketTab";
type Props = PropsWithChildren;
export default function RootLayout({ children }: Props) {
return (
<html lang="en">
<head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<Providers>
<Header />
{children}
<Footer />
<MyBasketTab /> // this client component can now fully use the redux hooks and access the store.
</Providers>
</body>
</html>
);
}
"use client";
import styles from "./MyBasketTab.module.css";
import { useAppSelector } from "../../../redux/hooks/hooks";
export default function MyBasketTab() {
const isBasketSideMenuOpened = useAppSelector(
(x) => x.basket.isBasketSideMenuOpened
);
return (
<div
className={`${styles.container} ${
!isBasketSideMenuOpened ? styles.opened : styles.closed
}`}
>
<p>My Basket</p>
</div>
);
}
Upvotes: 23