I have a component that is already built out like so:
const Banner = ({ image, heading, text }) => (
<Container>
<Background src={image}>
<BannerContent>
<h1>{heading}</h1>
<h2>{text}</h2>
</BannerContent>
</Background>
</Container>
);
const BannerContent = styled.div`
h1 {
font-size: 24px;
}
h2 {
font-size: 16px;
}
`;
and I'm trying to override the styles of the h1 and h2 and add new styles like so in another component:
const PageBanner = styled(Banner)`
h1 {
font-size: 20px;
width: ...
}
h2 {
font-size: 13px;
width: ...
}
`;
However, none of that is happening. I'm assuming it's because it's nested in there? Am I able to override the styles? Or should I just build a similar component to it?
If you are styling one of your own custom components, you must make sure you use the className prop that styled components gives to the component.
const Banner = ({ image, heading, text, className }) => (
<Container className={className}>
<Background src={image}>
<BannerContent>
<h1>{heading}</h1>
<h2>{text}</h2>
</BannerContent>
</Background>
</Container>
);
const PageBanner = styled(Banner)`
h1 {
font-size: 20px;
width: ...
}
h2 {
font-size: 13px;
width: ...
}
`;
Related
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"/>
)
}
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;
}
I came across something weird while trying to pass props in SolidJS. I've created a store using createStore which I pass through the component tree using Context.Provider. I also have the helper function useStore which lets me access the store anywhere in the component tree (I'm experimenting with React design patterns in SolidJS). I have two components Anime.jsx (parent) and EpisodeList.jsx (child). I'm fetching data when the Anime component mounts and then populate the store with the setter provided by createStore.After which I pass the fetched data to EpisodeList. However, accessing the props of EpisodeList returns an empty proxy (Not sure why, but I think the EpisodeList component isn't re-rendered when store is updated with store.currentAnimeData). I've attached the output below of the console.log statements below.
Any help regarding this would be highly appreciated.
###################################
# Anime.jsx (Parent component)
###################################
const Anime = (props) => {
const [store, setStore] = useStore();
const getAnimeData = async () => {
const currentAnimeId = store.currentAnime.animeId;
const currentAnimeData = await firebase.getAnimeData(currentAnimeId);
setStore(
produce((store) => {
store.currentAnimeData = currentAnimeData;
})
);
};
onMount(() => {
getAnimeData();
});
return (
<>
<div
className={css`
width: 100%;
min-height: 20px;
margin: 8px 0px 5px 0px;
padding: 0px 10px;
box-sizing: border-box;
font-size: 20px;
word-wrap: break-word;
line-height: 1;
`}
>
<span
className={css`
font-size: 20px;
color: #e32451;
`}
>
{"Watching: "}
</span>
{store.currentAnime.name}
</div>
<Search></Search>
<EpisodeList animeData={store.currentAnimeData.episodes} />
</>
);
};
#####################################
# EpisodeList.jsx (child component)
#####################################
const EpisodeList = (props) => {
console.log(props);
console.log(props.animeData);
...... # UI stuff
return (
<div
className={css`
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding-bottom: 5px;
margin-top: 10px;
`}
>
<ScrollActionUp onmousedown={[scroll, true]} onmouseup={onmouseup}>
➭
</ScrollActionUp>
<div
className={css`
width: 100%;
height: 432px;
box-sizing: border-box;
padding: 10px 10px 0px 10px;
overflow: hidden;
`}
ref={scrollRef}
>
<For each={animeData.episodes}>
{(episode, index) => {
return (
<Episode status={episode.watched} episode={episode}></Episode>
);
}}
</For>
</div>
<ScrollActionDown onmousedown={[scroll, false]} onmouseup={onmouseup}>
➭
</ScrollActionDown>
</div>
);
};
###############
# store.jsx
###############
import { createContext, createSignal, useContext } from "solid-js";
import { createStore } from "solid-js/store";
const StoreContext = createContext();
export function ContextProvider(props) {
const [store, setStore] = createStore({});
return (
<StoreContext.Provider value={[store, setStore]}>
{props.children}
</StoreContext.Provider>
);
}
export function useStore() {
return useContext(StoreContext);
}
In my Class component Field.jsx render(), I'm expanding my <Position> component using <Flipper>, (an abstracted flip animation), like so:
import { Flipper, Flipped } from 'react-flip-toolkit'
import { Position } from "./Position";
import "./css/Position.css";
class Field extends Component {
constructor(props) {
super(props);
this.state = {
fullScreen: false,
};
}
toggleFullScreen() {
this.setState({ fullScreen: !this.state.fullScreen });
}
...
render() {
const { players } = this.props;
const { fullScreen } = this.state;
if(players){
return (
<div className="back">
<div className="field-wrapper" >
<Output output={this.props.strategy} />
<Flipper flipKey={fullScreen}>
<Flipped flipId="player">
<div className="field-row">
{this.getPlayersByPosition(players, 5).map((player,i) => (
<Position
key={i}
className={fullScreen ? "full-screen-player" : "player"}
getPositionData={this.getPositionData}
toggleFullScreen={this.toggleFullScreen.bind(this)}
>{player.name}</Position>
))}
</div>
</Flipped>
</Flipper>
</div>
</div>
);
}else{
return null}
}
When I render it, I get clickable items from the mapped function getPlayersByPosition(), like so:
And if I click on each item, it expands to a div with player name:
Which is passed as props.children at component <div>
Position.jsx
import React from "react";
import "./css/Position.css";
export const Position = props => (
<div
className={props.className}
onClick={() => {
props.getPositionData(props.children);
props.toggleFullScreen();
console.log(props.getPositionData(props.children))
}}
>
{props.children}
</div>
);
getPositionData(), however, returns an object with many items on its turn, as seen by console above:
{matches: 7, mean: 6.15, price: 9.46, value: 0.67, G: 3, …}
QUESTION:
How do I pass and print theses other props keys and values on the expanded purple div as text?, so as to end with:
Patrick de Paula
matches: 7
mean: 6.15
price:9.46
....
NOTE:
Position.css
.position-wrapper {
height: 4em;
display: flex;
justify-content: center;
align-items: center;
font-weight: lighter;
font-size: 1.4em;
color: #888888;
flex: 1;
/*outline: 1px solid #888888;*/
}
.player {
height: 4em;
width: 4em;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
font-weight: lighter;
font-size: 1.4em;
/*background-color: #66CD00;*/
color: #ffffff;
}
.full-screen-player {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: pointer;
background-image: linear-gradient(
45deg,
rgb(121, 113, 234),
rgb(97, 71, 182)
);
}
Looks like the props are all set & ready to be print as seen on your console. You can access them via props.getPositionData(props.children).property_name_here or destructure them
export const Position = props => {
const { matches, mean, price } = props.getPositionData(props.children);
return (
<div
className={props.className}
onClick={() => {
props.getPositionData(props.children);
props.toggleFullScreen();
console.log(props.getPositionData(props.children))
}}
>
<p>Name: {props.children}</p>
<p>Matches: {matches}</p>
<p>Mean: {mean}</p>
<p>Price: {price}</p>
</div>
)
}
Regarding the issue on the fullScreen prop (see comments section):
Is there a way to print them ONLY after toggleFullScreen()
Since you already have a state on the Field component which holds your fullScreen value, on your Field component, you need to pass the fullScreen prop as well to the Position component. e.g., fullScreen={this.state.fullScreen}. Back on Position component, have some condition statements when you are rendering.
Example:
<>
{props.fullScreen &&
<p>Name: {props.children}</p>
}
</>
I am Using GatsbyJs and am trying to accomplish a active class toggle. I have a simple Component composed of a anchor tag with a span inside. I want to change some css by toggling a active class on the anchor when it is clicked. Here is my code so far, I am also using styled-components. I tried some vanilla js in the broken code section below, that obviously didn't work hence broken code.
Thanks in advance
styles
const Menu = styled.a`
position: absolute;
cursor: pointer; padding: 10px 35px 16px 0px;
span, span:before, span:after{
cursor: pointer;
border-radius: 1px;
height: 5px;
width: 35px;
background: #000000;
position: absolute;
display: block;
content: '';
transition: all 200ms ease-in-out;
}
span:before{
top: -10px;
}
span:after{
bottom: -10px;
}
.active span{
background-color: transparent;
}
.active span:before, .active span:after{
top:0;
}
.active span:before{
transform: rotate(45deg);
}
.active span:after{
transform: rotate(-45deg);
}
`
component
const TemplateWrapper = ({ children }) => (
<div>
<Wrapper>
<Menu id="nav-toggle" className="menu"><span></span></Menu>
{children()}
</Wrapper>
</div>
)
TemplateWrapper.propTypes = {
children: PropTypes.func,
}
export default TemplateWrapper
broken code
document.querySelector( "#nav-toggle" )
.addEventListener( "click", function() {
this.classList.toggle( "active" );
});
This is the html that is rendered
<a class="menu sc-bwzfXH SqHLW" id="nav-toggle"><span></span></a>
Okay, you can take a look here REACT - toggle class onclick
. There are several answers that show the way how you can achieve that, but things get messier when you want to use more than one class.
I like this package to handle the cases where you deal with styling with multiple classes based on the props or state: https://github.com/JedWatson/classnames.
I simplified the styles for the sake of clarity of this answer and assumed that the menu is a global class, you want to apply as well.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import styled from 'styled-components';
const Wrapper = styled.div``;
const Menu = styled.a`
span {
color: green;
}
&.active span {
color: blue;
}
`;
class TemplateWrapper extends Component {
state = {
isActive: false
};
handleClick = () => {
this.setState(state => ({ isActive: !state.isActive }));
};
render() {
const menuClass = classNames({
menu: true,
active: this.state.isActive
});
return (
<Wrapper>
<Menu id="nav-toggle" className={menuClass} onClick={this.handleClick}>
<span>Test</span>
</Menu>
{this.props.children}
</Wrapper>
);
}
}
TemplateWrapper.propTypes = {
children: PropTypes.node
};
TemplateWrapper.defaultProps = {
children: null
};
export default TemplateWrapper;
Notice the ampersand (&) before .active. As stated in the docs, styled components supports all of CSS plus nesting.
Ampersands (&) get replaced by our generated, unique classname for that styled component, making it easy to have complex logic.
However, you can achieve the same effect without classnames by fully utilizing styled components functionality. Take a look at this section of the documentation.
A few changes needed:
const Menu = styled.a`
span {
color: ${props => (props.active ? 'blue' : 'green')};
}
`;
The render method would look like that:
render() {
return (
<Wrapper>
<Menu id="nav-toggle" className="menu" active={this.state.isActive} onClick={this.handleClick}>
<span>Test</span>
</Menu>
{this.props.children}
</Wrapper>
);
}
You can use component state to mange this.
Code will look like:
Updated: Moved logic to Menu component as it makes more sense to place it there. Below working example
class Menu extends React.Component {
constructor(props) {
super(props);
this.menuClick = this.menuClick.bind(this);
this.state = {
menuClass: '',
}
}
menuClick(e) {
const menuClass = this.state.menuClass === '' ? 'active' : '';
this.setState({ menuClass });
}
render() {
const {children, id} = this.props;
const menuClassName = `menu sc-bwzfXH SqHLW nav-toggle ${this.state.menuClass}`;
return (<a className={menuClassName} id={id} onClick={this.menuClick}>{children}</a>);
}
}
ReactDOM.render(<Menu><span>Test link</span></Menu>, document.getElementById('menu'))
.menu {
font-weight: bolder;
color: blue;
}
.active{
color:red;
}
<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>
<div id="menu" />