I have this Styled component, where I'm trying to pass DATA-attribute which is coming from props to it. (This is the solution we have on Stack Overflow)
export const InlineEditReadViewErrorContainer = styled.div.attrs(props => ({
'data-cy': props.dataCy
}))`
border: 2px solid #de350b;
border-radius: 3px;
`;
This is how I use this styled component in code
<InlineEditReadViewErrorContainer dataCy='blabla'>
{readView}
</InlineEditReadViewErrorContainer>
But this is doesn't change anything
I think that we must use correctly the attributes that are added to a component in React and more if they are needed only to style a component.
We should use as many native properties as possible but without compromising the private data that would be exposed in the HTML that the client displays in broser, therefore:
If you are going to use data-attributes:
Remember how to name these attributes:
The attribute name should not contain any uppercase letters, and must be at least one character long after the prefix "data-"
Note: I would just use the simplest possible, with booleans to give a set of properties as the first answer described, for example:
component.js
<Error data-active={true}>
{readView}
</Error>
component.styles.js
export const Error = styled.div`
&[data-active="true"] {
border: 2px solid #de350b;
border-radius: 3px;
}
`;
If you want to use custom props without them being displayed in the DOM as the second comment has described, using transient props:
For sample:
component.js
<Error $active={true}>
{readView}
</Error>
component.styles.js
export const Error = styled.div`
${({$active}) => $active ? css`
border: 2px solid #de350b;
border-radius: 3px;
`: null}
`;
The prop should already be "passed" in the sense that it will show up on the component for the purposes of using it in Cypress. If you want to use it internally you could also use transient props such as this
const Comp = styled.div`
color: ${props =>
props.$draggable || 'black'};
`;
render(
<Comp $draggable="red" draggable="true">
Drag me!
</Comp>
);
It was much easier. You can pass the data attribute directly where you use the styled component and everything will be fine.
<InlineEditReadViewErrorContainer data-cy='dateInput'>
{textValue}
</InlineEditReadViewErrorContainer>
Maybe it's related to your bundler, since you should be able to pass a data attribute to a styled-component directly. However, if you're extending an existing component, be aware that you need to pass it through props. Those two situations will attach the data attribute to the final HTML:
function Text(props) {
return (
<p data-foo={props['data-foo']} className={props.className}>
{props.children}
</p>
);
}
const StyledText = styled(Text)`
color: blueviolet;
`;
const StyledHeading = styled.h1`
color: gray;
`;
export default function App() {
return (
<div>
<StyledHeading data-bar="foo">Hello StackBlitz!</StyledHeading>
<StyledText data-foo="bar">
Start editing to see some magic happen :)
</StyledText>
</div>
);
}
Related
I am trying to run onClick on a React component that is created from a styled-component, but it does not work without a work-around.
Style 1:
const GenericStyledDeleteButton = styled.button`
height: 30px;
font-size: 20px;
text-align: center;
color: green;
border: none;
border-radius: 5px;
&:hover {
color: red;
}
`;
Component:
const DeleteButton = ({className}) => (
<GenericStyledDeleteButton className={className}
Delete
</GenericStyledDeleteButton>
);
Style 3:
const StyledDeleteButton = styled(DeleteButton)`
margin-left: auto;
margin-right: 25px;
`;
I’ve created a generic styled component, then created a React native component with some text, and then added some further styling onto the generic styled component.
I am trying to perform the following:
<StyledDeleteButton onClick={() => DeleteItem(item._id)} />
I have tried both onClick and onClick but they don’t work. However, it works when I add the following:
const DeleteButton = ({ className, onClick }) => (
<GenericStyledDeleteButton className={className} onClick={onClick}>
Delete
</GenericStyledDeleteButton>
);
The onClick work on other styled-components I have, but not this one.
So my question is, how do I call onClick directly on StyledDeleteButton without changing the DeleteButton component?
To use onClick directly in the styled-component without implementing a work-around.
const DeleteButton = ({ className, onClick }) => (
<GenericStyledDeleteButton className={className} onClick={onClick}>
Delete
</GenericStyledDeleteButton>
);
It is not a work-around. This is how it should work. If the DeleteButton does not accept onClick, you can not pass it. Props are passed from parent to children.
StyledDeleteButton passes onClick to DeleteButton. If the DeleteButton does not have onClick, it can not pass it to its children.
This is the natural way. If you do not want to change DeleteButton, you can make a work-around with context provider, but you have to change how GenericStyledDeleteButton receives onClick. It is not that good as adding an onClick prop to it.
I'm not sure why i'm getting this error, i'm just trying to make a button that inverts colors on hover, if you have a solution or a better way to do this please let me know
import React, {useState} from 'react'
function Login() {
const [bttnColor, setBttnColor] = useState('white')
const [textColor, setTextColor] = useState('black')
function colorChange1(){
setBttnColor('black')
setTextColor('white')
}
function colorChange2(){
setBttnColor('white')
setTextColor('black')
}
return (
<div className="Login" style={{display:'flex', flexDirection:'column', justifyContent:'center', alignItems:'center'}}>
<h1 style={{display:'flex', marginTop:'400px', marginBottom:'40px'}}>Login</h1>
<input style={{height:'30px', width:'140px', marginBottom:'10px'}} placeholder='Username'/>
<input style={{height:'30px', width:'140px'}} type='password'placeholder='Password'/>
<button style={{height:'30px', width:'140px', marginTop:'10px', background:bttnColor, color:textColor}} onMouseEnter={colorChange1()} onMouseLeave={colorChange2()}>Login</button>:
</div>
)
}
export default Login
When declaring a property, the result of what's inside of the {} is sent to the Component.
This will send the result of colorChange1() to the component, not the function itself
onMouseEnter={colorChange1()}
This is unwanted behavior in your use case, but remember that this is a property just like any other, like style or className.
You need to pass it a function reference instead of the result of the function. You can do that in two different ways:
onMouseEnter={colorChange1}
onMouseEnter={(event) => colorChange1(event, otherVariables...)}
The first way is a function reference to the existing function. Use this when you don't need to pass any other information to the function
The second way is to wrap the function call with a lambda. This will allow you to take variables from your current scope and pass them into the method when it's run.
EDIT:
On second thought, doing this at all is making it far more complicated than it needs to be. This can be done in a few lines of CSS.
Let's remove those color change methods and the onMouseEnter and onMouseLeave calls, and give the button a className so we can refer to it in CSS.
<button className="login-button">Login</button>:
Then let's create the following css file, named Login.css in the same folder as Login.js:
.login-button {
height: 30px;
width: 140px;
marginTop: 10px;
background:white;
color:black;
}
.login-button:hover {
background: black;
color: white;
}
Finally, let's import the css file:
import "./Login.css";
I've been hitting my head against a wall with this one and I can't quite grasp what's the issue here.
I am pulling some data from API. It's a simple object that contains username, score and isOwner which is basically information if the username equals currently logged user. Now, if isOwner is true i want to style that li position differently.
So lets say that's how my map looks like:
const List = myData.map((el, i) => (
<li key={el.name} isowner={el.isOwner ? 1 : 0}>
{i + 1}. {el.name}
<span>{el.score}</span>
</li>
));
So every li element is generated in a styled ul component.
Now, looking at the styled component list, it looks like:
export const ScoreList = styled.ul`
width: 80%;
display: flex;
flex-direction: column;
li {
border: ${props =>
props.isowner === 1 ? '2px solid yellow' : '1px solid black'};
border-radius: 5px;
}`
for some reason it ignores the value of isowner and displays everything with the black border.
Now looking at chrome dev tools I've noticed something odd.
<li isowner="1">...</li>
<li isowner="0">...</li>
<li isowner="0">...</li>
I can see the 'prop' being put there like this, which I can't notice in any other case when I'm passing props. It looks like the logic is working well but it just doesn't see the element as a prop.
Also, I am using isowner instead of isOwner because otherwise I'm getting a following warning:
React does not recognize the `isOwner` prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom attribute,
spell it as lowercase `isowner` instead.
If you accidentally passed it from a parent component, remove it from the DOM element.
I've tried different methods, instead of isOwner I was comparing el.name with username that I could yoink from redux, results were the same, I got the '1' or 'true' right where I needed it but I just couldnt pass it further.
I would be very very thankful if any of you has an idea about how to deal with this.
this might help you:
export const ScoreLi = styled.li`
border: ${props =>
props.isowner === 1 ? '2px solid yellow' : '1px solid black'};
border-radius: 5px;`
const List = myData.map((el, i) => (
<ScoreLi key={el.name} isowner={el.isOwner ? 1 : 0}>
{i + 1}. {el.name}
<span>{el.score}</span>
</ScoreLi>
));
I am calling a component "MyRadioButton" with following props:
<MyRadioButton
label="Radio Group"
theme="custom-red" //this line
error="Field is required "
radioBtns={options}
id="radioBtns"
name="radioBtns"
getValue={this.getValue}
/>
I have created a react component "MyRadioButton" that will accept color name(theme) as props.
export const MyRadioButton = props => {
const {theme} = props;
return (
<div className="my-radio-buttons"> // need to use theme here
<input
onChange={onChange}
type="radio"
/>
</div>
)}
Based on this prop i want to assign the variable in my components scss file, which will take the color code from my custom defined color pallet.
my-radio-button.scss
/* custom color pallet */
$custom-orange: #F060D6;
$custom-red: #BB532E;
$custom-blue: #4C9FEB;
.my-radio-buttons {
.input{
border: 2px solid $custom-red; // i want to assign the color variable based on input prop value to this property
}
}
I have already tried setting variable at css root with javascript and accessing it with variable function var(), it works fine.
But because of some limitations i dont want to use that approach.
also because the color pallet list is huge, i dont want to use separate classes for all of them.
I am looking for some other solution or different approach.
So you can use a combination of custom css variables and your passed theme property. In you css, you would define the basecolor of the border for example:
.my-radio-buttons {
--theme-color: red;
input {
border: 2px solid var(--theme-color);
}
}
This can be updated by your components via componentDidMount or useEffect with the passed theme:
const MyRadioButton = props => {
const { theme } = props;
React.useEffect(() => {
const input = document.querySelector(".my-radio-buttons input");
input.style.setProperty("--theme-color", props.theme);
}, []);
return (
<div className="my-radio-buttons">
<input />
</div>
);
};
Depending on your code style, you can replace the querySelector with a ref.
I want to make a dynamic component. (the dynamic TAG will be a styled component -> emotion)
const Dynamic = ({ tag: Tag, children, ...rest }) =>
<Tag {...rest}>
{ children }
</Tag>
The component will be a styled component like:
const Column = styled(div)({ color: 'red' })
const Row = styled(span)({ color: 'yellow' })
This looks all nice, and working properly, BUUUUUT:
When I try use a DynamicComponent inside another DynamicComponent:
<DynamicComponent tag={Row}>
{
mapOver.map(item=>
<DynamicComponent tag={Column}/>
)
}
</DynamicComponent>
then for some reason the Dynamic children will use the Dynamic Parent's style.
Is there anything I missing?
P.S.:
If instead of using dynamic styles, I do something like this:
<Row>
<Column/>
</Row>
then the styles, classNames, styled tags, are applied properly.
To make it a little more clear:
As you can see the DynamicComponent's will use the parent's styles, classNames, styled tags... (NOT THE BEHAVIOUR I WOULD EXPECT)
Below example creating a dynamic tag name for a styled-component:
// All headings use the same styled-component "Heading" function
const StyledHeading = styled.div`
font-size: ${({level}) => 4/level }em; // <- dynamic font size
font-weight: 300;
margin: 0;
`
// the trick here is the "as={...}" to create dynamic tag name
const Heading = ({level = 1, children}) =>
<StyledHeading as={`h${level}`} level={level}>
{children}
</StyledHeading>
ReactDOM.render([
<Heading>Hello, world!</Heading>,
<Heading level={2}>Title 2</Heading>,
<Heading level={3}>Title 3</Heading>
] ,document.body
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/styled-components#4.4.1/dist/styled-components.min.js"></script>
Reference:
"as" prop documentation
There is a misunderstanding in the usage of styled-components as a tag is intended as HTML tag (input, div and so on). The best way is to define a StyledRow and a StyledColumn separately and use them with appropriate names. This will help also to make your code more readable.