Open ai212983 opened 1 week ago
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. This has already been reported in #33, and sadly things have not changed on the SB side.
You can see https://github.com/storybookjs/storybook/issues/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 args
object.
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 useArgs
to restore my component state, so it kinda works.
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.
I'm using useArgs to restore my component state What do you mean exactly ?
@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}
/>
);
};
well, the usual
@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 !
...but yeah, it cannot work.
@JesusTheHun Not sure if I follow you here :) The example above works fine for me. items
are part of the args:
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. updateArgs
in the example above works as a state setter.
If we can use useArgs
inside the decorator, probably it is possible to parametrize withRouter
so it returns useArgs
? Not sure how decorators work though (maybe I should lol).
The example above works fine for me
Yes, but it doesn't use a decorator.
If we can use useArgs inside the decorator, probably it is possible to parametrize withRouter so it returns useArgs? Not sure how decorators work though (maybe I should lol).
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.
I've ended with special render function which uses useState
for state management. Linter is complaining, but it works.
Can't wait to test a custom decorator though ;)
@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 increase
.
@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 !
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