storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
83.94k stars 9.21k forks source link

Ability to refresh (force rerender) of story #1736

Closed victorandree closed 6 years ago

victorandree commented 7 years ago

It would be neat if addons could tell the Storybook API to refresh a given story. Using storybookAPI.selectStory() does nothing if the story is already selected.

This would be useful when a story wants to customize hot-reloading to maintain parts of its state, through e.g. a Redux store. This would support stories like this:

import { storiesOf } from '@storybook/react';
import reselectStory from 'storybook-hot';
import * as React from 'react';

import configureStore from './configureStore';
import HotStory from './HotStory';

// Configure your Redux store once. If you make edits to *this* module, it
// will get hot-reloaded by its parent so that your Redux state gets reset.
const store = configureStore();

// Store a mutable reference to the story, which gets replaced on hot reload
let Story = HotStory;

storiesOf('Hot story').add('Example', () => <Story store={store} />);

// Whenever the story changes, get the new module and tell Storybook to
// re-render us.
module.hot.accept('./HotStory', () => {
  Story = require('./HotStory').default;

  // Tell our addon to tell the API to reselect our story.
  reselectStory('Hot story', 'Example');
});

I've already written an addon (unpublished) which does this by selecting another (dummy) story and selecting back to the original story. It works, quite well, but requires a dummy story. This is obviously imperfect, and you still have to do hard reloads from time-to-time, but it comes close to the "regular" app workflow but I can isolate mocking my network stack to my stories, specifically.

The relevant "blocker" is in init_api.js#L48. I realize you wouldn't usually want to emit an event for the same story, but maybe there could be an escape hatch for an explicit re-render?

ndelangen commented 7 years ago

This is really interesting! We actually do need a good method to push new data to a currentSelectedStory, because Vue and Angular work quite different from react. And we want to wrap the storyFn as little as possible actually.

1209

stale[bot] commented 6 years ago

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. We do try to do some housekeeping every once in a while so inactive issues will get closed after 90 days. Thanks!

dangreenisrael commented 6 years ago

I'll take a crack at it when I get a chance

Hypnosphi commented 6 years ago

Released as 3.4.0-alpha.0

netpoetica commented 6 years ago

Is there any doc on how to use this/is this actually available?

dangreenisrael commented 6 years ago

@netpoetica https://github.com/storybooks/storybook/blob/4b5622a1c1d8d4cc44160b659289f954e8740b13/examples/cra-kitchen-sink/src/stories/force-rerender.stories.js

axelnormand commented 6 years ago

awesome! is this method available for the react native storybook? I couldn't see it in the src

Hypnosphi commented 6 years ago

No. I'm going to support it there as part of #3555 (It's needed for knobs)

hrishikeshpotdar91 commented 5 years ago

How do I use this in Angular? Any examples?

transitorytammy commented 5 years ago

How do we use this with Ember? Is there support for that?

robbertvancaem commented 5 years ago

For anyone trying to accomplish this in combination with a button-knob, I managed to do so with a little workaround.

It's important that you specify a key that is based on a counter variable which you update. This key will be used by React to determine if it should re-render or not. I tried getting it to work in combination with the forceReRender function but that didn't work

(input on how to get this working using the forceReRender method is very welcome as it didn't trigger a re-render for me)

It's worth noting that a global variable should exist, f.e. counter which you update. Not the most elegant, but it works.

import { button } from '@storybook/addon-knobs';

let counter = 0;
const reRender = () => ( counter += 1)

storiesOf("Story title", module).add("default", () => {
  button("Refresh story", reRender);
  return (
    <div key={counter}>Content of the story</div>
  )
});

I'm using @storybook/react@5.0.11

martinsik commented 1 year ago

For anyone wondering how to use this feature in Angular you need to import the function:

import { forceReRender } from '@storybook/angular';

... and then use it whenever you need.

emqfe5l commented 1 year ago

Working Solution Example (inside preview.js):

import { addons } from '@storybook/addons';
import { FORCE_REMOUNT, STORY_ARGS_UPDATED } from '@storybook/core-events';

const channel = addons.getChannel();

const storyListener = (args) => {
    if (args.storyId === 'your-story-id') {
        addons.getChannel().emit(FORCE_REMOUNT, { storyId: 'your-story-id' });
    }
};

const setupStoryListener = () => {
    channel.removeListener(STORY_ARGS_UPDATED, storyListener);
    channel.addListener(STORY_ARGS_UPDATED, storyListener);
}

setupStoryListener();
petrvostry commented 1 year ago

Hello, please does anyone have working solution of

addons.getChannel().emit(FORCE_REMOUNT, { storyId: 'your-story-id' });

with Storybook 7?

This code works perfectly with sb6, but after upgrade it stopped work.

Thank you

kasir-barati commented 11 months ago

Just coming from the future 2023, this function has been removed from storybook/react oin version 7

Error: @storybook/client-api:forceReRender was removed in storyStoreV7.
chosh-dev commented 6 months ago

TEMP SOLUTION for Storybook7

function forceRerender() {
  const buttons = window.parent.document.querySelectorAll('button[title]');
  for (const button of buttons) {
    if (button.getAttribute('title') === "Remount component") {
      button.click();
      return
    }
  }
}
joe-was-here commented 6 months ago

I think I have a related issue. I have some globalTypes that change a value which is used in decorators like so

<div id="root" className={context.globals.themeClass}>

This updates properly, but my component still has the previous class, if I click the button in the toolbar for a theme a second time it updates appropriately.