Search code examples
reactjsreduxreact-reduxgatsby

Read and write to Redux store from multiple pages of a Gatsby App


My app is like this:

Gatsby project

index.tsx - Entry point of app - I call

<Provider store={store}>
   <HomePage />
</Provider>

HomePage.js uses mapStateToProps and uses connect(mapStateToProps)(HomePage)

Inside my HomePage, of course I use multiple React components. And I can make use of the store, read/write of anything that happens on that HomePage.

On my Homepage, I have links to ActivityPage. Once I click on this page and land there - problem starts.

ActivityPage makes use of multiple sub-components to achieve tasks. How do I access read/write from ActivityPage?

I am unable to useDispatch to write or even read from the store. I can see the store on my ReactDev tools, but on trying to read/write I get 11 errors, mostly along the lines of I need to wrap Components with <Provider>

I read different solutions on Stack Overflow which suggested that I need to have mapStateToProps and use connect on whichever Component I need access the store.

This is not working, as I am specifically looking to get access to the store from a 'new page'. Do I need to go and make another store for my child pages? Help please. error stack trace:


Solution

  • Your component must be inside of a redux Provider component in order to use the redux hooks useSelector and useDispatch or the connect HOC.

    In a typical React app you would place the Provider in your App component which is a parent of everything. However Gatsby uses a different setup where each page is totally independent, so there is no shared parent where we can place the Provider.

    What Gatsby does have is an API for overriding customizing behaviors by defining functions in configuration files. There are a bunch of files that you can place in the root of your app, in the same folder as package.json and outside of src. The two that we will use here are gatsby-browser.js, which controls the client-side, and gatsby-ssr.js, which controls the creation of static HTML pages through server-side rendering. Both of these files support a function called wrapRootElement.

    We use the wrapRootElement function to place our Redux provider as a wrapper around every page. Since we are using the same function in two files, the official "using-redux" example defines that function in a separate file and imports it into both of the configurations. wrap-with-provider.js is not a special file name, it's just a holder for the function.

    A wrapRootElement function receives the element as a prop which is similar to the children prop. Our function creates a store instance and returns a Provider with that store which has the element as its child. We are creating the store in this function, so if your current redux file is exporting a created store as a constant, you'll want to export a callable function that creates the store instead. You can see their example here.

    wrap-with-provider.js

    import React from "react"
    import { Provider } from "react-redux"
    
    import createStore from "./src/state/createStore"
    
    // eslint-disable-next-line react/display-name,react/prop-types
    export default ({ element }) => {
      // Instantiating store in `wrapRootElement` handler ensures:
      //  - there is fresh store for each SSR page
      //  - it will be called only once in browser, when React mounts
      const store = createStore()
      return <Provider store={store}>{element}</Provider>
    }
    

    Again, this file does not manipulate Gatsby behavior on its own. We need to export its value and assign it to a special variable in a configuration file to do that. Here are those files:

    gatsby-browser.js

    import wrapWithProvider from "./wrap-with-provider"
    export const wrapRootElement = wrapWithProvider
    

    gatsby-ssr.js

    import wrapWithProvider from "./wrap-with-provider"
    export const wrapRootElement = wrapWithProvider
    

    The content is the same in both. All we are doing is importing the function that we created in wrap-with-provider.js and assigning it to the special named variable wrapRootElement which controls behavior.