I am creating a checkbox component using React, Typescript, Storybook and Styled-Components, after following this tutorial: building-a-checkbox-component-with-react-and-styled-components. I have had to adjust the code as I am using a FunctionComponent, but I am facing an issue with the change handler. I cannot check or uncheck the Checkbox which seems to be readonly, and I'm not sure where I am going wrong. Here is my code:
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>;
});
As I am a novice at both React and Storybook I may require some expert input into whether my change handler code is correct or not. I have looked at other examples, but none of them use Typescript. Any help would be appreciated.
Solution is to change onChange to onClick in CheckboxProps in Checkbox.tsx, as follows:
onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
and to simplify CheckboxStateful in Checkbox.stories.js as follows:
const CheckboxStateful = () => {
const [value, setValue] = useState(false);
return <Checkbox checked={value} onClick={() => setValue(!value)}></Checkbox>;
};
The complete updated code is now as follows:
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 />;
});
Once you create the React project and add Storybook, just run yarn storybook
and the checkboxes will show up correctly.