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:

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

Sending callback from server component to client component in Next.js

I've hit a roadblock while creating a custom button component, designating it as a client component, and then attempting to pass its onClick callback from a server-side component. Unfortunately, I'm encountering this error: error Error: Event ha ...

JavaScript - Ensure all special characters are maintained when using JSON.parse

I have encountered an issue while running a NodeJS app that retrieves posts from an API. The problem arises when trying to use JSON.parse on text containing special characters, causing the parsing process to fail. Special characters can include symbols fr ...

SSI stands for Server Side Includes, a feature that allows

I have multiple versions of the same HTML page, each with only one hidden variable that is different. This variable is crucial for tracking purposes. Now, I am exploring options to rewrite this by incorporating a HTML file with a hidden variable. Here is ...

Determine if a specific checkbox with a particular id has been selected using JQuery

Looking for assistance on how to determine if a checkbox with a specific ID is checked or unchecked. <input name="A" id="test" type="checkbox" value="A" /> <input name="B" id="test" type="checkbox" value="B" /> <input name="C" id="test1" t ...

Leveraging jQuery's load within a series of sequential operations

I am currently working on populating different cells in a table with the IDs #RECALRow1, #RECALCol1, and #RECALBodySum. Each cell is being filled from a database using AJAX with jQuery's load method. In my initial approach, I had separate functions f ...

Utilizing jQuery to implement a function on every individual row within a table

I have a collection of objects that requires applying a function to each item, along with logging the corresponding name and id. Below is the snippet of my code: var userJson = [ { id: 1, name: "Jon", age: 20 }, { ...

JavaScript - Modify input character prior to appending it to the text area

I am working on creating a virtual keyboard using jQuery. Whenever I press 'a' in the textarea, I want it to display 'z' instead. In my investigation of typing a letter in the textarea, I discovered the following sequence: keyDown ev ...

Using jQuery and regex to only allow alphanumeric characters, excluding symbols and spaces

Seeking advice, I am using a jquery function called alphanumers var alphanumers = /^[a-zA-Z0-9- ]*$/; which currently does not allow all symbols. However, I now wish to disallow the space character as well. Any suggestions? ...

What is the best method for effectively organizing and storing DOM elements alongside their related objects?

In order to efficiently handle key input events for multiple textareas on the page, I have decided to create a TextareaState object to cache information related to each textarea. This includes data such as whether changes have been made and the previous co ...

Display the image regardless of whether the component is currently visible

I need help with my Vue.js web application that includes a side navigation menu component. This component uses conditional rendering to display only when necessary. Within the component, there is an image for the close button of the side menu. <transiti ...

Cannot assign border to an undefined element - JavaScript

Currently, I am attempting to validate some code using javascript. However, I am encountering a frustrating issue where I keep getting an "Uncaught TypeError: Cannot set property 'border' of undefined". Being relatively new to javascript, I ...

Access information through token-based verification

Just starting out in this area of development, a colleague shared some information with me on how to retrieve the database. I'm feeling a bit lost as to what to do next. curl -X GET -H "Authorization: Token token=xxxxxxxxxxxxxxxxxxxxxxxxx" "https://w ...

Removing outline from a Material UI button can be done using a breakpoint

Is there a way to remove the outlined variant for small, medium, and down breakpoints? I have attempted the following approach: const selectedVariant = theme.breakpoints.down('md') ? '' : 'outlined'; <Button name="buy ...

Trouble with HTML2PDF.js - download not being initiated

Currently, I am utilizing html2pdf.js in my project by following this tutorial: https://github.com/eKoopmans/html2pdf.js However, I've encountered an issue where despite implementing everything as instructed, the download prompt for the specified div ...

Placing a small image on top of multiple images using CSS

I am facing a CSS issue and I need help with positioning a small image (using position absolute) like a warranty badge on top of larger images. The challenge is to ensure that the badge is fixed at the bottom left corner of each image, despite variations ...

How to toggle between two background colors in a webpage with the click of a button using JavaScript

I need help with a unique website feature. I want to implement a button that cycles between two different background colors (white and black) as well as changes the font color from black to white, and vice versa. My goal is to create a negative version of ...

Retrieve the next 14 days starting from the current date by utilizing Moment.js

Looking to display the next 14 days in a React Native app starting from today's date. For example, if today is Friday the 26th, the list of days should be: 26 - today 27 - Saturday 28 - Sunday 01 - Monday 02 - Tuesday ............ 12 - Friday Is ther ...

Angular 6 provides a regular expression that specifically targets the removal of any characters that are not numbers and enforces the allowance of

I have tried various solutions to restrict input in an Angular material input box, but none seem to be effective for my specific case. I need the input field to only allow numbers and a decimal point. Any other characters should be automatically removed as ...

Upon selecting the correct prompt, it fails to respond when I press enter

I attempted to use a multi-search script that I found on a website. I followed the instructions given and made the necessary changes. After saving the file as an HTML document, I opened it in Chrome. However, when I type in a word and hit enter, nothing ha ...

ReactJS incorporates multiple CSS files

I am currently working on developing a Single Page Application using ReactJS. However, I am facing an issue with styling. Currently, I have created 3 different pages (with the intention of adding more in the future), each having its own CSS file that is im ...