0

I'm trying to detect URL path inside my nextjs app and set the initial state based on the url path in the context, but am getting window is not defined. I know window won't be defined until client render but where would i put it if I want to pass initial state into context?

import React, {createContext, useReducer} from "react";
import Reducer from './reducer'

const initialState = {
  'about': window.location.pathname == 'about' ? true : false
};

const Store = ({children}) => {

    const [state, dispatch] = useReducer(Reducer, initialState);


    return (
        <Context.Provider value={[state, dispatch]}>
            {children}
        </Context.Provider>
    )
};

export const Context = createContext(initialState);
export default Store;
user3226932
  • 2,042
  • 6
  • 39
  • 76

2 Answers2

1

You have access to the RouterContext, You can make use of pathname value provided from it instead.

import { useRouter } from 'next/router';

const Store = ({children}) => {
  const router = useRouter();

  const [state, dispatch] = useReducer(Reducer, {
      about: router.pathname === '/about'
  });

  // rest of the logic

}
subashMahapatra
  • 6,339
  • 1
  • 20
  • 26
  • how could i do `export const Context = createContext(initialState);`, which is outside the component? initial state is inside the component so it's not defined outside of it – user3226932 Jun 19 '20 at 22:28
  • 1
    The default value for `createContext` is like a fallback when there is no provider. From react [docs](https://reactjs.org/docs/context.html#reactcreatecontext) "The defaultValue argument is only used when a component does not have a matching Provider above it in the tree". I don't think you need any initial value for created context here. – subashMahapatra Jun 19 '20 at 22:30
  • Great. I'm glad to help. – subashMahapatra Jun 19 '20 at 22:35
  • 1
    Including this, because there can be a very wrong interpretation of the comment above I made since I can't edit it anymore. It should be "The default value provided as an argument to `createContext` is like a fallback when there is no provider". – subashMahapatra Jun 19 '20 at 22:41
0

useReducer has ability to do lazy initialization via a third argument. Perhaps you can dispatch an action in your component when window is available such as in componentDidMount() or with hooks inside a useEffect():

// set initial state, can also pass values here from dispatch also
// This would be called when `window` is available
function init() {
 return {
   'about': window.location.pathname == 'about' ? true : false
 };
}

// ...
// Some initial state
const initialState = { about: false };

// ..

useReducer(Reducer, initialState, init); // pass third argument, the init function

Then update your reducer to use init:

function reducer(state, action) {
  switch (action.type) {
    // ...
    case 'init':
      return init(); // execute init function
  }
}

Finally trigger this new action type from your component using something like a useEffect hook:

// Assuming you are using context to get store state/dispatch
const [state, dispatch] = useContext(MyContext);
useEffect(() => {
  dispatch({ type: 'init' }); // could pass window values here also via payload perhaps
}, []);

Hopefully that helps!

Alexander Staroselsky
  • 37,209
  • 15
  • 79
  • 91
  • thanks for the suggestion, though `window` seems to still be undefined as well when used in `init()`, not sure why – user3226932 Jun 19 '20 at 22:23