How to create dynamic styled component? - reactjs

What I'm trying to achieve is something like this:
import styled from 'react-emotion'
const StyledComponent = styled(({tag}) => tag)`
// some css styles
`
And use it like:
<StyledComponent tag=div"/>
<StyledComponent tag="p"/>
<StyledComponent tag="ul"/>
// etc
My expectation was that it should generate HTML as follows:
<div class="some-class"></div>
<p class="some-class"></p>
<ul class="some-class"></ul>
Actual output:
div
p
ul
My question is, can this be achieved or am I missing anything?

Seems like I have figured out a solution to my problem. Sharing my answer for those who may run into the same problem.
I changed StyledComponent declaration to following:
import styled from 'react-emotion'
const StyledComponent = styled(({tag, children, ...props}) => React.createElement(tag, props, children))`
// some css styles
`
This is working as expected.
If anyone has the better answers please do post. Thank you

You are using react-emotion in wrong way, please try this.
const StyledComponent = ({ tag, children, ...props }) => {
const Container = styled(tag)`
background-colo: red;
`;
return <Container {...props}>{children}</Container>;
};
Demo: https://codesandbox.io/s/lr4xxp3757

What worked form me was using the "as" polymorphic prop (https://styled-components.com/docs/api#as-polymorphic-prop):
const StyledComponent = styled.div``
type ComponentProps = {
tag?: AnyStyledComponent
children: React.ReactNode
}
const Component = ({ tag, children }: ComponentProps) => (
<StyledComponent as={tag}>{children}</StyledComponent>
)
Declaring the styled component outside of the function component will spare us from console warnings such as this one: https://github.com/styled-components/styled-components/issues/3117

Related

Styled-components add styles to custom component

I want to add styles to the custom component with a help of the styled-components library. I found some similar questions on StackOverflow with answers but it seems not to work for me. I do something like this:
Here is my custom component
const ComponentStyledBox = styled.div`
color: red;
`;
const ComponentStyled = () => (
<ComponentStyledBox>Component With StyledComponents</ComponentStyledBox>
);
And here I try to use my custom component and add some styles to it
const ComponentStyledWrapper = styled(ComponentStyled)`
background-color: green;
`;
export default function App() {
return (
<div>
<ComponentStyledWrapper />
</div>
);
}
As the result, I don't have a green background. Only red text. The same will be if I use instead of the styled custom component just a component with styles added via CSS in a common way. What do I do wrong?
For your convenience here is a CodeSandbox
Thanks in advance!
The problem is caused by this:
const ComponentStyled = () => (
<ComponentStyledBox>Component With StyledComponents</ComponentStyledBox>
);
You've defined a new component called ComponentStyled which is not style-able. Try this instead (in App.styles.tsx):
import { ComponentStyledBox } from "./components/ComponentStyled/ComponentStyled.styles";
export const ComponentStyledWrapper = styled(ComponentStyledBox)`
background-color: green;
`;
ComponentStyledBox is style-able, so you'll get the green background.
Edit (response to #zakharov.arthur's comment): If really do want a custom component with hard-coded children (are you sure this is what you want?), you can make your custom component style-able by exposing ComponentStyledBox's props:
// I haven't actually tried this but it should work.
const ComponentStyled = (props: Omit<Parameters<ComponentStyledBox>[0], 'children'>) =>
<ComponentStyledBox {...props}>Component With StyledComponents</ComponentStyledBox>

Unable to pass react props.children

I am using react.
When I pass props.children to the Header component, I get the following error.
I think the type is correct because I am passing React.
I don't know why the error occurs.
error
react-dom.development.js?61bb:13231 Uncaught Error: Objects are not valid as a React child (found: object with keys {children}). If you meant to render a collection of children, use an array instead.
import React from 'react';
import styled from 'styled-components';
export const Header: React.FC = (children) => {
return <Head>{children}</Head>;
};
const Head = styled.div`
background: #8b8b8b;
`;
const Home = () => {
return (
<div>
<Header>
<span>home</span>
</Header>
<div>
);
};
export default Home;
You just named your props as children, try:
export const Header: React.FC = ({children}) => {
return <Head>{children}</Head>;
};
Let's just take a step back from destructuring for a second because it confuses a lot of JS beginners.
By default, your component receives an argument which is a bunch of properties, so we usually call it props. We can get particular properties from this props object like so:
export const Header = (props) => {
console.log(props)
return <Head>{props.children}</Head>;
};
Then once you get your head around destructuring, you'll notice we can do this in a faster, cleaner manner to get properties from our props object.
export const Header = ({ children }) => {
console.log(children)
return <Head>{children}</Head>;
};
The trade-off is we can't do props.something now, we'll need to add something to the restructure... but our code looks cleaner.

React, styled-components and TypeScript: How to wrap a styled component in a functional component

I'm trying to create a wrapper for my styled component but I'm having trouble getting the types right.
Let's say I have a styled component like this:
const Button = styled.button<ButtonProps>`
background-color: ${(props) => props.color};
`;
Now I want to create a wrapper component that contains this styled button, eg. like this:
const WrappedButton: React.FunctionComponent<ButtonProps> = ({ children, ...rest }) => (
<div>
<Button {...rest}>{children}</Button>
</div>
);
Now this all works fine, but what I actually want is the WrappedButton component to accept all props that the Button component would accept and pass them along to the wrapped Button component.
So for example I want this to compile, as type is a valid prop of a HTML button element (and therefore also a valid prop of the Button component, but not when the Button component is wrapped):
// TypeScript will throw an error because "type" is not a valid prop of WrappedButton.
const MyComponent = () => <WrappedButton type="submit">Test</WrappedButton>
I know I can make "type" a prop of the WrappedComponent, but that's not the point, I want the WrappedComponent to accept all props that a normal HTML button would accept.
EDIT: Also I need all styled-component specific props on the wrapped component, such as the as prop of styled-components. Here's an updated version of the code sandbox: https://codesandbox.io/s/react-typescript-styled-components-forked-3o20j?file=/src/index.tsx
I have tried so many things but TypeScript is always complaining. I have also searched the docs and the internet but have not found anything.
I believe You are asking about React.ButtonHTMLAttributes<HTMLButtonElement>.
import React from 'react'
import styled from 'styled-components'
const Button = styled.button<ButtonProps>`
background-color: ${(props) => props.color};
`;
type ButtonProps = {
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
const WrappedButton: React.FunctionComponent<ButtonProps> = ({ children, ...rest }) => (
<div>
<Button {...rest}>{children}</Button>
</div>
);
If you want to use raw html <button>, you mignt be interested in: JSX.IntrinsicElements["button"];
Check this answer
UPDATE
The quick and dirty solution would be :
type ButtonProps = {
} & React.ButtonHTMLAttributes<HTMLButtonElement> & { as?: string | React.ComponentType<any> };
But it is not generic.
There is StyledComponentPropsWithAs type in SC typings, but it is not exported (
If you're trying to suppress native attributes you could do something like this:
interface Props {
type?: 'error' | 'success'
}
type OmitNativeAttrs = Omit<React.HTMLAttributes<HTMLButtonElement>, keyof Props>
export type ButtonProps = Props & OmitNativeAttrs

No overload matches this call when usign styled-components

There are several questions about this error on SO but none from anybody in a similar situation and none of the solutions I have found work so I'm going to post a specific question.
When using a Styled component inside another component and passing props to it, do I have to create a new type to pass the props through to the styled component or is there some way of using the existing styled component types?
Here is my code. It seems to be the as="" attribute which is giving the error above. I assume this is something to do with the fact that my component only takes the prop so it can pass it to the Styled Component as it is a feator of Styled Components.
import React from 'react'
import styled from 'styled-components'
type Props = {
additionalClass?: string,
as?: string
}
const Component: React.FC<Props> = (props) => {
return (
<StyledComponent as={props.as} className={props.additionalClass}>
{props.children}
</StyledComponent>
)
}
export default Component
const StyledComponent = styled.div`
...styles go here
`
You just need to validate the prop when you are creating the styled.div
Example
const Menu= styled.div<{ menuOpen: boolean }>`
${({ menuOpen }) => {
if (menuOpen) {
return 'background-color: white';
}
return 'background-color: black';
}}
`;
export const Component: React.FC = () => {
const [menuOpen, setMenuOpen] = useState(false);
return(
<Menu menuOpen={menuOpen}>
whatever content
</Menu>
)
}
The problem was that I was trying to pass the "as" prop to styled components and I thought it needed to be a string so I did this...
type Props = {
className?: string,
as?: string
}
The problem is that once you pass it to styled-components it expects it to be the name of an element. I found the answer on Github and it can be one of two different things. This works...
type Props = {
className?: string,
as?: React.ElementType | keyof JSX.IntrinsicElements
}
type Props = {
additionalClass?: string,
[key: string]: any
}

styled-components strongly typed [theme] property

I am using styled-components TypeScript and ThemeProvider for my components, and I had a couple of questions:
First of all, my components are created using map, so I used to assign a key to each one, now I have put ThemeProvider to be the top parent component, and hence I need to set the key on that. I was just wondering does it hurt to do this? Or should I find a way to create a single ThemeProvider?
Since I'm using TypeScript, it would be very nice if I could somehow make my props.theme property be strongly typed. Right now when I hover over props.theme, I see that the type is any. It would VERY nice if I could somehow define the type for the theme property while not changing the inferred type for props
The problem I have right now is that when I define a custom interface for the props I'm using in a styled component, I loose the default properties inferred by the component. For example, if I want to have something like this:
interface ComponentProps {
status: string;
}
Then, I create a component like this:
const MyComp = styled.div`
background-color: ${(props: ComponentProps) => props.theme...};
`
Then, TypeScript will complain that theme doesn't exist on ComponentProps, but if I don't define the type for the props, then when I want to create my custom component:
<MyComp status="hello" />
Now, the TypeScript is complaining that property status doesn't apply to MyComp
I would appreciate any help
You can create a container, and pass any property you like. Like this:
in styledWithProps.ts file:
import { ThemedStyledFunction } from 'styled-components';
export const styledWithProps = <U>() =>
<P, T, O>(
fn: ThemedStyledFunction<P, T, O>
): ThemedStyledFunction<P & U, T, O & U> => fn;
when using it:
import styled, { css } from "styled-components";
import { styledWithProps } from "./styledWithProps.ts";
const MyComp = styledWithProps<any>()(styled.div) `
${props => props.theme==="dark" && css`
background-color: black;
`}
`;
const app = props => <MyComp theme="dark">{props.children}</MyComp>
For more details, take a look this thread: https://github.com/styled-components/styled-components/issues/630

Resources