Storybook does not render Stories, Error: "useContext(...) is undefined"
Asked Answered
L

4

8

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
Lenticularis answered 15/11, 2019 at 16:43 Comment(6)
It would still be helpful to know what the error messages are.Vevine
I added a picture of the Console. My App heavily relies on a React Context which Stores the users role, which is used for conditional rendering and the protected routes. I guess since Storybook can't make use of this context it is throwing these errors?Lenticularis
See why not upload images of code on SO when asking a question and idownvotedbecau.se/imageofanexceptionBuckra
I actually cannot access Imgur on this network :(Vevine
Sorry wasn't aware of that problem, will update the post in a sec.Lenticularis
Isn't this overkill - if storybook has context you presumably also have context in your app so you have context in context and its an uncontrolled component so there may be issues with re-rendering? What is thought to be best practice - imo it'd be best to avoid context in storybook and use state sparingly - i.e. best to use controlled components.Windjammer
P
15

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+

Picturize answered 5/12, 2019 at 1:27 Comment(2)
Can you please give the exact answer how to configureContour
@Contour you'll have to figure out what works for your specific case, but I've updated the links to the latest docs and updated the example for the current version of StorybookPicturize
A
2

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.

Antifederalist answered 13/11, 2021 at 18:17 Comment(0)
B
1

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} />
Buitenzorg answered 13/9, 2020 at 4:21 Comment(0)
D
0

I was wrapping my context providers in preview.js file but it was not still working. My problem was solved when I changed render from passing component directly to an arrow function, though this way I lost "show code":

Before:

export const Default = {
  render: DefaultExample,
};

✅ After:

export const Default = {
  render: () => <DefaultExample />,
};
Dispatcher answered 16/3 at 10:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.