reactjs - מתקן שינוי התיבה של React Storybook לא עובד


אני יוצר קומפוננטת צ'קבוקס באמצעות React, Typescript, Storybook ו-Styled-Components, אחרי שעקבתי אחרי המדריך הזה: building-a-checkbox-component-with-react-and-styled-components. נאלצתי להתאים את הקוד כי אני משתמש ב-FunctionComponent, אבל אני נתקל בבעיה עם מנהל השינויים (change handler). אני לא יכול לסמן או להסיר סימון מהצ'קבוקס שמירא שהוא לקריאה בלבד, ואני לא בטוח איפה אני טועה. הנה הקוד שלי:

Checkbox.tsx

import React from 'react';
import styled from 'styled-components';
import { FunctionComponent } from 'react';

type CheckboxProps = {
    checked?: boolean;
    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
};

const CheckboxContainer = styled.div`
    display: inline-block;
    vertical-align: middle;
`;

const Icon = styled.svg`
    fill: none;
    stroke: white;
    stroke-width: 2px;
`;

const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
    border: 0;
    clip: rect(0 0 0 0);
    clippath: inset(50%);
    height: 1px;
    margin: -1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
    white-space: nowrap;
    width: 1px;
`;

const StyledCheckbox = styled.div`
    display: inline-block;
    width: 16px;
    height: 16px;
    background: ${(props: CheckboxProps) => (props.checked ? 'green' : 'white')};
    border-color: 'dark gray';
    border-width: 1px;
    border-style: solid;
    border-radius: 2px;

    ${HiddenCheckbox}:focus + & {
        box-shadow: 0 0 0 3px grey;
    }

    ${Icon} {
        visibility: ${(props: CheckboxProps) => (props.checked ? 'visible' : 'hidden')};
    }
`;

const Checkbox: FunctionComponent<CheckboxProps> = ({ onChange, checked, ...props }) => {
    return (
        <CheckboxContainer>
            <HiddenCheckbox checked={checked} {...props} onChange={onChange} />
            <StyledCheckbox data-testid="styledcheckbox" checked={checked} {...props} onChange={onChange}>
                <Icon viewBox="0 0 24 24">
                    <polyline points="20 6 9 17 4 12" />
                </Icon>
            </StyledCheckbox>
        </CheckboxContainer>
    );
};

export default Checkbox;

Checkbox.stories.js

// Checkbox.stories.js
import React, { useState, useRef } from 'react';
import Checkbox from '@components/Checkbox/Checkbox';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const CheckboxStateful = props => {
    const [value, setValue] = useState(props);
    const valRef = useRef(value);
    // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
    const onChange = event => {
        setValue(event.target.value);
        valRef.current = event.target.value;
    };

    return (
        <Checkbox
            value={value}
            onChange={event => {
                onChange(event);
            }}
        ></Checkbox>
    );
};
storiesOf('Checkbox', module)
    .add('with checked', () => {
        const value = true;
        // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
        const onChange = event => setValue(event.target.value);
        return <CheckboxStateful value={value} onChange={onChange}></CheckboxStateful>;
    })
    .add('with unchecked', () => {
        const value = false;
        // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
        const onChange = event => setValue(event.target.value);
        return <CheckboxStateful value={value} onChange={onChange}></CheckboxStateful>;
    });

כיוון שאני חדש גם ב-React וגם ב-Storybook, ייתכן שאני יזדקק לקצת עזרה מקצועית כדי לבדוק אם קוד מנהל השינויים שלי נכון או לא. הסתכלתי על דוגמאות אחרות, אבל אף אחד מהן לא משתמש ב-Typescript. כל עזרה תוערך.

תשובה #1

הפתרון הוא לשנות את onChange ל-onClick ב-CheckboxProps בקובץ Checkbox.tsx, באופן הבא:

onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;

ולפשט את CheckboxStateful ב-Checkbox.stories.js כך:

const CheckboxStateful = () => {
const [value, setValue] = useState(false);
return <Checkbox checked={value} onClick={() => setValue(!value)}></Checkbox>;

};

הקוד המעודכן המלא הוא כעת כפי שמוצג להלן:

Checkbox.tsx

import * as React from 'react';
import styled from 'styled-components';
import { FunctionComponent } from 'react';

type CheckboxProps = {
    checked?: boolean;
    onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
};

const CheckboxContainer = styled.div`
    display: inline-block;
    vertical-align: middle;
`;

const Icon = styled.svg`
    fill: none;
    stroke: white;
    stroke-width: 2px;
`;

const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
    border: 0;
    clip: rect(0 0 0 0);
    clippath: inset(50%);
    height: 1px;
    margin: -1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
    white-space: nowrap;
    width: 1px;
`;

const StyledCheckbox = styled.div`
    display: inline-block;
    width: 16px;
    height: 16px;
    background: ${(props: CheckboxProps) => (props.checked ? 'green' : 'white')};
    border-color: 'dark gray';
    border-width: 1px;
    border-style: solid;
    border-radius: 2px;

    ${HiddenCheckbox}:focus + & {
        box-shadow: 0 0 0 3px grey;
    }

    ${Icon} {
        visibility: ${(props: CheckboxProps) => (props.checked ? 'visible' : 'hidden')};
    }
`;

const Checkbox: FunctionComponent<CheckboxProps> = ({ onClick, checked, ...props }) => {
    return (
        <CheckboxContainer>
            <HiddenCheckbox checked={checked} {...props} />
            <StyledCheckbox checked={checked} {...props} onClick={onClick} data-testid="styledcheckbox">
                <Icon viewBox="0 0 24 24">
                    <polyline points="20 6 9 17 4 12" />
                </Icon>
            </StyledCheckbox>
        </CheckboxContainer>
    );
};

export default Checkbox;

Checkbox.stories.js

/* eslint-disable @typescript-eslint/explicit-function-return-type */
// Checkbox.stories.js
import * as React from 'react';
import { useState } from 'react';
import Checkbox from '@components/Checkbox/Checkbox';
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';

export default {
    title: 'Checkbox',
    component: Checkbox,
};

const CheckboxStateful = () => {
    const [value, setValue] = useState(false);
    return <Checkbox checked={value} onClick={() => setValue(!value)}></Checkbox>;
};

storiesOf('Checkbox', module)
    .add('with checked', () => {
        const value = true;
        return <Checkbox checked={value} onClick={action('checked')}></Checkbox>;
    })
    .add('with unchecked', () => {
        const value = false;
        return <Checkbox checked={value} onClick={action('checked')}></Checkbox>;
    })
    .add('stateful', () => {
        return <CheckboxStateful />;
    });

לאחר שיצרת את פרויקט ה-React והוספת את Storybook, פשוט הרץ yarn storybook ותיבות הסימון יופיעו כראוי.

תשובה #2

במקום ליצור קומפוננטת Stateful חדשה, גישה הרבה יותר טובה היא להשתמש ב-state בתבנית של הקומפוננטה.

דוגמה:

const Template: ComponentStory<typeof MyComponent> = (args) => {
  const [state, setState] = useState('');

  return (
    <MyComponent
      prop1={args.prop1}
      prop2={args.prop2}
      onChange={() => setState('Change')}
    />
  );
};

הנה קישור לתשובה המקורית שקיבלתי ממנה את הגישה הזו: https://stackoverflow.com/a/64722559/10641401