How to extend styled components? - styled-components

I have a styled component:
interface FlexContainerProps {
children: any;
className?: string;
}
function FlexContainer(props: FlexContainerProps) {
const Container = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-start;
`;
return (
<Container className={props.className}>
{props.children}
</Container>
);
}
I want to be able to extend it when I use it in components.
The following does not work because the "extended" class has lower specificity (or is later in the code):
const FlexContainerExtended = styled(FlexContainer)`
flex-direction: column-reverse;
`;
The following works but is hacky:
const FlexContainerExtended = styled(FlexContainer)`
flex-direction: column-reverse !important;
`;
Is there another way to extend styled components?

You can just do like this:
const FlexContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-start;
`;
const FlexContainerExtended = styled(FlexContainer)`
flex-direction: column-reverse;
`;

type FlexContainerProps = {
className?: string,
}
const StyledFlexContainer = styled.div<FlexContainerProps>`
display: flex;
flex-direction: column;
justify-content: flex-start;
`
export const FlexContainer: React.FC<FlexContainerProps> = props => {
return (
<StyledFlexContainer className={props.className}
{props.children}
</StyledFlexContainer>
);
}
In other component u can extend your FlexContainer like this:
const FlexContainerExtended = styled.div`
flex-direction: column-reverse;
`;
<FlexContainerExtended as={FlexContainer}/>

Related

jest testing on react-hook-form

I have below react code and jest code but I am having trouble targeting the name input field using jest. I have set (data-testid="name") on the FormModal component but jest couldn't find it and returned with error ( TestingLibraryElementError: Unable to find an element by: [data-testid="name"]). I keep on debugging but couldn't find the problem.
FormModal.tsx
import { FunctionComponent } from "react";
import Modal from "react-modal";
import { useForm } from "react-hook-form";
export const InputField = styled.input`
width: 70%;
height: 25px;
border-radius: 5px;
&:focus {
border: 3px solid #89cff0;
outline: none;
}
`;
export const InputSpan = styled.div`
width: 30%;
`;
export const InputContainer = styled.div`
width: 100%;
display: flex;
margin: 15px 0px;
height: 30px;
align-items: center;
`;
return (
<form onSubmit={handleSubmit(onSubmit)}>
<InputContainer>
<InputSpan>Name:</InputSpan>
<InputField data-testid="name" {...register("name")}></InputField>
</InputContainer>
<ModalFooter>
<FormSubmit type="submit">Submit</FormSubmit>
<FormCancel onClick={props.openModal}>Cancel</FormCancel>
</ModalFooter>
</form>
);
};
Here is the jest test
FormModal.test.tsx
import { fireEvent, render, screen } from "#testing-library/react";
import FormModal from "../FormModal";
it("calls the onSubmit function without valid inputs", () => {
render(<FormModal />);
const createButton = screen.getByTestId("name");
expect(createButton).not.toBeNull();
});

Sibling component styling with styled-components

This is quite a simple case which I can't, for some reason, get to work. I have a styled-components Container, and I'd like to define the styles of p inside that container like so:
const Container = styled.div`
& p {
margin: 0;
& + & {
margin-top: 10px;
}
}
`
So, I would expect whenever there's more that on p inside the Container, that the second p would get a top-margin, but this doesn't happen.
Here's a codesandbox.
Any ideas?
I just inverted the condition.
Is this want you want?
CodeSandbox
import styled from "styled-components";
const Container = styled.div`
background: grey;
& p:nth-child(1) {
margin: 0px;
}
& p {
margin-top: 10px;
}
`;
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Container>
<p>Line One</p>
<p>Line Two</p>
<p>Line Two</p>
</Container>
</div>
);
}

React + Material-UI | How to create horizontal scroll cards?

I'm attempting to create a react component that will display 3 cards, each containing some information from an array horizontally. There would be left/right buttons allowing the user to scroll back and forth horizontally to 3 more cards until the array is completed.
I've been doing some research and have had a really difficult time finding a solution to complete this task easily. This is my first time using Material-UI so this is all quite new.
What can I do to obtain what I'm looking for? Is there some sort of scroll feature I can give to material-UI to easily create these left/right scroll buttons?
Thanks!
example:
Maybe you can try using a library like this one:
react-material-ui-carousel
Instead of putting images in this component, try putting cards instead.
Do you need the buttons, or just the horizontal scroll? This is a simple example of horizontal scroll section with images:
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import GridList from '#material-ui/core/GridList';
import GridListTile from '#material-ui/core/GridListTile';
import GridListTileBar from '#material-ui/core/GridListTileBar';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
overflow: 'hidden',
},
gridList: {
flexWrap: 'nowrap'
}
}));
const tileData = [
{
img: 'images/image1.jpg',
title: 'title'
},
{
img: 'images/image2.jpg',
title: 'title'
},
{
img: 'images/image3.jpg',
title: 'title'
}
];
export default function SingleLineGridList() {
const classes = useStyles();
return (
<div className={classes.root}>
<GridList className={classes.gridList} cols={2.5}>
{tileData.map((tile) => (
<GridListTile key={tile.img}>
<img src={tile.img} alt={tile.title} />
<GridListTileBar
title={tile.title}
/>
</GridListTile>
))}
</GridList>
</div>
);
}
I think for the buttons you would need to set a <div> for each section of 3, and set the href=#id on each button.
You can use below code to get it done. I have used some CSS properties to make it work. This will remove the additional usage of arrows to scroll horizontally.
I have used this in a Netflix clone app. This is a Row Component. I have used it in my Home page and passed different genre of movie list to it. Based on the genre it will show different movies in a row.
<div className="row">
{/* title */}
<h2>{title}</h2>
{/* container -> posters */}
<div className="row__posters">
{/* several row posters */}
{movies.map((movie) => (
<img
key={movie.id}
className="row__poster row__posterLarge"
src={`${image_base_url}${
isLargeRow ? movie.poster_path : movie.backdrop_path
}`}
alt={movie.name}
/>
))}
</div>
</div>
Below is the CSS used for above component.
.row {
margin-left: 20px;
color: white;
}
.row__posters {
display: flex;
overflow-x: scroll;
overflow-y: hidden;
padding: 20px;
}
.row__posters::-webkit-scrollbar {
display: none;
}
.row__poster {
width: 100%;
object-fit: contain;
max-height: 100px;
margin-right: 10px;
transition: transform 450ms;
}
.row__posterLarge {
max-height: 250px;
}
.row__posterLarge:hover {
transform: scale(1.09);
}
.row__poster:hover {
transform: scale(1.08);
}
I can't share the full code because it's mixed a lot with other code that is not related to this question, but here is something similar that I can share:
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Chip from "#material-ui/core/Chip";
import Box from "#material-ui/core/Box";
import Tabs from "#material-ui/core/Tabs";
import IconButton from "#material-ui/core/IconButton";
import styled from "#emotion/styled";
import ChevronLeftIcon from "#material-ui/icons/ChevronLeftRounded";
import ChevronRightIcon from "#material-ui/icons/ChevronRightRounded";
const StyledChip = styled(Chip)`
border-radius: 16px;
text-transform: capitalize;
color: ${(props) => (props.selected ? "#FFFFFF" : "#6877AE")};
background-color: ${(props) => (props.selected ? "#03194F" : "#FFFFFF")};
border: 4px solid ${"#03194F"};
border-color: ${(props) =>
props.selected ? "#03194F" : "rgba(0, 83, 229, 0.12)"};
.MuiChip-root&:hover {
background-color: ${(props) => (props.selected ? "#03194F" : "")};
}
`;
const StyledIconButton = styled(IconButton)`
left: ${(props) => (props.isLeft ? "0" : "none")};
right: ${(props) => (props.isLeft ? "none" : "0")};
height: 32px;
width: 32px;
position: absolute;
border-radius: 16px;
border: 1px solid gray;
//top: 33%;
background-color: white;
color: rgba(0, 83, 229, 1);
border-color: rgba(0, 83, 229, 0.12);
z-index: 1;
opacity: 1;
margin: 20px;
:hover {
box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2),
0px 4px 5px rgba(0, 0, 0, 0.14), 0px 1px 10px rgba(0, 0, 0, 0.12);
border-color: white;
background-color: inherit;
}
`;
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
justifyContent: "center",
flexWrap: "nowrap",
listStyle: "none",
padding: theme.spacing(0.5),
margin: 0,
overflow: "auto",
maxWidth: "100%"
},
chip: {
margin: theme.spacing(2)
}
}));
export default function ChipsArray() {
const classes = useStyles();
const [chipData, setChipData] = React.useState([
{ key: 0, label: "Angular" },
{ key: 1, label: "jQuery" },
{ key: 2, label: "Polymer" },
{ key: 3, label: "React" },
{ key: 4, label: "Vue" },
{ key: 5, label: "Knockout" },
{ key: 6, label: "Ember" },
{ key: 7, label: "D3" },
{ key: 8, label: "Google Charts" },
{ key: 9, label: "C+" },
{ key: 10, label: "C++" },
{ key: 11, label: "NodeJS" }
]);
const [selectedIndustryFilter, setSelectedIndustryFilter] = React.useState(
"Angular"
);
return (
<Box className={classes.root}>
<Tabs
variant="scrollable"
scrollButtons="on"
aria-label="scrollable auto tabs example"
ScrollButtonComponent={(props) => {
if (props.direction === "left") {
return (
<StyledIconButton isLeft {...props}>
<ChevronLeftIcon />
</StyledIconButton>
);
} else if (props.direction === "right") {
return (
<StyledIconButton {...props}>
<ChevronRightIcon />
</StyledIconButton>
);
} else {
return null;
}
}}
>
{chipData.map((data) => {
return (
<StyledChip
label={data.label}
onClick={() => {
setSelectedIndustryFilter(data.label);
console.log(data.label);
}}
selected={data.label === selectedIndustryFilter}
key={data.key}
className={classes.chip}
/>
);
})}
</Tabs>
</Box>
);
}
also check it here:
https://codesandbox.io/s/demo-material-ui-chips-single-line-with-scroll-forked-2f0z30?file=/src/App.js

React: Map does not give expected result

I am using React-typescript for my app. For styling I am using styled-components. I have created one global breadcrumbs components. When I am using the breadcrumbs components to parent components there will be couple links. after mapping the links it should display single links but I am getting combined links. This is how it looks like:
My expected result is:
This is my parent component where I am importing my Breadcrumbs
<BreadCrumb>
check
this
out
</BreadCrumb>
This is my global Breadcrumb component
import React from "react";
import styled from 'styled-components'
export interface IBreadCrumb {
direction?: "";
children: JSX.Element[];
onClick?: () => void;
}
export const BreadCrumb = ({ direction, children, onClick }: IBreadCrumb) => {
return (
<div>
<Breadcrumbs>
{
children.map(i => //In here I am doing my mapping.
<Crumb>
<a href="#" onClick={onClick}>{children}</a>
</Crumb>
)
}
</Breadcrumbs>
</div >
)
}
const Direction = {
right: "\\2192",
left: "\\2190 ",
slash: "/"
}
const Crumb = styled.li`
display: inline-block;
&:last-of-type:after {
content: "";
padding: 0;
}
a {
color: grey;
text-decoration: none;
&:hover,
&:active {
text-decoration: underline;
}
}
`
const Breadcrumbs = styled.ul`
list-style: none;
padding: 0;
& > li:after {
content: "${Direction.right}";
padding: 0 8px;
color: grey
}
`;
Should it not be e.g.:
children.map((child, index) =>
<Crumb>
<a key="{index}" href="#" onClick={onClick}>{child}</a>
</Crumb>
)

Incorrect rendering of a component when using Math.random in setState in React Component

The intention is to display an item from a list of objects but on every page refresh, the item should be randomly chosen from the list. Here, Testimonials is the list and I want to display any random item from this list. If I use a constant, it works fine. When I use, the random function, it does not display proper image with its associated item message.
I use React 16, Next.js, styled components as the tech.
The problem is in rendering of Employees section. The console displays a warning as
warning.js?6327:33 Warning: Propsrcdid not match. Server: "/static/images/testimonials/2.jpg" Client: "/static/images/testimonials/5.png"
Here is the piece of my code
import {Component} from 'react';
import Row from '../../../common-util/row';
import Col from '../../../common-util/col';
import {Container, Content, Image, StyledCol, Statement, Title, Designation, Heading, Arrow} from './styles';
const Testimonials = [{
name: 'ACX',
role: 'XYZ',
image: '/static/images/testimonials/1.jpeg',
message: 'KL DSAD E'
}, {
name: 'HJK',
role: 'Growth Hacker',
image: '/static/images/testimonials/2.jpg',
message: 'JKLASD ASDA'
}, {
name: 'ZXCV',
role: 'Product Manager',
image: '/static/images/testimonials/3.jpg',
message: 'KKK'
}, {
name: 'UIP',
role: 'Data Integrity',
image: '/static/images/testimonials/4.JPG',
message: 'LOPO'
}, {
name: 'NMa',
role: 'Sales Evangelist',
image: '/static/images/testimonials/5.png',
message: 'KK D D D'
}];
export default class Employees extends Component {
constructor(props) {
super(props);
this.state = {
currentIndex: parseInt((Math.random()*10))%5,
};
}
render() {
let {currentIndex} = this.state;
return (
<Container>
<Content>
<Title>Our employees say...</Title>
</Content>
<Row>
<Col xs={1} sm={1} md={2} lg={2}>
<Arrow className={currentIndex === 0 ? 'disabled' : ''} onClick={this.handleClick.bind(this, 'DECREMENT')}>{'<'}</Arrow>
</Col>
<Col xs={10} sm={10} md={8} lg={8}>{this.currentItem(currentIndex)}</Col>
<Col xs={1} sm={1} md={2} lg={2}>
<Arrow className={currentIndex === Testimonials.length - 1 ? 'disabled' : ''} onClick={this.handleClick.bind(this, 'INCREMENT')}>{'>'}</Arrow>
</Col>
</Row>
</Container>
);
}
currentItem(currentIndex) {
const item = Testimonials[currentIndex];
return (
<Row>
<StyledCol xs={4} md={4} lg={3}>
<Image src={item.image} alt={item.name} />
</StyledCol>
<Col xs={8} md={8} lg={9}>
<Statement>{item.message}</Statement>
<Heading className='font-Bold'>{item.name},</Heading>
<Designation className='font-DemiBold'>{item.role}</Designation>
</Col>
</Row>
);
}
handleClick(type: string) {
let {currentIndex} = this.state;
switch (type) {
case 'DECREMENT':
this.setState({
currentIndex: currentIndex - 1
});
break;
case 'INCREMENT':
this.setState({
currentIndex: currentIndex + 1
});
break;
default:
}
}
}
The corresponding style is
import styled from 'styled-components';
import Col from '../../../common-util/col';
import Grid from '../../../common-util/grid';
import H4 from '../../../common-util/headers/h4';
import H5 from '../../../common-util/headers/h5';
export const Container = styled(Grid)`
padding-bottom: 20px;
`;
export const Content = styled.div`
display: flex;
flex-flow: row wrap;
width: 100%;
justify-content: center;
margin-bottom: 20px;
`;
export const Title = styled.h1`
font-size: 42px;
line-height: 1.0;
letter-spacing: -0.3px;
text-align: justify;
font-weight: 500;
`;
export const Image = styled.img`
width: 100%;
`;
export const Statement = styled.p`
padding: 15px 20px;
background: url(/static/images/svg/top-left-bg.svg) top left no-repeat, url(/static/images/svg/bottom-right-bg.svg) bottom right no-repeat;
background-size: 20px;
line-height: 2.3;
letter-spacing: -0.2px;
font-size: 16px;
margin: 0
`;
export const Heading = H4.extend`
color: #4990e2;
text-align: left;
font-weight: bold;
line-height: 1.2;
margin-bottom: 5px;
margin-left: 20px;
`;
export const Designation = H5.extend`
text-align: left;
font-weight: 600;
line-height: 1.22;
letter-spacing: -0.2px;
margin-top: 0;
margin-left: 20px;
`;
export const Arrow = styled.div`
margin: auto;
color: grey;
font-size: 20px;
font-weight:lighter;
cursor: pointer;
&:hover {
font-size: 22px;
}
&.disabled {
pointer-events: none;
&:hover {
font-size: 20px;
}
}
`;
export const StyledCol = Col.extend`
margin: auto;
`;
That's the problem.
this.state = {
currentIndex: parseInt((Math.random()*10))%5,
};
This will be invoked on a server and in the browser causing a mismatch in rendered markup.
You could fix that by making sure random will only be called in a browser:
this.state = {
currentIndex: 0,
};
componentDidMount(){
this.setState({ currentIndex: parseInt((Math.random()*10))%5 })
}
Using React's useState and useEffect hook introduced in React 16.8.0:
const [selectedImage, setImage] = useState(<defaultImage>);
useEffect(() => {
setImage(randomImage());
}, [selectedImage]);

Resources