'Storybook does not render Stories, Error: "useContext(...) is undefined"

If I create a Story for the following Component I only get a couple of messages in a red frame, which do not contain any information I can gain any insight from:

import React, { useState, useContext } from "react";
import { FormGroup, FormControl, Col } from "react-bootstrap";
import Button from "../button/Button";
import AuthContext from "../../AuthContext";
import { useHistory } from "react-router-dom";
import LoginWarning from "./LoginWarning";

export function Login(props) {
  const [email, setEmail2] = useState("");
  const [password, setPassword] = useState("");
  const [showAlert, setShowAlert] = useState(false);
  const {
    setRole,
    setName,
    setEmail,
    setSessionId,
    setLocalStorage
  } = useContext(AuthContext);
  let history = useHistory();

  function validateForm() {
    return email.length > 0 && password.length > 0;
  }

  const loginPost = {
    email: email,
    password: password
  };

  function handleSubmit(event) {
    event.preventDefault();

    const fetchUserInfo = async () => {
      const result = await fetch(`/account`, {
        method: "GET"
      });
      const body = await result.json();

      console.log(body);
      setRole(body.role);
      setName(body.name);
      setEmail(body.email);
      history.push("/home");
    };

    const fetchAuth = async () => {
      const result = await fetch(`/login`, {
        method: "POST",
        body: JSON.stringify(loginPost),
        headers: {
          "Content-Type": "application/json"
        }
      });
      const body = await result.json();

      console.log(body);
      if (body.isUser === true) {
        setSessionId(body.sessionId);
        setLocalStorage(body.sessionId);
        fetchUserInfo();
      } else {
        setShowAlert(true);
      }
    };
    fetchAuth();
  }

  return (
    <div>
      {showAlert ? <LoginWarning /> : null}
      <form onSubmit={handleSubmit}>
        <Col lg={6}>
          <FormGroup controlId="email" bsSize="large">
            <label>Email</label>

            <FormControl
              autoFocus
              type="email"
              value={email}
              onChange={e => setEmail2(e.target.value)}
            />
          </FormGroup>
        </Col>
        <Col lg={6}>
          <FormGroup controlId="password" bsSize="large">
            <label>Passwort</label>
            <FormControl
              value={password}
              onChange={e => setPassword(e.target.value)}
              type="password"
            />
          </FormGroup>
          <Button disabled={!validateForm()} type="submit" text="Login" />
        </Col>
      </form>
    </div>
  );
}

export default Login;

The Story looks like this:

import React from "react";
import { Login } from "./Login";
import { storiesOf } from "@storybook/react";

export default {
  title: "Login Form"
};

storiesOf("Login Form", module).add("default", () => <Login />);

And this is what Storybook is showing. Since my Component is showing in the App without any problems, I can't figure out what causes the problem for Storybook:

useHistory@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:100829:10
Login@http://localhost:6006/main.e31f087434cfd38286ae.bundle.js:763:84
renderWithHooks@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:84742:27
mountIndeterminateComponent@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:87276:13
beginWork$1@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:88638:16
callCallback@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:68837:14
invokeGuardedCallbackDev@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:68886:16
invokeGuardedCallback@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:68941:31
beginWork$$1@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:94239:28
performUnitOfWork@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:93166:12
workLoopSync@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:93139:22
performSyncWorkOnRoot@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:92738:11
scheduleUpdateOnFiber@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:92166:28
updateContainer@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:95562:15
legacyRenderSubtreeIntoContainer/<@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:95986:22
unbatchedUpdates@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:92901:12
legacyRenderSubtreeIntoContainer@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:95985:21
render@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:96073:12
render/<@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:20472:26
render@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:20471:10
_callee$@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:20563:20
tryCatch@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:19561:40
invoke@http://localhost:6006/vendors~main.e31f087434cfd38286ae.bundle.js:19787:30
defineIteratorMethods/

The browsers console shows the following:

The above error occurred in the <Login> component:
    in Login (at Login.stories.js:10)
    in AuthProvider (at Login.stories.js:10)
    in storyFn
    in ErrorBoundary

React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. react-dom.development.js:21810
    React 18
    render render.js:70
    render render.js:69
    _callee$ render.js:161
    Babel 8
    _callee$ start.js:408
    Babel 8
    renderUI start.js:453
    emit index.js:180
    setSelection story_store.js:325
TypeError: "useContext(...) is undefined"
    useHistory react-router.js:706
    Login Login.js:19
    React 16
    render render.js:70
    render render.js:69
    _callee$ render.js:161
    Babel 8
    _callee$ start.js:408
    Babel 8
    renderUI start.js:453
    emit index.js:180
    setSelection story_store.js:325


Solution 1:[1]

If your component uses useContext(), you actually have to initialize the context. Storybook has a different entry point from your app so all your normal app initialization won't happen unless you do it yourself.

The easiest way to do so is with a decorator. See https://storybook.js.org/docs/react/writing-stories/decorators#global-decorators for details on how to do this from 5.0+.

From 5.0 onwards, to do this globally, add something like this to .storybook/preview.js

export const decorators = [
  (Story) => (
    <YourContextProvider>
      <Story />
    </YourContextProvider>
  ),
];

Before 5.0, to do this globally, add something like this to your .storybook/config.js:

const StorybookWrapper = (storyFn) => (
  <YourContextProvider>
    {storyFn()}
  </YourContextProvider>
);
addDecorator(StorybookWrapper);

Updated: distinguish between pre-5.0 and 5.0+

Solution 2:[2]

You can create custom context decorator as follows

----- Button.js ----- 
export const Button = (props) => {
  const { state, dispatch } = useContext(BoxContext);
  return <button>{state.name}</button>
}
/*I had useContext in Button.js so need to
 create ContextDecorator*/

----- ContextDecorator.js -----
import React, { useReducer, createContext } from 'react';
import { initialState } from '../../App';
import { reducer } from '../../Context/Reducer';

export const BoxContext = createContext();

export const ContextDecorator = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
  return <BoxContext.Provider value={{state,dispatch}}>
     {props.children}
     </BoxContext.Provider>
}

----- AddButton.stories.js -----
import React from 'react';
import {Button} from './Button';
import { ContextDecorator } from './ContextDecorator';
export default {
  title: 'Example/AddButton',
  component: Button,
  decorators:[(Story) => {
      return <ContextDecorator><Story /></ContextDecorator>
  }],
  argTypes: {
      onClick: {
          action:'clicked'
      },
    backgroundColor: { control: 'color' },
    
  },
};

export const SampleAddButton = (args) => <Button {...args} />

Solution 3:[3]

I had the same issue: I wrapped my component in my .stories.tsx file <BrowserRouter> tags and BOOM, problem was gone. ¯_(?)_/¯

The culprit here is useHistory().

Damn, did that take a while.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Akshay Vijay Jain
Solution 3 Werewoof