Maintaining state value during client-side navigation in NextJs with Next-Redux-Wrapper

Currently, I am working on resolving the hydration issue that occurs when using wrapper.getServerSideProps. The problem arises when I reroute with the existing setup and the store gets cleared out before adding new data. This leads to a blank page as essential data like translations and cms data are no longer available.

Here is a screenshot showing the difference in the redux-dev-tools Hydrate action:

https://i.sstatic.net/Dmh5W.png

The screenshot captures the state after navigating from the homepage to a product page, indicating that the entire store is reset to the initial app state.

My Approach

In my store.js file, I establish the store and set up a reducer to manage the Hydrate call. However, the downside of this method is that the payload always results in a new store object being created since it is invoked on the server. My idea was to analyze the variance between the two JSON versions and then apply only the differences rather than refreshing the entire initial store.

  1. Determine the variance between client and server states.
  2. Create the next state by replacing the client state with the patched server state, incorporating updated state from hydration and the current client state.
  3. Unfortunately, this approach currently leads to a blank page.

You can view the reducer code below in the store.js file

//store.js

import combinedReducer from './reducer';

const bindMiddleware = (middleware) => {
    if (process.env.NODE_ENV !== 'production') {
        return composeWithDevTools(applyMiddleware(...middleware));
    }
    return applyMiddleware(...middleware);
};

const reducer = (state, action) => {
  if (action.type === HYDRATE) {
    const clientState = { ...state };
    const serverState = { ...action.payload };

    if (state) {
      // preserve state value on client side navigation

      // Get the difference between the client and server state.
      const diff = jsondiffpatch.diff(clientState, serverState);
      if (diff !== undefined) {
        // If there is a diff patch the serverState, with the existing diff
        jsondiffpatch.patch(serverState, diff);
      }
    }

    // Make next state, overwrite clientstate with patched serverstate
    const nextState = {
      ...clientState,
      ...serverState,
    };

    // Result, blank page.
    return nextState;
  }
  return combinedReducer(state, action);
};

export const makeStore = () => {
    const cookies = new Cookies();
    const client = new ApiClient(null, cookies);

    const middleware = [
        createMiddleware(client), 
        thunkMiddleware.withExtraArgument(cookies),
    ];

    return createStore(reducer, bindMiddleware(middleware));
};

const wrapper = createWrapper(makeStore);

export default wrapper;
//_app.jsx

const App = (props) => {
    const { Component, pageProps, router } = props;

    return (
        <AppComponent cookies={cookies} locale={router.locale} location={router}>
            <Component {...pageProps} />
        </AppComponent>
    );
};

App.getInitialProps = async ({ Component, ctx }) => {
    return {
        pageProps: {
            ...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}),
        },
    };
};

App.propTypes = {
    Component: PropTypes.objectOf(PropTypes.any).isRequired,
    pageProps: PropTypes.func,
    router: PropTypes.objectOf(PropTypes.any).isRequired,
};

App.defaultProps = {
    pageProps: () => null,
};

export default wrapper.withRedux(withRouter(App));
// Product page
export const getServerSideProps = wrapper.getServerSideProps(
async ({ query, store: { dispatch } }) => {
    const productCode = query.id?.split('-', 1).toString();
    await dispatch(getProductByCode(productCode, true));
});

const PDP = () => {
    const { product } = useSelector((state) => state.product);
    return (
        <PageLayout>
            <main>
                <h1>{product?.name}</h1>
                <div
                    className="description"
                    dangerouslySetInnerHTML={{ __html: product?.description }}
                />
            </main>
        </PageLayout>
    );
};

export default PDP;

Answer №1

After a period of contemplation, I found the solution to my problem by simplifying the concept and revisiting the basics.

I realized that only a few state objects needed to persist while navigating through the client side.

The key change I made was to update my i18n for dynamic translations based on the page being accessed.

For anyone facing a similar issue in the future, here is the final reducer code:

const reducer = (state, action) => {
  if (action.type === HYDRATE) {
    const clientState = { ...state };
    const serverState = { ...action.payload };
    const nextState = { ...clientState, ...serverState };

    const locale = nextState.i18n.defaultLocale || config.i18n.defaultLocale;

    const nextI18n = {
      ...state.i18n,
      locale,
      messages: {
        [locale]: {
          ...state.i18n.messages[locale],
          ...nextState.i18n.messages[locale],
        },
      },
      loadedGroups: {
        ...state.i18n.loadedGroups,
        ...nextState.i18n.loadedGroups,
      },
    };

    if (state) {
      nextState.i18n = nextI18n;
      nextState.configuration.webConfig = state.configuration.webConfig;
      nextState.category.navigation = state.category.navigation;
    }

    return nextState;
  }
  return combinedReducer(state, action);
};

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

Is it better to include the Google Analytics code in the master page or on every individual page of an asp.net

Looking for a way to track every page on my website effectively. Should I insert the Analytics tracking code in each aspx page inherited from the master page, or is it sufficient to place it only in the master page to track all inherited pages? ...

The value of a select box cannot be retrieved until it has been clicked on

I am working with a selectBox element in my code: <select class="span2" name="filterYear" id="filterYear" style="margin-right:10px;"> <% for (var i = 0; i < years.length; i++) { %> <% if (years[i] == selectedYear) { %> ...

The authorization header for jwt is absent

Once the user is logged in, a jwt token is assigned to them. Then, my middleware attempts to validate the token by retrieving the authorization header, but it does not exist. When I try to display the request header by printing it out, it shows as undefine ...

jquery for quick search

<form method="post" action="search.php"> Commence search: <input id="search" type="text" size="30" > <div id="search_results"></div> <script src="//code.jquery.com/jquery-1.12.0.min.js"></script> <script src="//code. ...

Is there a way for rainyday.js to utilize a CSS background image as its background?

I have a simple website with a fixed, full-sized background set using CSS. I am trying to incorporate rainyday.js to create a rain effect over the entire site, including the text. Currently, I am only able to get rainyday.js to display rain over a small s ...

Is there a way to alphabetically sort V-Chips in Vuetify?

Hello, I'm currently attempting to organize multiple V-Chips alphabetically. Does anyone have any suggestions? Here is the code snippet I am working with. I want to display all my V-Chips in alphabetical order. Should I sort my array or is there a sp ...

Add elements continuously without the need to refresh the page

How can I dynamically create a variable value by combining two different inputs without having to reload the page Here is the code snippet: {exp:safecracker channel="blending_log" return="sthome/blending/ENTRY_ID"} <h3>Select and enter data</h3 ...

Error message: Cannot access 'context' property in Webpack 4 css modules due to TypeError

I recently updated to webpack 4 and I am utilizing css modules. Encountered ERROR: ERROR in ./client/src/common/accordian-component/accordian.css (./node_modules/css-loader??ref--5-1!./node_modules/postcss-loader/lib??ref--5-2!./client/src/common/acc ...

Creating synchronization mechanisms for events in JavaScript/TypeScript through the use of async/await and Promises

I have a complex, lengthy asynchronous process written in TypeScript/JavaScript that spans multiple libraries and functions. Once the data processing is complete, it triggers a function processComplete() to indicate its finish: processComplete(); // Signa ...

The Axios GET call encountered an error with a status code of 404

I am currently working on developing a blog/articles application using vue.js. This app utilizes axios to retrieve data from my db.json file by making a get request. The objective is to display the selected article's content when it is clicked on from ...

Tips for successfully importing .eot and .woff documents using Babel?

When attempting to start my project, I encountered the following error message (You may need an appropriate loader to handle this file type.) for .eot, .woff, .ttf, and .svg files: ERROR in ./src/styles/fonts/nm/icons/nm-icons.svg?aaf8685e87a6901c76c52d00 ...

Unable to resolve the issue with ExpressPeerServer not being recognized as a function in server.js

I'm facing an issue with the peer.js library in my npm project. I have successfully installed it, but when I try to use it in my server.js file, I get an error saying that peerServer is not a function. const express = require('express'); con ...

Angularjs: The Art of Loading Modules

I am facing an issue while trying to load certain modules. controller1.js: angular.module('LPC') .controller('lista_peliculas_controller', ['$scope', function($scope) { $scope.hola="hola peliculas"; }]); And ap ...

Is it possible to have a field automatically calculate its value based on another field?

Consider the following data structure: { "rating": 0, "reviews": [ {"name": "alice", rating: 4}, {"name": "david", rating: 2} ] } What is the best way to recalculate the overall rating when new reviews are added or existing reviews are upda ...

What is the best method for choosing all options in a select box in IE without experiencing the scrolling effect?

Here's the scenario: I have a select element with multiple options and a checkbox that allows users to select/deselect all options. The issue arises in Internet Explorer, where selecting all options using code causes the entire select box to scroll un ...

Analyzing the use of statically imported image files versus path strings for the NextJS Image component

In the process of developing a statically exported website using NextJS (13.5.6), I came across information in the NextJS documentation regarding the Image component. It appears that the "src" parameter has the flexibility to accept either a statically imp ...

Differences Between Android and JavaScript: Ensuring Library Validity

Validation in JS is provided by the validator library which can be found at https://www.npmjs.com/package/validator Is there an equivalent library for validation in Android? If so, what is the name of Android's library? ...

A blank page is appearing mysteriously, free of any errors

I have experience with ReactJs, but I am new to Redux. Currently, I am working on implementing an async action where I call an API and display the data received from it. Initially, when all the Redux components (actions, reducers, containers) were on a sin ...

Identifying whether a child component is enclosed within a specific parent using React

Is there a more elegant and efficient method to determine if the parent of SeminarCard is Slider? Currently, I am passing this information through a prop. The prop value (true/false) is used to provide additional padding. When used independently: <Semi ...

What is the best way to troubleshoot an unresponsive button element to determine the listeners it has?

In a previous situation, I encountered an issue with the event modifier of a nested b-input not working as expected. To resolve this, I had to add .native because I was interacting with a component: b-dropdown(text="Actions") b-drop ...