-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
useArgs
in render function breaks Storybook
#67
Comments
useArgs
in render function breaks Storybook
Hello @ai212983 👋 Sadly this is true with any decorator. In the example below, as soon as you click on the button to increment the value, it will break. export const RawStory = {
decorators: [
(Story: any) => {
return (
<section>
decorated:
<Story />
</section>
);
},
],
render: (args: { foo: string }) => {
const [updateArgs] = useArgs();
const [count, setCount] = useState(0);
return (
<>
<h1>Args</h1>
<p>{JSON.stringify(args)}</p>
<button onClick={() => setCount((count) => count + 1)}>Increase</button>
<div role={'status'}>{count}</div>
</>
);
},
args: {
foo: 'bar',
},
}; The reason for that is SB clears the storyContext as soon as the story is rendered. So during the first render it works, but on the next render, it breaks. You can see storybookjs/storybook#12006 . Now, if what you want is only to read the story args, you should known that the first argument received by the story function is the export const RawStory = {
render: ({ foo }) => { // <== the args are accessible here
const location = useLocation();
const [updateArgs] = useArgs();
return (
<div>
<p>{location.pathname}</p>
<Link to={'/login'}>Login</Link> | <Link to={'/signup'}>Sign Up</Link>
</div>
);
},
args: { foo: 'bar' }
} |
@JesusTheHun Hey there! :) You're right, the story context does reset on every re-render. I'm using I did see both of those tickets but thought maybe there was some way to work around this. Thanks so much for explaining. I guess you can go ahead and close the ticket now. |
|
@JesusTheHun well, the usual. Something like this: const defaultRender = function Render(args: any) {
const [{ onChange }, updateArgs] = useArgs();
return (
<ComboList
{...args}
onChange={onChange
? (items: ListItems[]) => updateArgs({ items })
: undefined}
/>
);
}; |
@ai212983 I've never used it like that. That's not a bad idea, but yeah, it cannot work. A dedicated decorator with a context and hook would work though ! |
@JesusTheHun Not sure if I follow you here :) The example above works fine for me. export const DefaultUsage: Story = {
// @ts-ignore
args: {
// @ts-ignore
onChange: false,
items: generateItems(5),
layers: generateLayerInfos(50),
},
render: defaultRender,
}; they are passed to my component by the render function. If we can use |
Yes, but it doesn't use a decorator.
We could do some reference passing, but that's a bit dirty. Maybe I'll create such decorator as a standalone package, it should be straight forward. Maybe next week, I'll keep you posted ;) |
@JesusTheHun Actually it does. Here's the full code for ComboList.stories.tsx: import "../styles/tailwind.css";
import "../styles/index.scss";
import { ComboList } from "@/Keyboard/Combos/ComboList";
import { ZMKCombo } from "@/localResources";
import { SearchContext } from "@/providers";
import { faker } from "@faker-js/faker";
import { useArgs } from "@storybook/preview-api";
import { Meta, StoryObj } from "@storybook/react";
import { createFnMapping, createTestSearchContext, generateItemDescription, generateLayerInfos } from "./common";
const meta = {
title: "Keyboard/Combo/List",
component: ComboList,
parameters: {
layout: "padded",
},
argTypes: {
onItemUpdate: {
control: "boolean",
name: "Read Only",
mapping: createFnMapping(false),
},
onItemDelete: { table: { disable: true } },
onItemCreate: { table: { disable: true } },
},
decorators: [
(Story) => (
<div className="tailwind" style={{ height: "calc(100vh - 3rem)" }}>
<div className="h-full">
<Story />
</div>
</div>
),
],
} satisfies Meta<typeof ComboList>;
// noinspection JSUnusedGlobalSymbols
export default meta;
type Story = StoryObj<typeof meta>;
const searchContext = createTestSearchContext(20);
const defaultRender = function Render(args: any) {
const [{ items, onItemUpdate }, updateArgs] = useArgs();
const setItems = (items: ZMKCombo[]) => {
updateArgs({ items });
};
return (
<SearchContext.Provider value={searchContext}>
<ComboList
{...args}
onItemDelete={(name: string) => {
setItems(items.filter((m: ZMKCombo) => m.name !== name));
}}
onItemUpdate={onItemUpdate
? (name: string, item: ZMKCombo) => {
setItems(items.map((m: ZMKCombo) => (m.name === name ? item : m)));
}
: undefined}
onsItemCreate={(item: ZMKCombo) => {
setItems([...items, item]);
}}
/>
</SearchContext.Provider>
);
};
// noinspection JSUnusedGlobalSymbols
export const DefaultUsage: Story = {
// @ts-ignore
args: {
items: generateItems(5),
layers: generateLayerInfos(50),
// @ts-ignore
onItemUpdate: false,
},
render: defaultRender,
}; There's no any logic in this decorator, but still. |
@ai212983 can you provide a repro inside a stackblitz for example ? or a git repo |
@JesusTheHun Here you go - also I've updated your project so Stackblitz displays Stories by default. Navigate to Demo/useArgs/Default Usage story and click |
@ai212983 yes it does work in this case, because the args update do not trigger the decorator function to run again. |
@JesusTheHun Not exactly so. I've updated the example, you can trigger re-run decorator function with local state counter. |
@ai212983 Indeed. I've also tried to create an addon to have a custom hook. It's harder than it looks like ! |
I hope I can use
Any workaround? |
@helloint well really it's a limitation of Storybook addons in React. It's not specific to this addon. I've started to work on something to workaround it but it's a mess. I really hope SB will fix this on their end. |
Storybook crashes with
Error: Storybook preview hooks can only be called inside decorators and story functions.
when attempting to use theuseArgs
hook withstorybook-addon-remix-react-router
.To Reproduce
useArgs
hook:Additional context
Although SB hooks are not permitted directly inside components, they are allowed within render functions. The inability to use
useArgs
prevents testing configurable component behavior in response to navigation events.In my specific case, it affects the
useBlocker
hook in my custom form component.Environment
The text was updated successfully, but these errors were encountered: