How to pass prop in styledcomponent to stylerule? - reactjs

I am using styledcomponents in my reactjs app. In one of the components I have a mediarule:
#media (max-width: 1450px)
{
padding-right: ${props => (props.meta.count > 0 ? '6px' : '0')};
}
This meta.count prop does exist but when I run the app I get:
TypeError: Cannot read property 'count' of undefined
The render starts like this:
const { lastUpdated, meta } = this.props;
The meta prop gets loaded through a redux selector:
const mapStateToProps = state => ({
meta: getAlertsMeta(state),
});
How can I avoid this / fix this?

Related

Styled Components - content passed through props (content)

I am trying to toggle text inside a div (styled component) depending on the props value. The idea is simple: state changes, its value goes to props of the styled component, and then depending what it is, the content adjusts. What is not working is the content in before / after. Any idea why?
STYLED COMPONENT:
export const Info = styled.div`
padding: 5px;
margin-top: 15px;
&::after {
content: ${props => props.content === "intro" && "hello"};
}
`
JS
const CheckNumber = () => {
const [msg, setMsg] = useState("intro")
return (
<Info content={msg}/>
)
}
export default CheckNumber
Your current code generates style
content: hello
that is invalid CSS. You need extra quotes(one pair for JS level and nested quotes for CSS):
&::after {
content: ${props => props.content === "intro" && "'hello'"};
}
PS found that really accidentally by duplicating content like:
&::after {
content: "aaaa";
content: ${props => props.content === "intro" && "hello"};
}
because browser just did not create "::after" prseudoelement while content is invalid - so we were unable to check what styles were actually generated.

Images Rerendering inside Styled Component when Chrome Dev Tools is open

This is a bit of a strange one and not sure why it's happening exactly.
When the component mounts, I call a function that in my application makes an HTTP request to get an array of Objects. Then I update 3 states within a map method.
enquiries - Which is just the response from the HTTP request
activeProperty - Which defines which object id is current active
channelDetails - parses some of the response data to be used as a prop to pass down to a child component.
const [enquiries, setEnquiries] = useState({ loading: true });
const [activeProperty, setActiveProperty] = useState();
const [channelDetails, setChannelDetails] = useState([]);
const getChannels = async () => {
// In my actual project,this is an http request and I filter responses
const response = await Enquiries;
const channelDetailsCopy = [...channelDetails];
setEnquiries(
response.map((e, i) => {
const { property } = e;
if (property) {
const { id } = property;
let tempActiveProperty;
if (i === 0 && !activeProperty) {
tempActiveProperty = id;
setActiveProperty(tempActiveProperty);
}
}
channelDetailsCopy.push(getChannelDetails(e));
return e;
})
);
setChannelDetails(channelDetailsCopy);
};
useEffect(() => {
getChannels();
}, []);
Then I return a child component ChannelList that uses styled components to add styles to the element and renders child elements.
const ChannelList = ({ children, listHeight }) => {
const ChannelListDiv = styled.div`
height: ${listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
return <ChannelListDiv className={"ChannelList"}>{children}</ChannelListDiv>;
};
Inside ChannelList component I map over the enquiries state and render the ChannelListItem component which has an assigned key on the index of the object within the array, and accepts the channelDetails state and an onClick handler.
return (
<>
{enquiries &&
enquiries.length > 0 &&
!enquiries.loading &&
channelDetails.length > 0 ? (
<ChannelList listHeight={"380px"}>
{enquiries.map((enquiry, i) => {
return (
<ChannelListItem
key={i}
details={channelDetails[i]}
activeProperty={activeProperty}
setActiveProperty={id => setActiveProperty(id)}
/>
);
})}
</ChannelList>
) : (
"loading..."
)}
</>
);
In the ChannelListItem component I render two images from the details prop based on the channelDetails state
const ChannelListItem = ({ details, setActiveProperty, activeProperty }) => {
const handleClick = () => {
setActiveProperty(details.propId);
};
return (
<div onClick={() => handleClick()} className={`ChannelListItem`}>
<div className={"ChannelListItemAvatarHeads"}>
<div
className={
"ChannelListItemAvatarHeads-prop ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.propertyImage})`
}}
/>
<div
className={
"ChannelListItemAvatarHeads-agent ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.receiverLogo})`
}}
/>
</div>
{activeProperty === details.propId ? <div>active</div> : null}
</div>
);
};
Now, the issue comes whenever the chrome dev tools window is open and you click on the different ChannelListItems the images blink/rerender. I had thought that the diff algorithm would have kicked in here and not rerendered the images as they are the same images?
But it seems that styled-components adds a new class every time you click on a ChannelListItem, so it rerenders the image. But ONLY when the develop tools window is open?
Why is this? Is there a way around this?
I can use inline styles instead of styled-components and it works as expected, though I wanted to see if there was a way around this without removing styled-components
I have a CODESANDBOX to check for yourselves
If you re-activate cache in devtool on network tab the issue disappear.
So the question becomes why the browser refetch the image when cache is disabled ;)
It is simply because the dom change so browser re-render it as you mentioned it the class change.
So the class change because the componetn change.
You create a new component at every render.
A simple fix:
import React from "react";
import styled from "styled-components";
const ChannelListDiv = styled.div`
height: ${props => props.listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
const ChannelList = ({ children, listHeight }) => {
return <ChannelListDiv listHeight={listHeight} className={"ChannelList"}>{children}</ChannelListDiv>;
};
export default ChannelList;
I think it has to do with this setting to disable cache (see red marking in image)
Hope this helps.

Creating styled-component with React.createElement

I would like to create a styled-component with a function, taking element as an argument, which I create with React.createElement call.
const StyledComponent = (element) => styled(element)`
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
`;
const myComponent = (props) => {
const element = React.createElement('div', null, `Hello World!`);
return (
StyledComponent(element)
);
}
This results in following error:
Uncaught Error: Cannot create styled-component for component: [object Object].
I am probably missing something, but am unable to figure it out.
You can't create a HOC of a rendered React node. The following line is a render call:
const element = React.createElement('div', null, `Hello World!`);
// this is the same as:
const element = <div >Hello World</div>
What you may want is to replace the createElement with an actual functional component:
const element = () => React.createElement('div', null, `Hello World!`);
// this is the same as:
const element = () => <div >Hello World</div>
Now you can create a styled component and render it. Full (relevant) code:
const element = () => React.createElement('div', null, `Hello World!`);
const SComponent = StyledComponent(element);
return React.createElement(SComponent);
Felix answer was almost right, but you still need to pass the className property in the inline component function in order to get the styles defined in your styled component applied.
I was able to find the right answer in this thread, I decided to use the JSX-syntax, If you need to pass refs, use React.cloneElement instead.
Complete example:
const StyleMyElement = (Element) => styled(({ className }) => <Element.type {...Element.props} className={className} />)`
position: absolute;
top: 0;
...
`;
...
const element = () => React.createElement('div', null, `Hello World!`);
const StyledComponent = StyleMyElement(element);
return (
<StyledComponent />
)
As of styled-components v4 you can just use the as prop to do this easily.
From the docs:
If you want to keep all the styling you've applied to a component but just switch out what's being ultimately rendered (be it a different HTML tag or a different custom component), you can use the "as" prop to do this at runtime.
import styled from "styled-components";
const Component = styled.div`
color: red;
`;
render(
<Component
as="button"
onClick={() => alert('It works!')}
>
Hello World!
</Component>
)
Here's how I implemented it:
The component:
export default function StyledCustomComponent({tag, className, children}) {
const customTag = React.createElement(tag);
const StyledCustomElement = styled(customTag.type)`
"some css rules"
`
return (
<StyledCustomElement {...className}>
{children}
</StyledCustomElement>
)
}
Then calling the component in another file:
<StyledCustomElement tag='div'> Some content <StyledCustomElement/>
Hope it helped!
Let make things simple
import styled from 'styled-component'//import library for making styled components
If for intance would like to create a styled component based on a <p></p> you would do lile this:
const MyP=styled.p
color:red;
font-size:10px;
font-weight:bold
;
You can ads as much css defintion as you want.
Now to use it:
const MyComponent=()=>{
<MyP>
Hello
</MyP>
}
Here instead of 'MyP' you could use 'p'(not styled).
It will also work for the const MyComponent=React.creatElement(MyP,null,'Hello');
Hope it helped

Warning: Received `false` for a non-boolean attribute. How do I pass a boolean for a custom boolean attribute?

Warning: Received `false` for a non-boolean attribute `comingsoon`.
If you want to write it to the DOM, pass a string instead:
comingsoon="false" or comingsoon={value.toString()}.
How do I pass a boolean in a custom attribute for React?
I'm using styled-components and passing the attribute through the component. Here is a picture of how I'm passing the attr.
passing boolean custom attr as "comingsoon"
styled-components css props
Try this instead:
comingsoon={value ? 1 : 0}
As of 5.1 you can now use transient props ($) which prevents the props being passed to the DOM element.
const Comp = styled.div`
color: ${props =>
props.$draggable || 'black'};
`;
render(
<Comp $draggable="red" draggable="true">
Drag me!
</Comp>
);
You have to add $ prefix to attribute:
$comingsoon={value}
Styled Components had added transient props in 5.1 version:
https://styled-components.com/docs/api#transient-props
In my case, it was because I was accidentally passing {...#props} down into a div.
Usually passing attribute={false} is fine, but not to native elements.
Similar to Frank Lins answer above but I had to use undefined instead of 0 to get rid of the warning:
comingsoon={value ? 1 : undefined}
Just make it a number instead, this is the workaround from https://github.com/styled-components/styled-components/issues/1198:
This error with styled-components appears to be due to styled() attempting to apply a boolean to an element in the DOM, but DOM elements only accept strings as attributes.
This is well documented in the styled-components repository here: https://github.com/styled-components/styled-components/issues/1198
There are two solutions:
Lift the styled component w/ the passed attribute up, so that the attribute is not applied to the element directly. Or,
Filter the passed attribute out of the props when calling styled components.
Both of these options are demonstrated in the code below.
CodeSandbox: https://codesandbox.io/s/cool-thunder-9w132?file=/src/App.tsx
import React, { useState } from "react";
import styled from 'styled-components';
// demonstration of two different solutions for solving the styled-components error:
// `Warning: Received `false` for a non-boolean attribute`
// Solution 1: Lift the attribute up into a wrapper.
// Solution 2: Filter-out the `toggle` attribute passed to styled-component.
interface BtnProps {
toggle: boolean;
}
const Container = styled.div`
width: 100%;
height: 500px;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
`;
const StyledBtnOne = styled.div<BtnProps>`
& button {
background-color: ${({toggle}) => toggle ? ' #2ecc71' : '' };
};
`;
const StyledBtnTwo = styled(({primary, ...props}) =>
<button {...(({toggle, ...propz}) => propz)(props)} />)<BtnProps>`
background-color: ${({toggle}) => toggle ? ' #3498db' : '' };
`;
const App = () => {
const [ btnOne, setBtnOne ] = useState(false);
const [ btnTwo, setBtnTwo ] = useState(false);
const triggerOne = () => setBtnOne(!btnOne);
const triggerTwo = () => setBtnTwo(!btnTwo);
return (
<Container>
<StyledBtnOne toggle={btnOne}>
<button
onClick={triggerOne}>
Solution 1
</button>
</StyledBtnOne>
<StyledBtnTwo
toggle={btnTwo}
onClick={triggerTwo}>
Solution 2
</StyledBtnTwo>
</Container>
);
}
export default App;
This warning can be caused also if the property of styled component has the name existing in HTML. For example I had such issue when named property wrap. After renaming warning disappeared.
Add "+" before your booleans values.
comingsoon = {+creator.comingSoon}
example below from the Link to answer
import styled from "styled-components";
import { Link } from "react-router";
const StyledLink = styled(Link)`
color: ${({ inverted }) => (inverted ? "white" : "chartreuse")};
`;
function Navbar() {
return (
<nav>
{# Bad #}
<StyledLink inverted={true}>Home</StyledLink>
{# Good #}
<StyledLink inverted={+true}>About</StyledLink>
</nav>
);
}
Solved this by enclosing with brackets {} the boolean variable I was passing through props.
const childrenWithProps = React.Children.map(props.children, child => {
return React.cloneElement(child, { showcard: { showCard } }
)});
I got this issue and also shows React Hydration Error in my Next.js application. In my case it seems Styled component got a custom component and it can't process 'boolean'.
Here is my workaround:
before:
styled(Text)<{ center?: boolean}>
// Text is my custom component
after:
styled.div<{ center?: boolean}>

Add srcSet to styled-component themed image

I have multiple themes for my website. I'm attempting to add a srcSet to an image like this:
export const Header = styled.img.attrs({
src: props => props.theme.images.Header1x,
srcSet: `${props => props.theme.images.Header1x} 1x, ${props => props.theme.images.Header2x} 2x, ${props => props.theme.images.Header3x} 3x`,
})`
height: 42px
`;
The src attribute is proper, but srcSet is not:
<img src="/images/header.jpg" srcset="function (props) {
return props.theme.images.Header1x;
} 1x, function (props) {
return props.theme.images.Header2x;
} 2x, function (props) {
return props.theme.images.Header3x;
} 3x" class="sc-VJcYb epAqoN">
styled-components theming feature is wonderful, and I'd like to keep all my theming organized with it, but I can't think of a way to properly build a srcSet with it. Anyone have any strategies, or do I need to go in another direction entirely?
The value of your srcSet property is a template string but should be a function that returns a string.
export const Header = styled.img.attrs({
src: props => props.theme.images.Header1x,
srcSet: props => `${props.theme.images.Header1x} 1x, ${props.theme.images.Header2x} 2x, ${props.theme.images.Header3x} 3x`,
})`
height: 42px
`;

Resources