How do i use styled components with dynamic imports - reactjs

I am trying to find a way to dynamically import an svg Icon that inherits some standard styling. I havn't see anyone else writing about this... so i think i am on the wrong track.
I have tried creating an SVG wrapper in styled components but that gives me a nest of svg > svg > path
I'm not sure if this is the right idea.
I want to stay away from using inline styles in case of specificity issues later.
The SVG Icon code <-- using Create React App i am exporting the svg as a default react component
export { ReactComponent as default } from './logo.svg';
My Icon code <-- the styled.svg is something i would like to merge with the DynamicIcon... is there a way to do something like DynamicIcon as TestStyle? <-- in the documentation the as is used to change the tag type so i don't think this is right?
const TestStyle = styled.svg`
height: 1rem;
width: 1rem;
display: inline-block;
`
const Icon: React.FC<IconProps> = ({ name }: IconProps): React.ReactElement => {
const DynamicIcon = lazy((): Promise<any> => import(`../../assets/icons/${name}.tsx`));
return (
<Suspense fallback={<div>Loading ...</div>}>
<DynamicIcon />
</Suspense>
);
};
The import is working and displaying but i would like to have the component as a styled component, this will give me access to themeing and dynamically changing the SVG style with props.

ℹ - My TypeScript foo is weak so will be using vanilla JS below
You can simply wrap the dynamically loaded component with styled(DynamicIcon).
Reference: Extending Styles documentation
// Instead of 👇
// const TestStyle = styled.svg`
// height: ${props => props.size};
// width: ${props => props.size};
// display: inline-block;
// `;
const Icon = ({ name, iconSize }) => {
const DynamicIcon = lazy(() => import(`./icons/${name}.jsx`));
// ... 👇 Wrap the dynamically loaded component in `styled()`.
const StyledIcon = styled(DynamicIcon)`
height: ${props => props.size}px;
width: ${props => props.size}px;
display: inline-block;
`;
return (
<Suspense fallback={<div>Loading ...</div>}>
<StyledIcon size={iconSize} />
</Suspense>
);
};
⚠ 🔥
But be aware that the above usage of using prop.size is not a good idea as it creates a multiple classes per each width/height.
(I was trying to get around it with .attrs but couldn't get it working but I find it outside the scope of this question and leave that to you 😉)
Check out the forked demo here.
And here is how the logo looks wrapped in styled component

Related

MUI styled a Custom React Component from external lib does not work

I currently have a React component that is from a module (it is in node_modules folder):
type Props = {
someProps: any
};
export function ComponentA(props: Props) {
const [value, setValue] = React.useState("");
return (
<TextField
name="search-bar"
value={value}
label="Search"
/>
);
}
Then in my project, I want to styled in using styled from mui material and using theme:
import {ComponentA} from '..';
export const StyledComponent = styled(ComponentA)(({ theme }) => ({
width: 'auto',
height: '5%',
margin: '10px 15px 20px',
}));
Then finally use it like this:
<StyledComponent props={someProps: 1}/>
This however does not seem to work. Other styled components that were created in my project alone is working correctly. I wonder if I did it wrongly or missing something.
I was able to fix it with className props passed in component, as suggested in the comments.

styled components - overwrite default headings

How can I overwrite default headings globally with styled components theme?
For colors I'm doing:
import React from "react";
import { ThemeProvider } from "styled-components";
const theme = {
colors: {
main: "#8563FF",
mainLight: "#AB93FF",
.....
}
};
const Theme = ({ children }) => (
<ThemeProvider theme={theme}>{children}</ThemeProvider>
);
export default Theme;
and them in my component I'm getting those colors trough props
${props => props.theme.colors.main}
I would like to overwrite default headings eg. h1 with my custom fontSize, weight..., so that when I'm using it in my component it looks like this:
const StyledH1 = styled.h1`
/* additional local styling for overwritten h1 */
color: ${props => props.theme.colors.main};
marginTop: 20px;
`;
On top of that, it should contains media query inside overwritten heading.
How to add overwrites to theme?
You should use createGlobalStyle and ensure that its ThemeProvider's child.
A helper function to generate a special StyledComponent that handles global styles.
const GlobalStyle = createGlobalStyle`
h1 {
color: ${props => props.theme.colors.main};
margin-top: 20px;
}
`
// will have margin-top
styled.h1``
<ThemeProvider>
<GlobalStyles />
</ThemeProvider>
You can create reset styles with createGlobalStyle and put it in the root of your project.
For example here are reset styles for all HTML attributes, you can import it in the root index file, and in the project, all default styles of HTML tags will be reset. (styles like margins of <p> or <h1> and so on)
You can do some modifies to set default values of some attributes, for example like the default color of <p>, <h1> or others
Examples of reset.css
https://gist.github.com/DavidWells/18e73022e723037a50d6
https://cssreset.com/scripts/eric-meyer-reset-css/

how to change image src using props with styled component and react

I want to reuse my component for multiple pages.
so, I have to change img src in styled.img component as passing props.
is it possible to change image src on styled component ?
I applied my code like below.
it's not working..
//logic
import Myimg from './img/icon_my';
const OngoingLists = ({ ...props }) => {
return (
<ListsOutWrap>
<ProgramListWrap {...props}>
<IconWrap {...props} >
<MyIcon {...props} />
</IconWrap>
</<ProgramListWrap>
</ListsOutWrap>
);
}
// styled component
const MyIcon = styled.img.attrs({
src: props => props.Img || { Myimg },
})`
width: 100%;
`;
I tried like above, any image doesn't show...even default img file doesn't show..
how can I change src file by using props ??
should I use background-image ?
thanks for ur help in advance ! :)
Styled components are still just react components, and you can simply provide a default prop value in the case a src prop isn't passed when used.
import Myimg from './img/icon_my';
const MyIcon = styled.img`
width: 100%;
`;
MyIcon.defaultProps = {
src: Myimg,
};
Usage:
<MyIcon /> // uses default Myimg
<MyIcon src="/path/to/sourceImage" /> // uses "/path/to/sourceImage"
BTW Correct attrs format to accept props would be to define a function taking props and returning an object:
const MyIcon = styled.img.attrs(props => ({
src: props.Img || Myimg,
}))`
width: 100px;
`;

How to add Sass variable to Styled components in react js

I need to add sass variable to styled components in react js ,I have go through this link Sass-extract-js and i have followed their procedure.
but i'm stuck in this line
import styled from 'styled-components';
const Button = styled.button`
background-color: ${p => p.theme.primary} //explain this line
`;
I have created var.scss file and contains
$black:#000000;
$white:#ffffff;
$red:#f64406;
$gray:#333333;
$green:#3bbd47;
In my render
<div>
<div>
<h2>Welcome Back!</h2>
<h3>Login Your Account</h3>
</div>
Styled component
const Content = styled.div`
h1:${}//how i can get variable here
`;
This can be achieved by using theming in the styled-components. You can add variables to ThemeProvider as follows:
const theme = {
black:#000000,
white:#ffffff
red:#f64406
gray:#333333,
green:#3bbd47
}
<ThemeProvider theme={theme} >
<App />
</ThemeProvider>
and then this object can be used in the styled-components
const BlackDiv = styled`
color: ${(props) => props.theme.black}`;
For more info please check theming

How to extend styled component without passing props to underlying DOM element?

I have a styled component that is extending a third-party component:
import Resizable from 're-resizable';
...
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
flex: 0 0 ${(props) => props.someProp}px;
`;
const PaneContainer = ({ children, someProp }) => (
<StyledPaneContainer
someProp={someProp}
>
{children}
</StyledPaneContainer>
);
export default PaneContainer;
This resulted in the following error in the browser console:
Warning: React does not recognize the someProp prop on a DOM
element. If you intentionally want it to appear in the DOM as a custom
attribute, spell it as lowercase someProp instead. If you
accidentally passed it from a parent component, remove it from the DOM
element
So, I changed the prop to have a data-* attribute name:
import Resizable from 're-resizable';
...
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
flex: 0 0 ${(props) => props['data-s']}px;
`;
const PaneContainer = ({ children, someProp }) => (
<StyledPaneContainer
data-s={someProp}
>
{children}
</StyledPaneContainer>
);
export default PaneContainer;
This works, but I was wondering if there was a native way to use props in the styled component without them being passed down to the DOM element (?)
2020 UPDATE: Use transient props
With the release 5.1.0 you can use transient props. This way you do not need an extra wrapper i.e. unnecessary code is reduced:
Transient props are a new pattern to pass props that are explicitly consumed only by styled components and are not meant to be passed down to deeper component layers. Here's how you use them:
const Comp = styled.div`
color: ${props => props.$fg || 'black'};
`;
render(<Comp $fg="red">I'm red!</Comp>);
Note the dollar sign ($) prefix on the prop; this marks it as transient and styled-components knows not to add it to the rendered DOM element or pass it further down the component hierarchy.
The new answer should be:
styled component:
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
flex: 0 0 ${(props) => props.$someProp}px;
`;
parent component:
const PaneContainer = ({ children, someProp }) => (
<StyledPaneContainer
$someProp={someProp} // '$' signals the transient prop
>
{children}
</StyledPaneContainer>
);
As suggested by vdanchenkov on this styled-components github issue you can destructure the props and only pass the relevant ones to Resizable
const ResizableSC = styled(({ someProp, ...rest }) => <Resizable {...rest} />)`
flex: 0 0 ${(props) => props.someProp}px;
`;
For those (like me) that landed here because of an issue with SC and react-router's Link.
This is basically the same answer as #tskjetne, but with JS syntax style.
const StyledLink = styled(({ isCurrent, ...rest }) => <Link {...rest} />)(
({ isCurrent }) => ({
borderBottomColor: isCurrent ? 'green' : 'transparent',
}),
)
Starting with version 5.1, you can use shouldForwardProp configuration property:
This is a more dynamic, granular filtering mechanism than transient props. It's handy in situations where multiple higher-order components are being composed together and happen to share the same prop name. shouldForwardProp works much like the predicate callback of Array.filter. A prop that fails the test isn't passed down to underlying components, just like a transient prop.
Keep in mind that, as in this example, other chainable methods should always be executed after .withConfig.
I find it much cleaner than transient props, because you don't have to rename a property, and you become explicit about your intentions:
const ResizableSC = styled(Resizable).withConfig({
// Filter out the props to not be shown in DOM.
shouldForwardProp: (prop, defaultValidatorFn) =>
prop !== 'someProp'
&& defaultValidatorFn(prop),
})`
flex: 0 0 ${(props) => props.someProp}px;
`;
This is especially handy if you are using TypeScript and sharing the same props type both in your main component and the corresponding styled component:
import { HTMLAttributes } from 'react';
import styled from 'styled-components';
// Props type.
type CaptionProps = HTMLAttributes<HTMLParagraphElement> & {
size: number,
};
// Main component.
export const CaptionStyles = styled('p').withConfig<CaptionProps>({
// Filter out the props to not be shown in DOM.
shouldForwardProp: (prop, defaultValidatorFn) => (
prop !== 'size'
&& defaultValidatorFn(prop)
),
})`
flex: 0 0 ${(props) => props.size}px;
`;
// Corresponding styled component.
export function Caption({ size }: CaptionProps) {
return (
<CaptionStyles size={size} />
);
}
shouldForwardProp API reference
You can try with defaultProps:
import Resizable from 're-resizable';
import PropTypes from 'prop-types';
...
const ResizableSC = styled(Resizable)``;
export const StyledPaneContainer = ResizableSC.extend`
flex: 0 0 ${(props) => props.someProp}px;
`;
StyledPaneContainer.defaultProps = { someProp: 1 }
const PaneContainer = ({ children }) => (
<StyledPaneContainer>
{children}
</StyledPaneContainer>
);
export default PaneContainer;
We can also pass props using 'attrs'. This helps in attaching additional props (Example taken from styled components official doc):
const Input = styled.input.attrs({
// we can define static props
type: 'password',
// or we can define dynamic ones
margin: props => props.size || '1em',
padding: props => props.size || '1em'
})`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
/* here we use the dynamically computed props */
margin: ${props => props.margin};
padding: ${props => props.padding};
`;
render(
<div>
<Input placeholder="A small text input" size="1em" />
<br />
<Input placeholder="A bigger text input" size="2em" />
</div>
);

Resources