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.
Instead of creating a new Stateful component a much better approach is to use state in the components template.
An example:
const Template: ComponentStory<typeof MyComponent> = (args) => {
const [state, setState] = useState('');
return (
<MyComponent
prop1={args.prop1}
prop2={args.prop2}
onChange={() => setState('Change')}
/>
);
};
Here's a link to the original answer I got this approach from:
https://stackoverflow.com/a/64722559/10641401
Related
There are three components Toggle, ToggleMenu, Wrapper
The toggle should be universal, and used for different functions
The Wrapper should only change background color when toggle is on
The question is how to pass the function that responcible for changing color to togglemenu, that it executs when switch toggle
App
import React from 'react';
import { Wrapper } from './containers/wrapper';
import { Button } from './components/buttons/button';
import { ToggleMenu } from './components/settings/settings';
const App = () => {
return (
<>
<Wrapper>
<Button />
<ToggleMenu />
</Wrapper>
</>
)
}
export default App;
ToggleMenu
import styled from "styled-components";
import { Toggle } from "../buttons/toggle";
import { WrapperProps } from "../../containers/wrapper";
import { test } from "./test"
const SettingsContainer = styled.div`
margin: auto;
width: 50%;
height: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: white;
`;
const Container = styled.div`
height: 50%;
width: 50%;
display: flex;
justify-content: center;
flex-direction: column;
background-color: lime;
`;
const TogCon = styled.div`
margin: 0.5em;
`;
export const ToggleMenu = (props: WrapperProps) => {
return (
<SettingsContainer>
<Container>
<TogCon>
<Toggle toggleText={"Dark mode"} onChange={props.handleTheme}/>
</TogCon>
<TogCon>
<Toggle toggleText={"Sth"} onChange={() => {console.log("Sth")}}/>
</TogCon>
</Container>
</SettingsContainer>
);
};
Wrapper
import React, { useState } from "react";
import styled from "styled-components";
export type WrapperProps = {
children?: React.ReactNode;
color?: string;
handleTheme?: () => void;
};
const Container = styled.div<WrapperProps>`
width: 100%;
height: 100%;
top: 0;
left: 0;
position: fixed;
background-color: ${props => props.color }
`
export const Wrapper = ({ children }: WrapperProps) => {
const [theme, setTheme] = useState("black")
const handleTheme = () => {
theme === "black" ? setTheme("white"): setTheme("black")
}
return(
<Container
color={theme}
handleTheme={handleTheme}
> { children }
</Container>
);
}
Was solved with React.cloneElement
export const Wrapper = (props: WrapperProps) => {
const [theme, setTheme] = useState("black")
const handleTheme = () => {
theme === "black" ? setTheme("white"): setTheme("black")
}
const childrenWithProps = React.Children.map(props.children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { handleTheme });
}
return child;
});
return(
<Container color={theme} >
{ childrenWithProps }
</Container>
);
}
I'm trying to add infinite scroll to my page but it's only showing me the mongo data. and while running react it's showing me a warning that says:-
"src\components\Article.jsx
React Hook useEffect has a missing dependency: 'news'. Either include it or remove the dependency array. You can also do a functional update 'setNews(n => ...)' if you only need 'news' in the 'setNews' call react-hooks/exhaustive-deps"
Article.jsx
import { useEffect , useState} from "react";
import { getNews } from "../service/api";
import InfiniteScroll from 'react-infinite-scroll-component';
import Articles from "./Articles";
const Article = () => {
const [news, setNews] = useState([]);
const [page, setPage] = useState(0);
useEffect(() => {
const dailyNews = async () => {
const response = await getNews(page);
console.log(new Set([...news, ...response.data]));
setNews([...new Set([...news, ...response.data])]);
}
dailyNews();
}, [page])
useEffect(() => {
console.log(news);
}, [news])
return(
<InfiniteScroll
dataLength={news.length *5 }
pullDownToRefreshThreshold={50}
next ={ () => setPage(page => page + 1)}
hasMore={true}
loader={<h4>Loading...</h4>}>
{
news.map(article =>(
<Articles article={article} />
))
}
</InfiniteScroll >
)
}
export default Article;
and Articles.jsx
import { Card, CardContent, Typography, styled, Grid } from "#mui/material";
const Component = styled(Card)`
box-shadow: 0 2px 5px 0 rgb(0 0 0 / 16%), 0 2px 10px 0 rgb(0 0 0 / 12%);
margin-bottom: 20px;
`;
const Container = styled(CardContent) `
display: flex;
padding: 8px;
padding-bottom: 4px !important;
`;
const Image = styled('img')({
height: 268,
width: '88%',
borderRadius: 5,
objectFit: 'cover'
});
const RightContainer = styled(Grid)(({ theme }) => ({
margin: '5px 0px 0 -25px',
display: 'flex',
flexDirection: 'column',
[theme.breakpoints.between('sm', 'lg')]: {
padding: '0 5px'
},
[theme.breakpoints.down('sm')]: {
margin: '5px 0'
}
}));
const Title = styled(Typography)`
font-weight: 300;
color: #44444d;
font-size: 22px;
line-height: 27px;
`;
const Author = styled(Typography)`
color: #808290;
font-size: 12px;
line-height: 22px;
`;
const Description = styled(Typography)`
line-height: 22px;
color: #44444d;
margin-top: 5px;
font-family: 'Roboto',sans-serif;
font-weight: 300;
`;
const Short = styled('b')({
color: '#44444d',
fontFamily: "'Convergence', sans-serif"
})
const Publisher = styled(Typography)`
font-size: 12px;
margin-top: auto;
margin-bottom: 10px;
'& > *': {
textDecoration: 'none',
color: '#000',
fontWeight: 900
}
`;
const Articles = ({ article }) => {
return (
<Component>
<Container>
<Grid container>
<Grid lg={5} md={5} sm={5} xs={12} item>
<Image src={article.url} />
</Grid>
<RightContainer lg={7} md={7} sm={7} xs={12} item>
<Title>{article.title}</Title>
<Author>
<Short>short</Short> by {article.author} / {new Date(article.timestamp).toDateString()}
</Author>
<Description>{article.description}</Description>
<Publisher>
read more at {article.publisher}
</Publisher>
</RightContainer>
</Grid>
</Container>
</Component>
)
}
export default Articles;
Try to write like this
useEffect(() => {
const dailyNews = async () => {
const response = await getNews(page);
console.log(new Set([...news, ...response.data]));
setNews([...new Set([...news, ...response.data])]);
}
dailyNews();
//eslint-disable-next-line
}, [page])
Please please help me in adding infinite scroll to my page
I'm using "react-infinite-scroll-component" npm package
Use following format for all useEffect hook
useEffect(()=> {
...
//eslint-disable-next-line
},[])
You can put
// eslint-disable-next-line react-hooks/exhaustive-deps
before the violating line if there is a good reason.
Read react team member comment
Write useEffect like this
useEffect(() => {
const dailyNews = async () => {
const response = await getNews(page);
console.log(new Set([...news, ...response.data]));
setNews([...new Set([...news, ...response.data])]);
}
dailyNews();
// eslint-disable-next-line
}, [page])
or you can add missing dependencies there like this
useEffect(() => {
const dailyNews = async () => {
const response = await getNews(page);
console.log(new Set([...news, ...response.data]));
setNews([...new Set([...news, ...response.data])]);
}
dailyNews();
}, [page, news, setNews])
The problem is this:
There is a created UiInput React component using styled-components, above it there is also a UiInputExt React component, which should override some of the styles defined in UiInput, but for some reason this does not happen, apparently it does not even add the corresponding class...
I attach the code below:
const StyledInput = styled.input`
color: ${(props) => props.styles.color};
::placeholder,
::-webkit-input-placeholder {
color: ${(props) => props.styles.color};
}
:-moz-placeholder {
color: ${(props) => props.styles.color};
opacity: 1;
}
::-moz-placeholder {
color: ${(props) => props.styles.color};
opacity: 1;
}
:-ms-input-placeholder {
color: ${(props) => props.styles.color};
}
`;
<StyledInput
id={id}
className={cn(styles.input, classes)}
type={type}
placeholder={placeholder}
styles={isTheme.styles}
>
</StyledInput>
And the corresponding override (which doesn't work!)
import UiInput from '../';
const StyledUiInputExt = styled(UiInput)`
color: ${(props) => props.styles.color_uiinputext};
::placeholder,
::-webkit-input-placeholder {
color: ${(props) => props.styles.color_uiinputext};
}
:-moz-placeholder {
color: ${(props) => props.styles.color_uiinputext};
opacity: 1;
}
::-moz-placeholder {
color: ${(props) => props.styles.color_uiinputext};
opacity: 1;
}
:-ms-input-placeholder {
color: ${(props) => props.styles.color_uiinputext};
}
`;
<StyledUiInputExt classes={cn(styles.input, classes)} {...props} styles={isTheme.styles}></StyledUiInputExt>
You are not exporting the styled component StyledInput, rather you are exporting a plain React component that is rendering StyledInput.
const StyledInput = styled.input`
color: blue;
`;
const UiInput = () => {
return <StyledInput />;
};
export default UiInput;
UiInputExt
import UiInput from "./UiInput";
const StyledInputExt = styled(UiInput)`
color: green;
`;
const UiInputExt = () => {
return <StyledInputExt />;
};
The style you are applying to UiInput isn't applied to the JSX it renders, i.e. it isn't passed through to StyledInput.
You can only override other styled-components. For this you should export/import StyledInput (renamed to UiInput) directly.
const UiInput = styled.input`
color: blue;
`;
export default UiInput;
An alternative would be to export UiInput as a styled component and ensure that the className prop is passed through to the StyledInput component.
const StyledInput = styled.input`
color: blue;
`;
const UiInput = ({ className }) => {
return <StyledInput className={className} />;
};
export default styled(UiInput)``;
Please I needed to add a button before the previousLabel property details and after the nextLabel prop details in React-Paginate but what I have below is not working. I have also tried to read the documentation and check all the prop details but still couldn't achieve the result below.
I want a first button to come before one button and a last button to come after the next button.
what the code below looks like above.
what I want to achieve below
```
import React, { useState, useEffect } from 'react'
import axios from 'axios'
import ReactPaginate from 'react-paginate';
import './styles.css'
export default function App() {
const [postsPerPage] = useState(5);
const [offset, setOffset] = useState(1);
const [posts, setAllPosts] = useState([]);
const [pageCount, setPageCount] = useState(0)
const getPostData = (data) => {
return (
data.map(post => <div className="container" key={post.id}>
<p>User ID: {post.id}</p>
<p>Title: {post.title}</p>
</div>)
)
}
const getAllPosts = async () => {
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts`)
const data = res.data;
const slice = data.slice(offset - 1 , offset - 1 + postsPerPage)
// For displaying Data
const postData = getPostData(slice)
// Using Hooks to set value
setAllPosts(postData)
setPageCount(Math.ceil(data.length / postsPerPage))
}
const handlePageClick = (event) => {
const selectedPage = event.selected;
setOffset(selectedPage + 1)
};
useEffect(() => {
getAllPosts()
}, [offset])
return (
<div className="main-app">
{/* Display all the posts */}
{posts}
{/* Using React Paginate */}
<ReactPaginate
previousLabel={"previous"}
nextLabel={"next"}
breakLabel={"..."}
breakClassName={"break-me"}
pageCount={pageCount}
onPageChange={handlePageClick}
containerClassName={"pagination"}
subContainerClassName={"pages pagination"}
activeClassName={"active"} />
</div>
);
}
```
Below is the css
.main-app {
margin: 2% 10%;
border: 1px ridge #464141;
padding: 5%;
font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
font-size: 20px;
color: #464141;
}
.container {
border-bottom: #fff 2px ridge;
}
.pagination {
margin-top: 45px;
margin-left: -40px;
display: flex;
list-style: none;
outline: none;
}
.pagination>.active>a {
background-color: #47ccde;
color: #fff;
}
.pagination>li>a {
border: 1px solid #47ccde;
padding: 5px 10px;
outline: none;
cursor: pointer;
}
.pagination>li>a, .pagination>li>span {
color: #47ccde;
background-color: azure;
}
I'm creating a navbar, but I'm not able to show the Wrapper menu when clicked.
The error occurs when inserting isOpen = {isOpen} in <Sidebar />
ERROR
Type '{ isOpen: boolean; toggle: () => void; }' is not assignable to type 'IntrinsicAttribute. Property 'isOpen' does not exist on type 'IntrinsicAttributes. ts(2322)
pages/Home.tsx
import React, { useState } from 'react';
...
import Navbar from '../../components/Navbar';
import Sidebar from '../../components/Sidebar';
interface SidebarProps {
isOpen?: boolean;
}
const Home: React.FC<SidebarProps> = () => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => {
setIsOpen(!isOpen);
};
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle} /> <----------- ERRO
<Navbar />
<HomePage>
...
</HomePage>
components/Sidebar/styles.tsx
import { FaTimes } from 'react-icons/fa';
import { Link as LinkScroll } from 'react-scroll';
import styled from 'styled-components';
interface SidebarProps {
isOpen?: boolean;
}
export const SidebarContainer = styled.aside<SidebarProps>`
position: fixed;
z-index: 999;
width: 100%;
height: 100%;
background: #010311;
display: grid;
align-items: center;
top: 0;
left: 0;
transition: 0.4s ease-in-out;
opacity: ${({ isOpen }) => (isOpen ? '100%' : '0')};
top: ${({ isOpen }) => (isOpen ? '0' : '-100%')};
`;
I think in the styled component you should access the boolean isOpen in the following way:
opacity: ${props => props.isOpen ? '100%' : '0'}