Unable to extend react component in styled-component - javascript

I am trying to extent react component in styled-component and trying to add custom style on extended component but unable to see the style changes that I am applying
I have created a button component in /src/newbutton.js with following code
import styled from "styled-components";
const Button = styled.button`
background: ${props => props.primary ? "palevioletred" : "white"};
color: ${props => props.primary ? "white" : "palevioletred"};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
export const NewButton = ({ className, children }) => {
return (
<Button primary>Primary</Button>
)
}
And extending and creating another button component with custom style in /src/custom-button.js with following code
import styled from "styled-components";
import { NewButton } from './button'
const ButtonWrapper = styled(NewButton)`
width: 100%;
color: red
`;
const ExtendedButton = ({ className, children }) => {
return (
<ButtonWrapper />
)
}
I have added the custom style like width: 100% & color: red but it is not applying on ExtendedButton. Infect colour and width is same as NewButton

You need to pass a className to your NewButton in order to customize it, using styled-components.
Styled components works by creating a unique className that associated with a component and its CSS.
export const NewButton = ({ className, children }) => {
return (
<Button className={className} primary>Primary</Button>
)
}

I am posting the complete working code for future reference based on #Flat Globe solution. And it is working fine as expected.
I have modified the Button component code just by adding className in /src/newbutton.js with following code
import styled from "styled-components";
const Button = styled.button`
background: ${props => props.primary ? "palevioletred" : "white"};
color: ${props => props.primary ? "white" : "palevioletred"};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
export const NewButton = ({ className, children }) => {
return (
<Button primary className={className}>Primary</Button>
)
}
I have also modified the extended-button code by just passing the className in /src/custom-button.js. check the full code below
import styled from "styled-components";
import { NewButton } from './button'
const ButtonWrapper = styled(NewButton)`
width: 100%;
color: red
`;
const ExtendedButton = ({ className, children }) => {
return (
<ButtonWrapper className="extended-button"/>
)
}

Related

How can I implement conditional rendering using map function?

I made 5 blocks and want to make the letters on each block thick when the mouse is hover. I made isHover state and changed the thickness of the writing according to the state, but the problem is that the thickness of all five changes. I think I can solve it by using conditional rendering, but I don't know how to use it. Of course, it can be implemented only with css, but I want to implement it with conditional rendering because I am practicing the code concisely.
import "./styles.css";
import styled from "styled-components";
import { useState } from "react";
export default function App() {
const array = [
{ id: "1", title: "ABC" },
{ id: "2", title: "DEF" },
{ id: "3", title: "GHI" },
{ id: "4", title: "JKL" },
{ id: "5", title: "MNO" }
];
const [isHover, setIsHover] = useState(false);
return (
<Head isHover={isHover}>
<div className="header">
{array.map((content, id) => {
return (
<div
className="header__title"
onMouseEnter={() => {
setIsHover(true);
}}
onMouseLeave={() => {
setIsHover(false);
}}
>
{content.title}
</div>
);
})}
</div>
</Head>
);
}
const Head = styled.div`
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
.header {
display: inline-flex;
border: 1px solid black;
box-sizing: border-box;
}
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: ${(props) => (props.isHover ? "700" : "400")};
}
`;
codesandbox
https://codesandbox.io/s/aged-cherry-53pr2r?file=/src/App.js:0-1170
The problem is that you are using the same state for all the 5 blocks. There are multiple approaches you could take to solve this problem.
1. Multiple states
You could create 5 different isHover<N> states (maybe a single one, but as an array)
2. Component extraction
You could just extract out a component for each entry in array and do state management in that component.
function App() {
const array = [...];
return (
<Head>
<div className="header">
{array.map((content, id) => (
<HeaderTitle key={content.id} content={content} />
)}
</div>
</Head>
);
}
function HeaderTitle({ content }) {
const [isHover, setIsHover] = useState(false);
return (
<StyledHeaderTitle
isHover={isHover}
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
>
{content.title}
</StyledHeaderTitle>
);
}
const StyledHeaderTitle = styled.div`
font-weight: ${(props) => (props.isHover ? "700" : "400")};
`
3. Using style prop
Directly apply the font weight using the style prop (An extension to approach 2)
function HeaderTitle({ content }) {
const [isHover, setIsHover] = useState(false);
return (
<StyledHeaderTitle
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
style={{ fontWeight: isHover ? "700" : "400" }}
>
{content.title}
</StyledHeaderTitle>
);
}
4. CSS
CSS already allows you to track hover states over different elements and you don't need to manually track it in javascript.
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: 400;
&:hover {
font-weight: 700;
}
}
There's no need to use React state and event listeners here, you can do it all in CSS instead:
.header__title {
border: 1px solid red;
padding: 5px 10px;
font-weight: 400;
}
.header__title:hover {
font-weight: 700;
}
Just add this pseudo class and you're good to go
.header__title:hover {
font-weight: 700;
}

Will I get the latest state value in this case?

I wanted to know whether my onAdd function in <NewItemButton> will get the latest value of text state.
import { useState } from "react";
import {
NewItemFormContainer,
NewItemInput,
NewItemButton
} from "./styles";
type NewItemFormProps = {
onAdd(text: string): void
}
const NewItemForm = (props: NewItemFormProps) => {
const [text, setText] = useState("");
return (
<NewItemFormContainer>
<NewItemInput
value={text}
onChange={(e) => setText(e.target.value)}
/>
<NewItemButton onClick={() => props.onAdd(text)}>
Create
</NewItemButton>
</NewItemFormContainer>
);
}
export default NewItemForm;
If it does not get the latest value, what other ways can you suggest me? One that comes to my mind to use Refs (forwardRef) and send it directly to the html input element and then call props.onAdd with the current value. But the thing is that I am using styled-components and my NewItemInput looks like this
export const NewItemInput = styled.input`
border-radius: 3px;
border: none;
box-shadow: #091e4240 0px 1px 0px 0px;
margin-bottom: 0.5rem;
padding: 0.5rem 1rem;
width: 100%;
background: #484747;
color: #f1f1f1;
`
So how will do that?

Want to target only one div at a time, the div which is hovered but not the other sibling div in ReactJS?

I am having a unique scenario but I am unable to understand this.
When I hover on one DIV then it selects both the sibling DIVS.
I do not want this behaviour.
I want to select only the DIV which is being hovered.
How can I achieve this in ReactJS ?.
The working code is shown below.
App.js
import React,{useState} from 'react';
import "./App.css";
const App = () => {
const [hover, setHover] = useState(false);
const texts = ["Arjun", "Andy"];
let cclass = hover ? "item itemHover":"item";
return (
<div className="wrapper">
{
texts.map((t, i) => (
<div className={cclass} key={i} onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}>
{t}
</div>
))
}
</div>
)
}
export default App;
App.css
.wrapper{
width: 60%;
margin: 10rem auto;
border: 1px solid;
display: flex;
}
.item{
width: 50%;
border: 1px solid grey;
height: auto;
padding: 2rem 3rem;
}
.itemHover{
background: grey;
}
The hover state in App is common for both divs. To make it work you need to
have hover state for each div separate. For this, create a new component TextDiv
import React, {useState} from "react";
import "./styles.css";
export default function TextDiv({t}) {
const [hover, setHover] = useState(false);
let cclass = hover ? "item itemHover":"item";
return (
<div className={cclass} onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}>
{t}
</div>
)
}
and change App.js file
import React, {useState} from "react";
import TextDiv from './TextDiv'
import "./styles.css";
export default function App() {
const texts = ["Arjun", "Andy"];
return (
<div className="wrapper">
{
texts.map((t, i) => (
<TextDiv t={t} key={i}/>
))
}
</div>
)
}
Can you do it with CSS? It's very simple with CSS. Just add this:
.item:hover{
background: grey;
}

Pass background as prop in styled-components

I am trying to pass background as a prop in styled-components but I'm not sure how to pass this via my cdn function as it doesn't output correctly in the css
this is the output of the css:
background: url(https://local.dev:5601/pub/media/icons/menu/function (props) { return props.background;}.png);
How can I pass the props via my cdn function?
config.js
export function cdn(path) {
return `https://local.dev:5601/pub/media/${path}`;
}
class Config {
cdn
}
export default Config;
App.js
import React from "react";
import styled from 'styled-components'
const Circle = styled.span`
display: block;
position: relative;
width: 35px;
height: 35px;
cursor: pointer;
border-radius: 50%;
background: url("${cdn('icons/menu/' + (props => props.background) + '.png')}");
margin: 5px;
`
const CircleWrap = styled.div`
display: flex;
flex-wrap: wrap;
padding: 0 0 5px 0;
`
class App extends React.Component {
selectColor = (color) => {
this.props.selectColor(color);
}
render() {
return (
<SettingDrop
title={"Profilfarbe"}
closeDropdown={this.props.closeDropdown}
openDropdown={this.props.openDropdown}
isOpen={this.props.isOpen}
isHidden={this.props.isHidden}
isValid={this.props.isValid}
icon={<ProfilFarbe />}
>
<CircleWrap>
{console.log(this.props.colors)}
{this.props.colors.map( color =>
<Circle
background={color.hash}
onClick={()=>this.selectColor(color.alias)}
>
</Circle>
)}
</CircleWrap>
</SettingDrop>
);
}
};
the following syntax will work:
background: url("${cdn('icons/menu/')}${props => props.background}.svg");

How to achieve tag agnostic styled components?

If I want a button but, only the presentational part of that, so if I do:
import styled from 'styled-components'
const Button = styled.button`
color: red;
text-align: center;
`
I'm forced to render a button tag, but what about if semantically I need an anchor?
Use the "as" polymorphic prop in v4
copy/pasta from the example in the docs:
const Component = styled.div`
color: red;
`;
render(
<Component
as="button"
onClick={() => alert('It works!')}
>
Hello World!
</Component>
)
styled-components provides withComponent that'll be useful for cases where you want to use an a different tag with a component. This is similar to #siddharthkp's answer in function, but uses the API.
Example from the documentation:
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// We're replacing the <button> tag with an <a> tag, but reuse all the same styles
const Link = Button.withComponent('a')
// Use .withComponent together with .extend to both change the tag and use additional styles
const TomatoLink = Link.extend`
color: tomato;
border-color: tomato;
`;
render(
<div>
<Button>Normal Button</Button>
<Link>Normal Link</Link>
<TomatoLink>Tomato Link</TomatoLink>
</div>
);
You can use it with a anchor tag as well, there's nothing stopping you.
import styled from 'styled-components'
const Button = styled.a`
color: red;
text-align: center;
`
If you want to keep both, you can reuse the styles by pulling them out:
import styled from 'styled-components'
const styles = `
color: red;
text-align: center;
`
const Button = styled.button`
${styles}
`
const LinkButton = styled.a`
${styles}
`
I've asked the same question on styled-components issue tracker: https://github.com/styled-components/styled-components/issues/494
And the current "solution" that I've found is:
// agnosticStyled.js
import React from 'react'
import styled from 'styled-components'
export default styled(
({tag = 'div', children, ...props}) =>
React.createElement(tag, props, children)
)
And then when you need it:
import React from 'react'
import styled from './agnosticStyled'
const Button = styled`
color: palevioletred;
text-transform: uppercase;
`
export default Button
And finally:
import React from 'react'
import Button from './Button'
const Component = () =>
<div>
<Button>button</Button>
<Button tag="button">button</Button>
<Button tag="a" href="https://google.com">button</Button>
</div>
export default Component
Here a full functioning example: https://codesandbox.io/s/6881pjMLQ
Since we're just using JavaScript, why not use a function?
const myButtonStyle = (styled, tag) => {
return styled[tag]`
color: red;
text-align: center;
`
}
const Button = myButtonStyle(styled, 'button')
As #typeoneerror pointed out, styled-components provides WithComponent. You can use this to to create a new component based on a prop containing the tag. Piggybacking off the example, it would look like this:
const _Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const Button = ({ as: tag = 'button', children, ...props }) => {
// We're replacing the <button> tag with whatever tag is assigned to the 'as' prop (renamed to 'tag' and defaulted to button), but reuse all the same styles
const Composed = _Button.withComponent(tag);
// We return the newly-created component with all its props and children
return <Composed {...props}>{children}</Composed>;
};
render(
<div>
<Button>Normal Button</Button>
<Button as='a'>Normal Link</Button>
</div>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Categories

Resources