[WIP]πŸ“˜stories λ‚΄ render ν•¨μˆ˜κ°€ React μ»΄ν¬λ„ŒνŠΈκ°€ μ•„λ‹Œ 일반 ν•¨μˆ˜λ‘œ μΈμ‹λ˜μ–΄ useState ν›… 호좜이 κΈˆμ§€λ˜μ–΄ λ°œμƒν•˜λŠ” 였λ₯˜

2025. 10. 27. 14:21ㆍTrouble Shooting & Issues/Linkiving

λ°˜μ‘ν˜•

TextField.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';
import React, { useState } from 'react';

import Image from '../../../public/icons/Image';
import TextField from './TextField';

const meta: Meta<typeof TextField> = {
  title: 'Components/TextField',
  component: TextField,
  tags: ['autodocs'],
  argTypes: {
    size: {
      control: { type: 'select' },
      options: ['sm', 'md', 'lg'],
    },
    radius: {
      control: { type: 'select' },
      options: ['none', 'sm', 'md', 'lg'],
    },
    variant: {
      control: { type: 'select' },
      options: ['outline', 'filled'],
    },
    disabled: {
      control: { type: 'boolean' },
    },
  },
};

export default meta;
type Story = StoryObj<typeof TextField>;

export const Default: Story = {
  render: args => {
    const [value, setValue] = useState('');

    return <TextField {...args} value={value} onChange={e => setValue(e.target.value)} />;
  },
  args: {
    placeholder: 'Enter text...',
    size: 'md',
    radius: 'md',
    variant: 'outline',
    icon: <Image src="file.svg" alt="img" />,
    disabled: false,
  },
};

 

error

34:31  Error: React Hook "useState" is called in function "render" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".  react-hooks/rules-of-hooks

 

ESLint react-hooks/rules-of-hooks κ·œμΉ™μ— μ˜ν•΄ Storybook μŠ€ν† λ¦¬ λ‚΄ render ν•¨μˆ˜κ°€ React μ»΄ν¬λ„ŒνŠΈκ°€ μ•„λ‹Œ 일반 ν•¨μˆ˜λ‘œ μΈμ‹λ˜μ–΄ useState ν›… 호좜이 κΈˆμ§€λ˜μ–΄ λ°œμƒν•˜λŠ” 였λ₯˜μž…λ‹ˆλ‹€.


큰 ꡬ쑰 λ³€κ²½ 없이 였λ₯˜λ₯Ό ν”Όν•˜λŠ” κ°„λ‹¨ν•œ 방법

render ν•¨μˆ˜μ—μ„œ λ°”λ‘œ 훅을 ν˜ΈμΆœν•˜μ§€ 말고, 훅을 μ‚¬μš©ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈλ₯Ό ν•¨μˆ˜ μ„ μ–Έλ¬ΈμœΌλ‘œ λ”°λ‘œ λ§Œλ“€μ–΄μ„œ renderμ—μ„œ ν˜ΈμΆœν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€.

import React, { useState } from 'react';

function ControlledTextField(args: typeof TextField extends React.ComponentType<infer P> ? P : never) {
  const [value, setValue] = useState('');
  return <TextField {...args} value={value} onChange={e => setValue(e.target.value)} />;
}

export const Default: Story = {
  render: args => <ControlledTextField {...args} />,
  args: {
    placeholder: 'Enter text...',
    size: 'md',
    radius: 'md',
    variant: 'outline',
    icon: <Image src="file.svg" alt="img" />,
    disabled: false,
  },
};

 


SearchInput.stories.tsx

export const Default: Story = {
  render: args => {
    const [value, setValue] = useState('');

    return (
      <SearchInput
        {...args}
        value={value}
        onChange={e => setValue(e.target.value)}
        onSubmit={e => {
          e.preventDefault();
          console.log('Submit:', value);
        }}
      />
    );
  },
  args: {
    placeholder: 'κ²€μƒ‰ν•˜μ„Έμš”',
    size: 'md',
  },
};

 

μˆ˜μ •

function ControlledSearchInput(
  args: typeof SearchInput extends React.ComponentType<infer P> ? P : never
) {
  const [value, setValue] = useState('');

  return (
    <SearchInput
      {...args}
      value={value}
      onChange={e => setValue(e.target.value)}
      onSubmit={e => {
        e.preventDefault();
        console.log('Submit:', value);
      }}
    />
  );
}

export const Default: Story = {
  render: args => <ControlledSearchInput {...args} />,
  args: {
    placeholder: 'κ²€μƒ‰ν•˜μ„Έμš”',
    size: 'md',
  },
};

 

 

 

 

 

 

 

 

 

λ°˜μ‘ν˜•