Skip to main content

Command Palette

Search for a command to run...

Getting default value from a React Context Consumer in a monorepo Project? How to solve it.

Updated
6 min read

For the last two days, I was wondering, why I'm getting the default value from my react Context.Consumer. everything was going well under development mode, yet when I switch to production mode my context stopped responding in parts of my application, but it was working on other parts of the app. I have tried deferent methods to get the value provided with Context.Provider using useContext and useMemo hooks. but nothing worked as intended. Finally, after many hours of searching and trying out different methods, I have found the solution, and it was a simple one.

To demonstrate the problem, my project hierarchy looked something like this:

/packages/my-ui/build/Basket/index.js:

export default from './Basket';

/packages/my-ui/build/Basket/BasketReducer.js:

const BasketActions = {
  ADD: 'add-item',
  DEL: 'delete-item',
  INC: 'increase-item',
  DEC: 'decrease-item',
  CLR: 'clear-items',
};

export function AddItemAction(key, item) {
  return {
    type: BasketActions.ADD,
    key,
    item,
  };
}

export function DelItemAction(key) {
  return {
    type: BasketActions.DEL,
    key,
  };
}

export function IncItemAction(key) {
  return {
    type: BasketActions.INC,
    key,
  };
}

export function DecItemAction(key) {
  return {
    type: BasketActions.DEC,
    key,
  };
}
export function ClrItemsAction() {
  return {
    type: BasketActions.CLR,
  };
}

export default function basketReducer(state = {}, action) {
  switch (action.type) {
    case BasketActions.ADD: {
      const { key, item } = action;
      if (state[key]) {
        return basketReducer(state, IncItemAction(key));
      }
      return {
        ...state,
        [key]: {
          ...item,
          count: 1,
        },
      };
    }
    case BasketActions.DEL: {
      const { key } = action;
      const newState = { ...state };
      delete newState[key];
      return newState;
    }
    case BasketActions.INC: {
      const { key } = action;
      const newState = { ...state };
      if (newState[key]) {
        newState[key].count += 1;
      }
      return newState;
    }
    case BasketActions.DEC: {
      const { key } = action;
      const newState = { ...state };
      if (newState[key]) {
        newState[key].count -= 1;
      }
      return newState;
    }
    case BasketActions.CLR: {
      return {};
    }
    default:
      return state;
  }
}

/packages/my-ui/build/Basket/BasketContext.js

import React from 'react';

const BasketContext = React.createContext({
  getState: () => ({}), // get current state
  addItem: () => {}, // add item to basket
  delItem: () => {}, // delete item from basket
  incItem: () => {}, // increment item's quantity
  decItem: () => {}, // decrement item's quantity
  clrItems: () => {}, // clear basket
});

export default BasketContext;

/packages/my-ui/build/Basket/BasketContainer.js

import React, { useReducer, useMemo } from 'react';
import PropTypes from 'prop-types';
import BasketContext from './BasketContext';
import basketReducer, {
  AddItemAction,
  DelItemAction,
  IncItemAction,
  DecItemAction,
  ClrItemsAction,
} from './BasketReducer';

export default function BasketContainer({ children }) {
  const [state, dispatch] = useReducer(basketReducer, {});
  const service = useMemo(() => ({
    getState: () => ({ ...state })
    addItem: (key, item) => dispatch(AddItemAction(key, item)),
    delItem: (key) => dispatch(DelItemAction(key)), 
    incItem: (key) => dispatch(IncItemAction(key)), 
    decItem: (key) => dispatch(DecItemAction(key)), 
    clrItems: () => dispatch(ClrItemsAction()), 
  }), [state]);
  return (
    <BasketContext.Provider value={service}>
      {children}
    </BasketContext.Provider>
  );
}
BasketContainer.propTypes = {
  children: PropTypes.node,
};
BasketContainer.defaultProps = {
  children: [],
};

/packages/my-ui/build/Basket/Basket.js

import React, { useContext, useMemo } from 'react';
import BasketContext from './BasketContext';

export default function Basket() {
  const service = useContext(BasketContext);

  const items = useMemo(() => service.getState(), [service]);
  const itemKeys = useMemo(() => Object.keys(items), [items]);
  const totalPrice = useMemo(() => itemKeys.reduce(
    (acc, cur) => (acc + (items[cur].price * items[cur].count)), 0,
  ), [itemKeys]);

  return (
    <div>
      <div>{itemKeys.map(k => (
        <div key={k}>
          {items[k].title}-{items[k].price}-{items[k].count}
        </div>))}
       </div>
       <div>Total Price: {totalPrice}</div>
    </div>
  )
}

/src/client/pages/StorePage..js

import React from 'react';
import Basket from '@myapp/ui/build/Basket';
import BasketContainer from '@myapp/ui/build/Basket/BasketContainer';
import BasketContext from '@myapp/ui/build/Basket/BasketContext';


export default function StorePage() {
  const storeItems = [/*my items*/];
  return (
    <BasketContainer>
       <BasketContext.Consumer>
         {({addItem}) =>( 
           <div>
             {storeItems.map(i => <button key={i.id} onClick={() =>addItem(i)}>{i.title}</button>)}
           </div>
         )}
       </BasketContext.Consumer>
       <div>
         <Basket .... /> //items did not show here when invoking addItem
       </div>
    </BasketContainer>
  )
}

To overcome this problem I had to change the way I'm importing my components from 'myapp/ui' package.

/packages/my-ui/build/Basket/index.js:

+import BasketContext from './BasketContext';
+import BasketContainer from './BasketContainer';

export default from './Basket';
+export {
+  BasketContext,
+  BasketContainer,
+}

/src/client/pages/StorePage..js

import React from 'react';
-import Basket from '@myapp/ui/build/Basket';
-import BasketContainer from '@myapp/ui/build/Basket/BasketContainer';
-import BasketContext from '@myapp/ui/build/Basket/BasketContext';
+import Basket, { BasketContainer, BasketContext } from '@myapp/ui/build/Basket';

...
50 views