What is the correct way to implement "next-redux-wrapper" with "Next.js", "Redux-ToolKit" and Typescript?

Currently, I am integrating RTK (redux-toolkit) into my Next.js App. I am facing an issue while trying to dispatch an AsyncThunk Action within "getInitialProps". During my research, I came across a package named "next-redux-wrapper" that allows access to the "store" within "getInitialProps", but I am finding it challenging to incorporate it into my project.

Below is a basic example of my project setup where I am utilizing Typescript with 2 reducers. One reducer employs AsyncThunk to fetch data from an API. Although I have installed "next-redux-wrapper", I am unsure how to wrap it around the so that all pages can utilize the "store" inside "getInitialProps". The documentation of the package provides an example, but it seems a bit confusing.

This is how my store.ts file looks like...

import { Action, configureStore, ThunkAction } from '@reduxjs/toolkit';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import { counterReducer } from '../features/counter';
import { kanyeReducer } from '../features/kanye';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    kanyeQuote: kanyeReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

As you can see, I have imported next-redux-wrapper, but that's as far as I've gotten.

Additionally, this is how my "_app.tsx" file is structured...

import { Provider } from 'react-redux';
import type { AppProps } from 'next/app';
import { store } from '../app/store';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}

export default MyApp;

I require the ability to dispatch the "getKanyeQuote" action in "getInitialProps" on this specific page...

import React from 'react';
import { useAppDispatch, useAppSelector } from '../app/hooks';
import { getKanyeQuote } from '../features/kanye';

const kanye: React.FC = () => {
  const dispatch = useAppDispatch();
  const { data, pending, error } = useAppSelector((state) => state.kanyeQuote);

  return (
    <div>
      <h2>Generate random Kanye West quote</h2>
      {pending && <p>Loading...</p>}
      {data && <p>{data.quote}</p>}
      {error && <p>Oops, something went wrong</p>}
      <button onClick={() => dispatch(getKanyeQuote())} disabled={pending}>
        Generate Kanye Quote
      </button>
    </div>
  );
};

export default kanye;

For a complete sample, please refer to this link. https://stackblitz.com/edit/github-bizsur-zkcmca?file=src%2Ffeatures%2Fcounter%2Freducer.ts

Your assistance on this matter would be greatly appreciated.

Answer №1

CLICK HERE FOR LIVE DEMO

Start by setting up the wrapper:

import {
  Action,
  AnyAction,
  combineReducers,
  configureStore,
  ThunkAction,
} from '@reduxjs/toolkit';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import { counterReducer } from '../features/counter';
import { kanyeReducer } from '../features/kanye';

const combinedReducer = combineReducers({
  counter: counterReducer,
  kanyeQuote: kanyeReducer,
});

const reducer = (state: ReturnType<typeof combinedReducer>, action: AnyAction) => {
  if (action.type === HYDRATE) {
    const nextState = {
      ...state, // use previous state
      ...action.payload, // apply delta from hydration
    };
    return nextState;
  } else {
    return combinedReducer(state, action);
  }
};

export const makeStore = () =>
  configureStore({
    reducer,
  });

type Store = ReturnType<typeof makeStore>;

export type AppDispatch = Store['dispatch'];
export type RootState = ReturnType<Store['getState']>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

export const wrapper = createWrapper(makeStore, { debug: true });

The new reducer function combines the server store and client store:

  • wrapper creates a new server-side redux store using makeStore function
  • wrapper dispatches HYDRATE action with newly created server store as payload
  • reducer merges the server store with the client store.

We are replacing the client state with the server state, but additional reconciliation may be needed for complex stores.

Wrap your _app.tsx file:

No need to manually provide Provider and store, as the wrapper handles it automatically:

import type { AppProps } from 'next/app';
import { wrapper } from '../app/store';

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default wrapper.withRedux(MyApp);

You can now dispatch thunk actions on your pages:

import { NextPage } from 'next/types';
import React from 'react';
import { useAppDispatch, useAppSelector } from '../app/hooks';
import { getKanyeQuote } from '../features/kanye';
import { wrapper } from '../app/store';

const kanye: NextPage = () => {
  const dispatch = useAppDispatch();
  const { data, pending, error } = useAppSelector((state) => state.kanyeQuote);

  return (
    <div>
      <h2>Generate random Kanye West quote</h2>
      {pending && <p>Loading...</p>}
      {data && <p>{data.quote}</p>}
      {error && <p>Oops, something went wrong</p>}
      <button onClick={() => dispatch(getKanyeQuote())} disabled={pending}>
        Generate Kanye Quote
      </button>
    </div>
  );
};

kanye.getInitialProps = wrapper.getInitialPageProps(
  ({ dispatch }) =>
    async () => {
      await dispatch(getKanyeQuote());
    }
);

export default kanye;


Answer №2

To implement the steps outlined in the Usage guide from the repository of next-redux-wrapper, you need to set up your store file as follows:

import { Action, configureStore, ThunkAction } from '@reduxjs/toolkit';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';
import { counterReducer } from '../features/counter';
import { kanyeReducer } from '../features/kanye';

const store = configureStore({
  reducer: {
    counter: counterReducer,
    kanyeQuote: kanyeReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

const makeStore = () => store;

export const wrapper = createWrapper(makeStore);

Next, make changes to the _app.js file as shown below:

import type { AppProps } from 'next/app';
import { wrapper } from '../app/store';

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default wrapper.withRedux(MyApp);

Once you navigate to the /kanye page, everything should work smoothly.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

transmit data retrieved from `getStaticProps` to other static pages in NextJS

While working on my project, I encountered the task of fetching a large JSON dataset from an external API within the getStaticProps function. This dataset needs to be divided into multiple parts and passed as props to numerous static pages (potentially hun ...

Is it possible to identify in Next.js whether scrollRestoration was triggered, or if the back button was pressed?

I've been utilizing the scrollRestoration functionality within Next.js to save and restore the page position whenever a user navigates using the back button. However, I've encountered an issue with it not restoring the horizontal scroll position ...

Enable next-i18next to handle internationalization, export all pages with next export, and ensure that 404 error pages are displayed on non-generated pages

After carefully following the guidelines provided by i18next/next-i18next for setting up i18n and then referring to the steps outlined in this blog post on locize on how to export static sites using next export, I have managed to successfully generate loca ...

typescript defining callback parameter type based on callback arguments

function funcOneCustom<T extends boolean = false>(isTrue: T) { type RETURN = T extends true ? string : number; return (isTrue ? "Nice" : 20) as RETURN; } function funcCbCustom<T>(cb: (isTrue: boolean) => T) { const getFirst = () => ...

I will evaluate two arrays of objects based on two distinct keys and then create a nested object that includes both parent and child elements

I'm currently facing an issue with comparing 2 arrays of objects and I couldn't find a suitable method in the lodash documentation. The challenge lies in comparing objects using different keys. private parentArray: {}[] = [ { Id: 1, Name: &ap ...

What is the best method for testing routes implemented with the application router in NextJS? My go-to testing tool for this is vitest

Is it possible to test routes with vitest on Next.js version 14.1.0? I have been unable to find any information on this topic. I am looking for suggestions on how to implement these tests in my project, similar to the way I did with Jest and React Router ...

Link a YAML file with interfaces in JavaScript

I'm currently learning JavaScript and need to convert a YAML file to an Interface in JavaScript. Here is an example of the YAML file: - provider_name: SEA-AD consortiumn_name: SEA-AD defaults: thumbnail Donors: - id: "https://portal.brain ...

What is the process for refreshing a user's session from the backend following updates to their metadata?

Currently, I am utilizing Next.js on the client side, auth0 for authentication, and Django Rest Framework for the backend. By following Auth0's Manage Metadata Using the Management API guide, I successfully managed to set new metadata values (verified ...

Incorporate a JavaScript script into an Angular 9 application

I have been experiencing issues trying to add a script.js file to angular.json and use it in one component. Adding a script tag directly to my HTML file is not the ideal solution. Can someone suggest an alternative approach or point out what I may be missi ...

Managing the state in NextJS applications

I've scoured the depths of the internet in search of a solution for this issue, but unfortunately I have yet to come across one that effectively resolves it. I've experimented with various state management tools including: useContext Redux Zusta ...

Encountered a TypeScript error: Attempted to access property 'REPOSITORY' of an undefined variable

As I delve into TypeScript, a realm unfamiliar yet not entirely foreign due to my background in OO Design, confusion descends upon me like a veil. Within the confines of file application.ts, a code structure unfolds: class APPLICATION { constructor( ...

When I try to make an on-demand revalidation API call on Vercel, it takes so long that it ends up timing

Inspired by Kent C. Dodds, I have created a blog using Github as my Content Management System (CMS). All of my blog content is stored in the same repository as the code, in mdx format. To streamline the process, I set up a workflow that detects changes i ...

Tips on preventing duplication of APIs when retrieving data using nextjs

In my code, I have a function that can be called either from server-side rendering or client side: export const getData = async (): Promise<any> => { const response = await fetch(`/data`, { method: 'GET', headers: CONTENT_TYPE_ ...

Limiting the size of image uploads in AWS S3

Currently, I am attempting to go through the steps outlined in this repo, which involves utilizing nextjs and AWS S3 for image uploading. However, one thing that is puzzling me is the limitation of 1MB on image sizes. I'm curious as to why this restri ...

The latest version of Next.js, 11.1.0, does not support monitoring for code changes within the node

In the latest version of Nextjs (11.1.0), there seems to be an issue where code changes in the `node_modules` directory are not being watched, unlike in the previous version (10.2.1). While working on a project with Nextjs 11.1.0, I have tried explicitly ...

Creating a fetcher that seamlessly functions on both the server and client within Nextjs 13 - the ultimate guide!

My Nextjs 13 frontend (app router) interacts with a Laravel-powered backend through an api. To handle authentication in the api, I am utilizing Laravel Sanctum as suggested by Laravel for SPAs. This involves setting two cookies (a session and a CSRF token) ...

What are some ways to implement a pre-execution step, such as an interceptor, before Nextjs runs getStatic

When working with getStaticProps and getServerSideProps in Next.js, I need to intercept and add common header properties to all request calls that are executed server-side. axios.interceptors.request.use(function (config) { // Perform actions before ...

Using a JavaScript command, connect a function from one file to another file

I attempted to import a function because I want to click on <il> servies</il> and scroll to the services section on the home page. However, I simply want to click on the li element in the navbar and scroll to the service section on the home pag ...

What steps do I need to take to ensure NextJS stores my edits in VSCode?

I have attempted various troubleshooting steps such as changing file extensions from .js to .jsx, turning on Prettier for formatting upon saving, setting it as the default formatter, reloading and restarting the editor. However, the issue with not being ...

The process of integrating Tailwind elements into NextJs version 13

Can anyone help me integrate Tailwind elements into my NextJs project using JavaScript instead of TypeScript? I tried following the documentation, but the navbar component's expand button doesn't work. It seems like all components are having some ...