Antonio Pavicevac-Ortiz
Antonio Pavicevac-Ortiz

Reputation: 7739

React Context: Error: userState must be used within a UserProvider despite nested in Provider?

I am following this great tut by Kent C. Dodds regarding usage React Context.

But I actually have this component nested within the provider so not sure why I am getting.

Error: userState must be used within a UserProvider

So perhaps I am not getting the point of creating a function which is throwing an Error despite following how you should use the provider... Perhaps I implemented it incorrectly?

So this is my userContext setup:

import React, { useState, useEffect, useContext, useReducer } from 'react';

var initialState = {
 ...state...
};

var UserStateContext = React.createContext();
var UserContextDispatch = React.createContext();

function setLocalStorage(key, value) {
   ...function innards...
}

function getLocalStorage(key, initialValue) {
   ...function innards...
}

function UserProvider({ children }) {
  function userReducer(state, { type, payload }) {    
    switch (type) {
      case 'setUserId': {
        return { ...state, ...{ id: payload.id } };
      }

      case 'setAvatar': {
        return {
          ...state,
          ...{ avatar: payload.avatar }
        };
      }

      case 'setIsRoutingVisible': {
        return {
          ...state,
          ...{ isRoutingVisible: payload.isRoutingVisible }
        };
      }

      case 'addMarker': {
        user.isLengthOfMarkersLessThanTwo
          ? {
              ...state,
              markers: user.markers.concat(payload.marker)
            }
          : null;
        break;
      }
    
      case 'setMap': {
        return {
          ...state,
          currentMap: payload.curerntMap
        };
      }

      default: {
        throw new Error(`Unhandled action type: ${type}`);
      }
    }
  }

  const [user, setUser] = useState(() => getLocalStorage('user', initialState));

  var [state, dispatch] = useReducer(userReducer, user);

  useEffect(() => {
    setLocalStorage('user', state);
  }, [state]);

  return (
    <UserStateContext.Provider value={state}>
      <UserContextDispatch.Provider value={dispatch}>
        {children}
      </UserContextDispatch.Provider>
    </UserStateContext.Provider>
  );
}

function userState() {
  const context = React.useContext(UserStateContext);
  if (context === undefined) {
    throw new Error('userState must be used within a UserProvider');
  }
  return context;
}

function userDispatch() {
  const context = React.useContext(UserContextDispatch);
  if (context === undefined) {
    throw new Error('userDispatch must be used within a UserProvider');
  }
  return context;
}

export { UserProvider, userState, userDispatch };

The error is pointing to my Map component, which is passing state and dispatch props from UserContext to my Routing component which is a class component.

import React, { useState, useContext, useEffect, useRef, useCallback } from 'react';
import { Button } from 'semantic-ui-react';

import L from 'leaflet';
import * as ELG from 'esri-leaflet-geocoder';
import { Map } from 'react-leaflet';
import { Dimmer, Loader } from 'semantic-ui-react';

import Routing from '../RoutingMachine/RoutingMachine.jsx';
import { userState, userDispatch } from '../Context/UserContext.jsx';

import UIContext from '../Context/UIContext.jsx';

import { stringify } from 'flatted';

export default function MyMap({}) {
  var [zoom, setZoom] = useState(18);
  var [animate, setAnimate] = useState(false);
  var [userLocation, setUserLocation] = useState(null);

  var mapRef = useRef();

  console.log('userState() ', userState());
  var {
    avatar,
    currentMap,
    id,
    isLengthOfMarkersLessThanTwo,
    isRoutingVisible,
    markers,
    removeRoutingMachine
  } = userState();

  var dispatch = userDispatch();

  var { isMobile, isDesktop } = useContext(UIContext);

  useEffect(() => {
    if (isRoutingVisible === false) {
      dispatch({
        type: 'setIsRoutingVisible',
        payload: {
          isRoutingVisible: true
        }
      });
    }
  });

  useEffect(() => {
    if (markers.length === 2) {
      dispatch({
        type: 'isLengthOfMarkersLessThanTwoFalse',
        payload: { isLengthOfMarkersLessThanTwo: false }
      });
    }
  }, [JSON.stringify(markers)]);

  return (
    <Map
      animate={animate}
      onLocationFound={handleOnLocationFound}
      zoom={zoom}
      ref={mapRef}
    >
       
      {isRoutingVisible && (
        <Routing
          markers={markers}
          dispatch={dispatch}
          removeRoutingMachine={removeRoutingMachine}
          map={currentMap}
          userLocation={userLocation}
          isMobile={isMobile}
          isDesktop={isDesktop}
        />
      )}
    </Map>
  );
}

Upvotes: 1

Views: 11527

Answers (1)

Antonio Pavicevac-Ortiz
Antonio Pavicevac-Ortiz

Reputation: 7739

It seemed the problem was the way I was passing the state & dispatch value into each provider:

 return (
    <UserStateContext.Provider value={state}>
      <UserContextDispatch.Provider value={dispatch}>
        {children}
      </UserContextDispatch.Provider>
    </UserStateContext.Provider>
  );

I got it working by passing a object with the value: value={{ key: value }}

So one should do this:

return (
    <UserStateContext.Provider value={{ state: state }}>
      <UserContextDispatch.Provider value={{ dispatch: dispatch }}>
        {children}
      </UserContextDispatch.Provider>
    </UserStateContext.Provider>
  );

Upvotes: 2

Related Questions