react I want to change the style in the props I pass - reactjs

I am using react and styled-components.
I want to apply the style defined in the dataList when I pass the page name (home,group) to the body component as a props.
I would like to apply the home style of dataList to the box when home is passed in props, and the group style of dataList to the box when group is passed.
I tried to implement it in my own way, but an error occurs at the dataList location.
code
import React from "react";
import styled from "styled-components";
type Props = {
type: "home" | "group";
};
export const Body: React.FC<Props> = ({ children, type }) => {
const dataList = {
home: {
gridTemplateAreas: 'header''body',
gridTemplateRows: 30px calc(100vh - 100px),
padding: 30
},
group: {
gridTemplateAreas: 'header''menu''body',
gridTemplateRows: 30px calc(100vh - 70px),
padding: 30
}
};
const data = dataList[type];
return <Box props={data}>{children}</Box>;
};
const Box = styled.div<{ props: any }>`
display: grid;
grid-template-areas: ${(props) => props.gridTemplateAreas};
grid-template-rows: ${(props) => props.gridTemplateRows};
padding-left: ${(props) => props.padding};
`;

You have to pass data as props in Box component. It would be like this:
const data = dataList[type];
return <Box {...data}>{children}</Box>;
Here is working demo: https://codesandbox.io/s/weathered-fire-7soy9?file=/src/Body.tsx:458-528

Related

TypeScript React, unable to send props to component

I have a simple button component, I am styling it with styled components, but I can not send a simple prop to the component as TypeScript complains with No overload matches this call
App.tsx
import React from 'react';
import Button from '../form/button';
export const App = (): React.ReactElement => <Button secondary>Click me</Button>;
Button.tsx
import React from 'react';
import styled from 'styled-components';
const StyledButton = styled.button`
background-color: #333;
padding: 10px 20px;
color: ${({ secondary }) => (secondary ? 'white' : 'red')};
`;
interface Props {
children: string;
secondary?: any;
}
const Button: React.FC<Props> = ({ children, secondary }) => (
<StyledButton secondary={secondary}>{children}</StyledButton>
);
export default Button;
This should work, what does TypeScript needs me to specify? The error is not very helpful
It's because you haven't passed the props to the styled component, so it does not know what secondary is.
You can pass your props like so (and I removed children since you don't need to specify this manually)
import React from "react";
import styled from "styled-components";
interface Props {
secondary?: any;
}
const StyledButton = styled.button<Props>`
background-color: #333;
padding: 10px 20px;
color: ${({ secondary }) => (secondary ? "white" : "red")};
`;
const Button: React.FC<Props> = ({ children, secondary }) => (
<StyledButton secondary={secondary}>{children}</StyledButton>
);
export default Button;
CodeSandbox: https://codesandbox.io/s/determined-worker-62xtm?file=/src/form/Button.tsx
You can just specify that the props passed and then destructured are from the interface Props and access the parameter secondary.
const StyledButton = styled.button`
background-color: #333;
padding: 10px 20px;
color: ${({secondary}: Props) => (secondary ? 'white' : 'red')}
`;

Typescript merging styled component props with the parent ones

I have a styled component that look like this:
interface BoxProps {
color?: string;
backgroundColor?: string;
borderColor?: string;
}
export const Box = styled.div<BoxProps>`
position: relative;
padding: 0.75rem 1.25rem;
margin-bottom: 1rem;
border: 1px solid transparent;
border-radius: 0.25rem;
text-align: left;
color: ${(props) => props.color};
background-color: ${(props) => props.backgroundColor};
border-color: ${(props) => props.borderColor};
`;
It's wrapped in another component:
export const Container: React.FC<ContainerProps> = ({
variant,
children,
...props
}) => {
const { color, backgroundColor, borderColor } = variantColor(variant);
return (
<div>
<Box
color={color}
backgroundColor={backgroundColor}
borderColor={borderColor}
{...props}
>
{children}
</Box>
<p></p>
// ...
</div>
);
};
If I add StyledComponent<"div", any, BoxProps, never> to React.FC<ContainerProps>:
React.FC<ContainerProps & StyledComponent<"div", any, BoxProps, never>>
spreading props will give me the following error:
Rest types may only be created from object types
I tried React.HTMLAttributes<{}> & typeof Box.propTypes and React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> without luck...
Is there a way to merge a styled-component props with the parent props?
Thanks!
styled.div just renders a div and it accepts any properties div would accept + your properties from BoxProps. You can get a type that described properties of a JSX element using React.HTMLAttributes<HTMLElementYouNeed> in this case React.HTMLAttributes<HTMLDivElement>, so props of Box would now be this plus BoxProps:
React.FC<ContainerProps & React.HTMLAttributes<HTMLDivElement> & BoxProps>
If you are too lazy to write this, you could make a utility namespace in some file in your project:
declare namespace HTMLProps {
type div = React.HTMLAttributes<HTMLDivProps>
// Maybe define props for other html elements if you need
}
export default HTMLProps
Then import this and use
React.FC<ContainerProps & HTMLProps.div & BoxProps>
Alex's answer is valid but in some cases it won't work. For instance I had this styled component:
export const CustomInput = styled.input.attrs({ type: 'text' })``;
using:
React.HTMLAttributes<HTMLInputElement>
// or
Omit<React.HTMLAttributes<HTMLInputElement>,'type'>
gives an error in the parent component.
for this to work you need:
Omit<React.ComponentPropsWithoutRef<'input'>, 'type'>

What is the best way to typing React Components when they styling with styled-components

I am migrating a project to Typescript. I have a component styled with styled-components and I need to override it with styled-components on some other component. My component looks like this.
This component aims to render an image and if image not exist, returns a Fallback element styled with styled-components.
import React, { useState } from 'react'
import styled from 'styled-components'
export const Fallback = styled.div`
padding-bottom: 56%; /* 16/9 ratio */
background-color: #e5e5e5;
`
export const Image = styled.img`
image-rendering: pixelated;
image-rendering: crisp-edges;
`
interface ImageWithFallbackProps {
src: string
alt: string
fallback: typeof Fallback
}
const ImageWithFallback = ({
src,
alt,
fallback = Fallback,
...props
}: ImageWithFallbackProps): React.ReactNode => {
const [error, setError] = useState(false)
const FallbackElement: React.FC = fallback
if (error) return <FallbackElement />
return <Image src={src} alt={alt} {...props} onError={() => setError(true)} />
}
export default ImageWithFallback
And, I use it on another component.
const Content: AnyStyledComponent = styled.div`
/* styles */
`
Content.Text = styled.div`
font-weight: bold;
flex: 1;
text-align: left;
`
Content.Image = styled(ImageWithFallback)`
flex: 1;
max-width: 80px;
align-self: baseline;
`
Content.Fallback = styled(Fallback)`
width: 80px;
padding-bottom: 54px;
`
But I get an error where I define Content.Image like this Content.Image = styled(ImageWithFallback). Error code is ts(2769). How can I solve this issue?

Receiving prop in Material UI and conditional rendering

This is a styled-component code block, it gets an img prop and based on the prop, it decides to load a specific image.
export const DialogBanner = styled.div`
min-height: 250px;
margin-bottom: 20px;
${({ img }) =>
img
? `background-image: url(${img});`
: `background-image: url("/img/banner.jpg");`}
background-position: center;
background-size: cover;
filter: contrast(85%);
`;
<DialogBanner img={food.img} />
How can I write the same thing using Material UI's useStyles?
EDIT: This is now legacy and was written for version 4
Using the latest version of Material-UI you would use makeStyles which creates a hook for use within your component. https://material-ui.com/styles/basics/#adapting-based-on-props
For Example.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
// Create our styles using passed props
// These are `functors` it seems
const useStyles = makeStyles({
root: {
backgroundImage: ({ img }) =>
img
? `url(${img});`
: `url("/img/banner.jpg");`}
},
});
function DialogBanner(props) {
// Separating out our children
const { children, ...rest } = props
// Prop containing our `img`
const classes = useStyles(rest);
retun <div className={classes.root>{children}</div>
}

Creating a simple animation in React-Pose

I'm having trouble creating a simple animation in React-Pose. The two problems are
1) I can't get the animation to revert to the initial condition. The hovering variable is changing to false when the mouse leaves, but it the animation doesn't change back.
2) I can't manipulate the animation, I wanted to have a longer duration and maybe an ease out or something, but its just an instant snap to the hovered status.
import React, { useState } from 'react';
import styled from 'styled-components';
import posed from 'react-pose';
import { render } from 'react-dom';
const UpFor = () => {
const [hovering, setHovering] = useState(false);
const HoverContainer = posed.div({
hoverable: true
})
const Container = styled(HoverContainer)`
font-family: 'Baumans';
font-size: 220px;
display: flex;
cursor: pointer;
`
const Up = styled.div`
color: #81D6E3;`
const Four = styled.div`
color: #FF101F
`
const Fours = styled.div`
display: flex;
`
const MirroredFour = posed.div({
unhovered: {transform: 'rotatey(0deg)'},
hovered: {transform: 'rotateY(180deg)',
transition: {
type: 'tween',
duration: '2s'
}}
})
const SecondFour = styled(MirroredFour)`
color: #FF101F
position: absolute;
transform-origin: 67%;
`
return (
<Container onMouseEnter={() => {setHovering({ hovering: true }), console.log(hovering)}}
onMouseLeave={() => {setHovering({ hovering: false }), console.log(hovering)}}>
<Up>Up</Up><Fours><Four>4</Four>
<SecondFour pose={hovering ? "hovered" : "unhovered"}
>4</SecondFour></Fours>
</Container>)
}
export default UpFor
There were two main issues with your code:
duration does not appear to support string values like '2s'. I changed this to 2000.
You were defining your components (e.g. using styled.div, posed.div) inside of your render function. This caused these components to be treated by React as unique component types with each re-render. This results in those components being unmounted and re-mounted each render which prevents transitions from working since the element isn't changing -- instead it is being replaced by a new component of a different type.
Below is a working version of your code which moves the component definitions outside of the render (UpFor) function. You can play around with it in the sandbox provided.
import React, { useState } from "react";
import styled from "styled-components";
import posed from "react-pose";
const Container = styled.div`
font-family: "Baumans";
font-size: 220px;
display: flex;
cursor: pointer;
`;
const Up = styled.div`
color: #81d6e3;
`;
const Four = styled.div`
color: #ff101f;
`;
const Fours = styled.div`
display: flex;
`;
const MirroredFour = posed.div({
unhovered: { transform: "rotateY(0deg)" },
hovered: {
transform: "rotateY(180deg)",
transition: {
type: "tween",
duration: 2000
}
}
});
const SecondFour = styled(MirroredFour)`
color: #FF101F
position: absolute;
transform-origin: 67%;
`;
const UpFor = () => {
const [hovering, setHovering] = useState(false);
console.log("hovering", hovering);
return (
<Container
onMouseEnter={() => {
setHovering(true);
}}
onMouseLeave={() => {
setHovering(false);
}}
>
<Up>Up</Up>
<Fours>
<Four>4</Four>
<SecondFour pose={hovering ? "hovered" : "unhovered"}>4</SecondFour>
</Fours>
</Container>
);
};
export default UpFor;

Resources