Storybook: Tidak dapat menggunakan kait React langsung di dalam cerita

Dibuat pada 22 Feb 2019  ·  53Komentar  ·  Sumber: storybookjs/storybook

Jelaskan bugnya

Tidak dapat menggunakan pengait secara langsung dalam sebuah cerita, gagal dengan Hooks can only be called inside the body of a function component .

Untuk Mereproduksi

Kode contoh:

import React from 'react'

import { storiesOf } from '@storybook/react'

const stories = storiesOf('Hooks test', module)

const TestComponent: React.FC = () => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
}

stories.add('this story works', () => <TestComponent />)

stories.add('this story fails', () => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
})

Perilaku yang diharapkan
Cerita pertama berfungsi dengan baik, cerita kedua gagal pada render awal

Versi
@storybook/[email protected]
[email protected]
[email protected]

react has workaround question / support

Komentar yang paling membantu

Kami telah mengalami masalah yang sama, dan ini adalah peretasan sederhana yang kami lakukan untuk mengatasinya.

stories.add('this story fails', () => React.createElement(() => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
}))

Saya setuju akan jauh lebih baik jika itu didukung secara asli tanpa peretasan. Jika pengelola juga setuju maka mungkin saya bisa meluangkan waktu untuk membuat PR 🙂.

Semua 53 komentar

Tidak yakin ini bug. Saya percaya, dan saya bisa saja salah, argumen kedua stories.add mengharapkan fungsi untuk mengembalikan komponen dan bukan komponen React yang sebenarnya. Coba pindahkan komponen fungsi Anda ke luar, Anda akan berhasil ..

Contoh

function SomeComponent() {
    const [blah] = React.useState('blah');
    return <div> {blah}</div>;
}
stories.add('BlahComponent', () => <SomeComponent />);

Saya percaya, dan saya bisa saja salah, argumen kedua stories.add mengharapkan fungsi untuk mengembalikan komponen dan bukan komponen React yang sebenarnya.

AFAIK seharusnya tidak menjadi masalah, tetapi selalu patut dicoba. Selain itu, @sargant apa yang membuat kesalahan? Buku cerita atau sistem tipe?

@ Keraito Tidak, saya cukup yakin kesalahannya benar.

Itu karena kita memanggil fungsi di luar konteks react, alias buku cerita akan memanggil fungsi seperti ini:

const element = storyFn();

tidak

const element = <StoryFn />

Sangat mungkin jika kita memulainya seperti itu mungkin akan berhasil.

Saat ini saran @gabefromutah masuk akal.

Inilah baris kode yang sebenarnya:
https://github.com/storybooks/storybook/blob/next/app/react/src/client/preview/render.js#L24

Jika seseorang ingin bereksperimen untuk membuat ini berhasil, saya pikir di situlah untuk memulai.

@sargant apakah saran @gabefromutah cocok untuk Anda?

Saran @ndelangen @gabefromutah sama dengan inisial saya contoh "cerita ini berhasil" saya percaya?

Jika ini bukan bug, ini mungkin masih merupakan peningkatan yang berguna untuk menghindari penggunaan plugin pihak ketiga untuk status.

Sangat mungkin jika kita memulainya seperti itu mungkin akan berhasil.

@ndelangen tidak sepenuhnya yakin bagaimana itu akan berinteraksi dengan addons tertentu lainnya, terutama addon-info , yang membutuhkan informasi seperti props dari komponen yang dirender yang mendasarinya.

Kami telah mengalami masalah yang sama, dan ini adalah peretasan sederhana yang kami lakukan untuk mengatasinya.

stories.add('this story fails', () => React.createElement(() => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
}))

Saya setuju akan jauh lebih baik jika itu didukung secara asli tanpa peretasan. Jika pengelola juga setuju maka mungkin saya bisa meluangkan waktu untuk membuat PR 🙂.

@ kevin940726 itu akan luar biasa 👍

Menambahkan yang berikut ke .storybook / config.js saya berhasil untuk saya

addDecorator((Story) => <Story />)

Saya telah menerapkan sesuatu yang serupa, tetapi saya pikir itu merusak banyak addons, khususnya addon-info untuk mengeluarkan dokumentasi prop. Saya telah memutuskan menggunakan hook lebih penting daripada info itu (karena saya menghasilkan itu secara terpisah) tetapi saya ragu itu akan berlaku untuk semua buku cerita secara umum. Tidak tahu bagaimana Anda akan menangani hook seperti api di luar react.

Saya mencoba menerapkannya di kode sumber, tetapi saya mengalami kesulitan untuk membuatnya berfungsi dengan storyshot-addon. Saya pikir ini akan menjadi perubahan besar karena akan menghasilkan node induk baru di setiap snapshot. Karena kami tidak mengontrol penyaji yang dipilih pengguna, kami tidak dapat menyelami satu tingkat lebih dalam. Saya pikir kita mungkin harus memikirkan alternatif lain, seperti kita dapat mendokumentasikan solusi dan mungkin menyediakan beberapa API pembantu bagi pengguna untuk ikut serta.

Menambahkan yang berikut ke .storybook / config.js saya berhasil untuk saya

addDecorator((Story) => <Story />)

@emjaksa Bisakah Anda memberikan potongan ini?

Ini adalah solusi saya untuk masalah ini:

import React, { useState } from 'react';

import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withInfo } from '@storybook/addon-info';

import SelectField from 'component-folder/SelectField';

/**
 * special wrapper that replaces the `value` and `onChange` properties to make
 * the component work hooks
 */
const SelectFieldWrapper = props => {
  const [selectValue, setValue] = useState('');

  return (
    <SelectField
      {...props}
      value={selectValue}
      onChange={e => {
        setValue(e.target.value);
        action('onChange')(e.target.value);
      }}
    />
  );
};
SelectFieldWrapper.displayName = 'SelectField';

const info = {
  text: SelectField.__docgenInfo.description,
  propTables: [SelectField],
  propTablesExclude: [SelectFieldWrapper]
};

storiesOf('Controls/SelectField', module)
  .addDecorator(withInfo)

  // ... some stories

  // this example uses a wrapper component to handle the `value` and `onChange` props, but it should
  // be interpreted as a <SelectField> component
  .add('change handler', () => 
    <SelectFieldWrapper
      id="employment-status"
      placeholder="some placeholder"
      value={//selectValue}
      onChange={e => {
          // setValue(e.target.value);
      }}
    />, { info });

Seperti yang saya sebutkan masih merupakan solusi, tetapi itu berhasil dan tidak merusak info addon. (Saya belum menguji addons lain)

Addon info saya tidak rusak asalkan saya menambahkan dekorator Story terakhir.

import React from 'react'

import { configure, addDecorator } from '@storybook/react'
import { withInfo } from '@storybook/addon-info'
import { withKnobs } from '@storybook/addon-knobs'

const req = require.context('../src', true, /\.stories\.js$/)

function loadStories() {
  req.keys().forEach(filename => req(filename))
}

addDecorator(
  withInfo({
    header: false,
  }),
)
addDecorator(withKnobs)
addDecorator((Story) => (
    <Story />
))

configure(loadStories, module)

Solusi lain:

Saya memiliki komponen utilitas bernama UseState didefinisikan seperti ini:

export const UseState = ({ render, initialValue }) => {
    const [ variable, setVariable ] = useState(initialValue)
    return render(variable, setVariable)
}

Dan saya menggunakannya dalam cerita seperti ini:

.add('use state example', () => (
    <UseState
        initialValue={0}
        render={(counter, setCounter) => (            
            <button onClick={() => setCounter(counter + 1)} >Clicked {counter} times</button>
        )}
    />
)

Tapi saya suka solusi terbaik dari @ kevin940726

Saya tidak bisa mendapatkan cerita untuk ditampilkan ulang pada perubahan keadaan. Paksa rendering ulang juga tidak berfungsi.

Meskipun kode ini berfungsi dengan baik untuk React Hooks

storiesOf("Dropdowns", module).add("Basic", () => <DropdownBasicStory />);

Ini tidak bekerja dengan baik dengan @storybook/addon-info :
Screenshot 2019-05-13 14 35 10

Ini membuat solusi ini tidak dapat digunakan. Ada ide? Buku Cerita 5.1.0-beta.0

@artyomtrityak Anda dapat mengganti opsi propTables di addon-info ? Saya akan memecahkan masalah ini dengan benar di addon-docs : https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a mendatang

@shilman akankah itu juga menyertakan sumber <DropdownBasicStory /> ?

@artyomtrityak Saya akan melihat apa yang dapat saya lakukan 😆

Halo semuanya! Sepertinya tidak banyak yang terjadi dalam masalah ini belakangan ini. Jika masih ada pertanyaan, komentar, atau bug, silahkan lanjutkan pembahasannya. Sayangnya, kami tidak punya waktu untuk membahas setiap masalah. Kami selalu terbuka untuk kontribusi, jadi kirimkan permintaan bantuan jika Anda ingin membantu. Masalah yang tidak aktif akan ditutup setelah 30 hari. Terima kasih!

Jika ada orang lain yang masih mendapatkan ini, saya menemukan bahwa masalah tersebut tampaknya menambahkan jenis prop ke komponen fungsional.

const Dropdown = () => (
  // component content
);

Dropdown.propTypes = {
  ...
}

Untuk beberapa alasan, mengomentari .propTypes sepertinya berhasil. Tidak yakin apakah ini masalah dengan penguraian jenis prop untuk dokumentasi atau yang lainnya.

Untuk menampilkan kode sumber komponen dengan kait

function WithState({ children }) {
  const [value, setValue] = React.useState([]);

  return React.cloneElement(children, {
    value,
    onChange: event => setValue(event.target.value),
  });
}

storiesOf(`${__dirname}`, module).add('Basic', () => (
  <WithState>
    <select value="[parent state]" onChange="[parent func]">
      <option value="Australia">Australia</option>
      <option value="Cambodia">Cambodia</option>
    </select>
  </WithState>
));

Halo semuanya! Sepertinya tidak banyak yang terjadi dalam masalah ini belakangan ini. Jika masih ada pertanyaan, komentar, atau bug, silahkan lanjutkan pembahasannya. Sayangnya, kami tidak punya waktu untuk membahas setiap masalah. Kami selalu terbuka untuk kontribusi, jadi kirimkan permintaan bantuan jika Anda ingin membantu. Masalah yang tidak aktif akan ditutup setelah 30 hari. Terima kasih!

Berhati-hatilah dengan pendekatan React.createElement dan <Story /> , saya pikir pendekatan tersebut kurang tepat dalam beberapa kasus, karena Anda sebenarnya membuat ulang komponen react (komponen cerita) daripada mengembalikan elemen yang dirender (saat menggunakan story() di buku cerita config.js seperti yang ditunjukkan di dokumen).

Misalnya, ketika cerita Anda memiliki Knobs.button yang memicu pembaruan status lokal komponen, setelah mengeklik tombol, Anda kemungkinan besar akan membuat komponen cerita baru daripada memperbarui status komponen saat ini.

Anda dapat memverifikasi asumsi saya dengan potongan kode sederhana ini, di mana nilainya tidak akan diperbarui setelah mengklik tombol kenop.

storiesOf('Test', module).add('with text', () => {
  return React.createElement(() => {
    const [value, setValue] = React.useState(1);

    Knobs.button('Increase', () => setValue(prev => prev + 1));

    return <span>{value}</span>;
  });
});

Untuk membuatnya berhasil, Anda dapat melakukan:

function MyStory() {
  const [value, setValue] = React.useState(1);

  Knobs.button('Increase', () => setValue(prev => prev + 1));

  return <span>{value}</span>;
}

storiesOf('Test', module).add('with text', () => <MyStory />);

Oleh karena itu, sebagai solusinya, saya telah membuat pembungkus:

import {
  DecoratorParameters,
  Story,
  StoryDecorator,
  storiesOf as origStoriesOf,
} from '@storybook/react';

class ReactStory {
  private readonly story: Story;

  constructor(name: string, module: NodeModule) {
    this.story = origStoriesOf(name, module);
  }

  public add(
    storyName: string,
    Component: React.ComponentType,
    parameters?: DecoratorParameters
  ): this {
    this.story.add(storyName, () => <Component />, parameters);
    return this;
  }

  public addDecorator(decorator: StoryDecorator): this {
    this.story.addDecorator(decorator);
    return this;
  }

  public addParameters(parameters: DecoratorParameters): this {
    this.story.addParameters(parameters);
    return this;
  }
}

new ReactStory('Test', module).add('with text', () => {
  const [value, setValue] = React.useState(1);

  Knobs.button('Increase', () => setValue(prev => prev + 1));

  return <span>{value}</span>;
});

Secara opsional, Anda dapat meniru API buku cerita untuk mengurangi overhead pemfaktoran ulang, dan beralih kembali nanti saat masalah telah diperbaiki.

export function storiesOf(name: string, module: NodeModule) {
  return new ReactStory(name, module);
}

Saya mungkin salah, jika demikian, beri tahu saya. Sangat dihargai!

Ta-da !! Saya baru saja merilis https://github.com/storybookjs/storybook/releases/tag/v5.2.0-beta.10 berisi PR # 7571 yang mereferensikan masalah ini. Tingkatkan versi hari ini untuk mencobanya!

Anda dapat menemukan prarilis ini di tag @next NPM.

Menutup masalah ini. Harap buka kembali jika menurut Anda masih banyak yang harus dilakukan.

@septianjoko_ apakah Anda memiliki contoh cuplikan kode tentang cara menggunakannya?

@shilman terima kasih!
tetapi masih mendapatkan kesalahan:
React Hook "useState" dipanggil dalam fungsi "komponen" yang bukan merupakan komponen fungsi React atau fungsi React Hook kustom

@shiranZe Apakah Anda menggunakan versi 5.2-beta terbaru?

@septianjoko_hhh ...

kode saya di cerita / komponen / Menu / index.js:

komponen const = () => {
const [buttonEl, setButtonEl] = useState (null)

const handleClick = (event) => {
    console.log('the event', event)
    setButtonEl(event.currentTarget)
}

return (
    <>
        <IconButton iconName={"menu-hamburger"} size="s" onClick={handleClick} color={"midGray"}></IconButton>

ekspor default [readme, komponen];

dan di stories / components / index.js:
storiesOf('Components', module) .addDecorator(withKnobs) .add('Text', withReadme(...Menu))

@shiranZe kurasa ini masalah dengan addon-readme - mungkin mengajukan masalah di sana?

Terima kasih @shilman atas balasan Anda. Saya tidak tahu apakah itu masalahnya. Saya mencoba tanpa addon-readme dan masih mendapatkan kesalahan yang sama. apakah Anda memiliki URL dari buku cerita 5.2-beta?
saya mencoba melihat https://storybooks-official.netlify.com/ (other | demo / Button)
tetapi saya tidak dapat menemukan di sana contoh dengan kait bereaksi.

Saya masih memiliki masalah dengan 5.2.0-beta.30 dan masalahnya adalah tidak ada solusi yang berhasil untuk saya, karena saya juga menggunakan tombol addon.

@shiranZe penerapan netlify kami dimatikan (cc @ndelangen) tetapi Anda dapat memeriksa repo di cabang next dan mencobanya di sana.

@sourcesoft Saya percaya bahwa masalah "kait pratinjau" yang terkait dengan kenop (cc @Hypnosphi) tidak ada hubungannya dengan masalah ini - satu-satunya kesamaan yang mereka miliki adalah konsep kait.

@shil Terima kasih, saya pikir Anda benar. Btw saya menemukan cara memperbaikinya dengan coba-coba, mengubah kait khusus:

export const useField = (id, updateField) => {
  const onChange = useCallback((event) => {
    const {
      target: { value },
    } = e;
    updateField(id, value);
  };

  return {
    onChange,
  };
}, []);

untuk

export const useField = (id, updateField) => {
  const onChange = (event) => {
    const {
      target: { value },
    } = e;
    updateField(id, value);
  };

  return {
    onChange,
  };
};

Pada dasarnya baru saja menghapus penggunaan useCallback sini. Saya tidak yakin apakah versi pertama adalah pengait yang valid tetapi dulu berfungsi. Juga agak membingungkan ketika kesalahan mengatakan Hooks can only be called inside the body of a function component atau memiliki beberapa versi React.

Dalam contoh di atas, setelah menghapus useCallback , apakah Anda benar - benar useField sama sekali? 🤔

sepertinya ada masalah saat menggunakan kait dengan kenop. Jika ada yang bisa memberikan repro sederhana, saya akan dengan senang hati melihatnya

@shilman Sudahkah Anda mencoba contoh yang saya posting sebelumnya? Berikut cuplikannya:

storiesOf('Test', module).add('with text', () => {
  return React.createElement(() => {
    const [value, setValue] = React.useState(1);

    Knobs.button('Increase', () => setValue(prev => prev + 1));

    return <span>{value}</span>;
  });
});

Ini menggunakan API lama, tetapi harus mudah diubah ke API terbaru untuk pengujian. Anda dapat menemukan perilaku yang diharapkan dari postingan asli .

@zhenwenc FYI kode tersebut berfungsi, tetapi merusak penggunaan dokumen yang dirender oleh react-docgen-typescript-webpack-plugin .

Solusi tampaknya agak rapuh dan bertentangan dengan perpustakaan lain. Sebagai alternatif, apakah seseorang memiliki pengalaman menggunakan ?: https://github.com/Sambego/storybook-state

Untuk yang di sini googling pesan kesalahannya:

Saya mendapat kesalahan ini ketika mengubah komponen kelas menjadi komponen fungsional dengan kait dan useState salah mengimpor dari @storybook/addons . Saya membutuhkannya dari react daripada @storybook/addons ... impor otomatis gagal.

Baru saja menjawab permintaan @orpheus untuk mendapatkan cuplikan

Menambahkan yang berikut ke .storybook / config.js saya berhasil untuk saya
addDecorator((Story) => <Story />)

@emjaksa Bisakah Anda memberikan potongan ini?

diff --git a/.storybook/config.js b/.storybook/config.js
--- a/.storybook/config.js
+++ b/.storybook/config.js
@@ -1,6 +1,9 @@
-import { configure } from '@storybook/react';
+import { configure, addDecorator } from '@storybook/react';
+import React from 'react';

 // automatically import all files ending in *.stories.js
 configure(require.context('../stories', true, /\.stories\.js$/), module);
+
+addDecorator((Story) => <Story />);

Hasil (selesaikan .storybook/config.js ):

import { configure, addDecorator } from '@storybook/react';
import React from 'react';

// automatically import all files ending in *.stories.js
configure(require.context('../stories', true, /\.stories\.js$/), module);

addDecorator((Story) => <Story />);

Saya tidak yakin apakah ini adalah cara terbaik untuk membuat kait reaksi bekerja di dalam buku cerita, tetapi membungkus buku cerita dalam komponennya sendiri <Story /> komponen bekerja di sini dengan "@storybook/react": "^5.2.6", .

Sebelum melakukan ini, setiap kali saya memperbarui sebuah knob (yaitu: a boolean), itu bekerja pada render pertama, tetapi kemudian berhenti rendering sesudahnya. Solusi di atas memperbaikinya. Ngomong-ngomong, saya tidak yakin itu praktik yang baik untuk menggunakan kait reaksi di buku cerita, cukup mengejek semuanya jika memungkinkan, mungkin lebih baik seperti ini.

Jelaskan bugnya

Tidak dapat menggunakan pengait secara langsung dalam sebuah cerita, gagal dengan Hooks can only be called inside the body of a function component .

Untuk Mereproduksi

Kode contoh:

import React from 'react'

import { storiesOf } from '@storybook/react'

const stories = storiesOf('Hooks test', module)

const TestComponent: React.FC = () => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
}

stories.add('this story works', () => <TestComponent />)

stories.add('this story fails', () => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
})

Perilaku yang diharapkan
Cerita pertama berfungsi dengan baik, cerita kedua gagal pada render awal

Versi
@storybook/[email protected]
[email protected]
[email protected]

======================================

Jangan gunakan fungsi panah untuk membuat komponen fungsional.
Lakukan seperti salah satu contoh di bawah ini:

function MyComponent(props) {
  const [states, setStates] = React.useState({ value: '' });

  return (
    <input
      type="text"
      value={states.value}
      onChange={(event) => setStates({ value: event.target.value })}
    />
  );
}

Atau

//IMPORTANT: Repeat the function name

const MyComponent = function MyComponent(props) { 
  const [states, setStates] = React.useState({ value: '' });

  return (
    <input
      type="text"
      value={states.value}
      onChange={(event) => setStates({ value: event.target.value })}
    />
  );
};

Jika Anda mengalami masalah dengan "ref" (mungkin dalam loop), solusinya adalah menggunakan forwardRef ():

// IMPORTANT: Repeat the function name
// Add the "ref" argument to the function, in case you need to use it.

const MyComponent = React.forwardRef( function MyComponent(props, ref) {
  const [states, setStates] = React.useState({ value: '' });

  return (
    <input
      type="text"
      value={states.value}
      onChange={(event) => setStates({ value: event.target.value })}
    />
  );
});

bagaimana ini bisa dicapai di MDX?

tetapi masih mendapatkan kesalahan:
React Hook "useState" dipanggil dalam fungsi "komponen" yang bukan merupakan komponen fungsi React atau fungsi React Hook kustom
https://github.com/storybookjs/storybook/issues/5721#issuecomment -518225880

Saya mengalami masalah yang sama persis. Cara mengatasinya adalah memanfaatkan ekspor cerita bernama.

 import React dari 'react';
 impor Foo dari './Foo';

 ekspor default {
 judul: 'Foo';
 };

 ekspor const Basic = () => <Foo />

Dokumen mengatakan kapitalisasi disarankan tetapi itu diperlukan jika Anda ingin peringatan itu hilang.

Dalam kasus saya, masalahnya adalah saya lupa mengimpor pengait yang saya gunakan.

Menambahkan yang berikut ke .storybook / config.js saya berhasil untuk saya

addDecorator((Story) => <Story />)

@emjaksa Thanx! Diuji dan berfungsi dengan baik di Storybook v5.3.

Saya memiliki masalah yang sama, komponen reaksi fungsional saya memiliki useState(value) dan value tidak merender ulang nilai baru dari Knobs, masalah ini disebabkan oleh useState:

Dari dokumen React:
Screen Shot 2020-08-07 at 19 00 04

Solusi kerja Buku Cerita v5.3:

Perbarui preview.js

.storybook/preview.js
import React from 'react'; // Important to render the story
import { withKnobs } from '@storybook/addon-knobs';
import { addDecorator } from '@storybook/react';

addDecorator(withKnobs);
addDecorator(Story => <Story />); // This guy will re-render the story

Dan juga perbarui main.js

.storybook/main.js
module.exports = {
  stories: ['../src/**/*.stories.jsx'],
  addons: [
    '@storybook/preset-create-react-app',
    '@storybook/addon-knobs/register', // Attention to this guy
    '@storybook/addon-actions',
    '@storybook/addon-links',
  ],
  webpackFinal: async config => {
    return config;
  },
};



Adakah yang tahu mengapa dekorator pemohon ini membuat kait berfungsi?

`` tsx
SomeComponent.decorators = [(Cerita) => ]

@tmikeschu karena <Story /> membungkus kode dalam React.createElement yang diperlukan untuk pengait.

@shilman terima kasih!

Apakah halaman ini membantu?
0 / 5 - 0 peringkat