Change hover style under custom component in styled-component - reactjs

I have a below structure.
<Nav>
<Title/>
<DropDown />
</Nav>
<Nav /> is a class component and I need to show Dropdown when I hover over <Nav />.
Here is the code snippet of <Nav />.
export default class HeaderLink extends React.PureComponent {
...
}
Here is the code snippet of <DropDown />.
const Container = styled.ul`
opacity: 0;
visibility: hidden;
transform: translateY(20px);
transition: all .3s ease-in-out;
${Nav}:hover & {
opacity: 1;
visibility: visible;
transform: translateY(-2px);
}
`;
const DropDown = ({ items }) => (
<Container>
{items.map(({ title, url }) => (
<a href={url}>{title}</a>
))}
</Container>
);
DropDown.propTypes = {
items: PropTypes.array.isRequired
};
export default DropDown;
This is not working but I figured that If I define <Nav /> component as a styled-component, it works
i.e. const Nav = styled.ul''
But it's not working for the class component.
Any thoughts on this?
Thanks.

You're attempting to use a parent as a selector, which is not currently possible in CSS (see: Is there a CSS parent selector?). Your :hover should be on your Nav component, which in turn targets the appropriate child element.
See example CodeSandbox here: https://codesandbox.io/s/x9lmkply4.

Related

Encapsulate a NavLink component and style it with Styled Component

I am trying to create a menu with hyperlinks from the react-rounter-dom library component NavLink
and style it with styled component
I Created a component called Link which is where NavLink is so you don't repeat this same line of code multiple times, then pass this component to styled component to inherit its properties so you can apply styles to it.
but the styles are not being applied to my component
NavLink
import { NavLink as NavLinkReactRouterDom } from "react-router-dom";
const Link = function ({ to, children, ...props }) {
return (
<>
<NavLinkReactRouterDom
{...props}
className={({ isActive }) =>
// console.log(isActive)
isActive ? "is-active" : undefined
}
to={to}
>
{children}
</NavLinkReactRouterDom>
</>
);
};
export default Link;
sidebarStyled.js (css)
import styled from "styled-components";
import Link from "./NavLink";
// NavLink
export const Prueba = styled(Link)`
color: white;
font-size: 50px;
text-decoration: none;
display: flex;
justify-content: flex-start;
align-items: stretch;
flex-direction: row;
&.is-active {
color: green;
}
`
Sidebar
const Sidebar = function () {
return (
<SidebarContainer>
<LogoContainer>
<img src={Logo} alt="Logo" />
</LogoContainer>
<h1>Sidebe Here</h1>
<Divider />
<Menu>
<NavList>
<NavItem>
<Prueba to="/">
<LinkIcon icon="typcn:home-outline" />
Inicio
</Prueba>
</NavItem>
</NavList>
</Menu>
</SidebarContainer>
);
};
export default Sidebar;
Issue
The className prop of the styled component, Prueba isn't passed through to the component it's attempting to style, the NavLinkReactRouterDom component. Or rather, it is passed implicitly when the props are spread into it, but NavLinkReactRouterDom is overriding and setting it's own className prop.
const Link = function ({ to, children, ...props }) {
return (
<>
<NavLinkReactRouterDom
{...props} // <-- styled-component className pass here
className={({ isActive }) => // <-- overridden here!
// console.log(isActive)
isActive ? "is-active" : undefined
}
to={to}
>
{children}
</NavLinkReactRouterDom>
</>
);
};
Solution
The solution is to merge the styled-component's className prop with the active classname used for the NavLinkReactRouterDom component.
Example:
const Link = function ({ to, children, className, ...props }) {
return (
<NavLinkReactRouterDom
{...props}
className={({ isActive }) =>
[className, isActive ? "is-active" : null].filter(Boolean).join(" ")
}
to={to}
>
{children}
</NavLinkReactRouterDom>
);
};
you may need to use Prueba, instead of Link. since you inherit the Link component, applied custom CSS and store it in the variable named Prueba.
hence import it in the sidebar.js file and use it there
refer: https://codesandbox.io/s/objective-smoke-1bflsh?file=/src/App.js
add
import 'Prueba' from sidebarStyled.js'
change
....
<Prueba to="/">
<LinkIcon icon="typcn:home-outline" />
Inicio
</Prueba>
....

How to override React Slick classes in a styled-component?

I have a React slick carrousel that I try to style to my convenience. I have wrapped the Slider component in a styled component, but I can't override any style of any classes.
Here is what I write:
const StyledSlider = styled(Slider)`
&.slick-list{
padding:0;
}`;
export default function App() {
return (
<StyledSlider {...settings}>
{images.map((image, i) => {
return (
<div key={i}>
<Image src={image} alt="img" />
</div>
);
})}
</StyledSlider>
);
}
It doesn't work at all. How to fix it? Here is also a sandbox just in case: https://codesandbox.io/s/cranky-noether-1hdhr?file=/src/App.js
This is working, but you need the !important to override the Slider style
const StyledSlider = styled(Slider)`
.slick-list {
padding: 0 !important;
}
`;
https://codesandbox.io/s/summer-glitter-568yj?file=/src/App.js:901-987

how change background color in different pages

i am two page in reactjs
pageOne.js:
import React from "react";
import { Link } from "react-router-dom";
import "./pageOne.css";
const PageOne = () => {
return (
<div>
one
<br />
<Link to="/pageTwo">Two Page</Link>
</div>
);
};
export default PageOne;
pageTwo.js:
import React from "react";
import { Link } from "react-router-dom";
import "./pageTwo.css";
const PageTwo = () => {
return (
<div>
two
<br />
<Link to="/">One Page</Link>
</div>
);
};
export default PageTwo;
i am define two css files for change background color when page loaded.
pageOne.css
body {
background-color: whitesmoke !important;
}
pageTwo.css
body {
background-color: crimson !important;
}
it's problem.in pageOne background color is crimson and in pageTwo background color is crimson.
sample
As I said earlier, there is only one body tag in the DOM tree by default. So when you try to style it whatever comes last will override the previous ones and in your case, the page two style will override the page one style.
To solve this, you got several options, but I will go with the easiest one. You can make a container for each of your pages and then assign a colour to that container to make the whole page background as you desired (You can simply make a layout component then wrap each of the components within it and with similar approach make it reusable). So, for example, you can create your first page like this:
<div className="crimson">
two
<br />
<Link to="/">one Page</Link>
</div>
and style it like this:
.crimson {
background-color: crimson;
min-height: 100vh; /* minimum height of page would be equal to available view-port height */
}
This goes the same for your other page. But you need to consider you have to remove the default margins from the body itself to prevent any disorder.
Working Demo:
I would solve this with Layout component:
const Layout = ({ backgroundColor = '#fff', children }) => (
<div style={{ backgroundColor }} className="layout">
{children}
</div>
)
then remove your css(and try not to use important in your css)
<Layout backgroundColor="#fff"><PageOne /></Layout>
and
<Layout backgroundColor="#f00"><PageTwo /></Layout>

how to pass multiple classNames to inner children with emotion js

I want to split the components into baseUI one and styled one:
eg.
MyComponent.jsx
export default class MyComponent extends React.Component {
...
...
render() {
const { wrapperClassName, className, childClassName } = this.props;
return (
<div className={wrapperClassName>
<div className={className />
<div className={childClassName} />
</div>
)
}
}
StyledMyComponent.jsx
import styled from 'react-emotion'
const StyledMyComponent = styled(MyComponent)(
...
...
)
export default StyledMyComponent
however anything I put to the styled function's argument they will go to the className only, is there a way I specify which props goes to which className?
also can I do something like sass/less with children selector?
hypothetically something like this:
const classes = css`
color: red;
span { // this works
color: black;
}
.childClassName { // this doesn't work
color: green;
}
`
<MyComponent className={classes} />
No you can't.
What you can do, is create specific components for the underlying div. This is how I make my components:
const MyComponentStyle = styled('div')....;
const MySecondComponentStyle = styled('div')...;
const MyThirdStyle = styled('div')...;
const MyComponent = ({ wrapperClassName, childClassName, className }) =>
<MyComponentStyle className={wrapperClassName}>
<MySecondComponentStyle className={className} />
<MyThirdStyle className={childClassName} />
</MyComponentStyle>
)
}
}
Conditionally styling the element and its children based on class names
You can conditionally change the styling of stuff below the main component based on its classes.
Taking your example:
const Something = () => (
<MyComponent className={classes}>
<div className="childClassName">child</div>
<div className="otherChildClassName">child</div>
</MyComponent>
You can style the children like so:
const classes = css`
color: red;
span {
color: black;
}
& .childClassName {
color: green;
}
`
note the & character. It essentially means "this class". So & .childClassName means "childrens of this element with class childClassName.
You could also use &.someClassName (note the lack of space), which would mean: "this element when it also has a class named someClassName.

React CSSTransition wrong class used on exit

UPDATE:
For those facing the same problem: I found a similar issue posted on the git page of ReactTransitionGroup with the solution in there: https://github.com/reactjs/react-transition-group/issues/182.
To be honest, 90% of the solution blew right over my head and I don't understand much of it at all, this is the only part that made sense to me(taken from React TransitionGroup and React.cloneElement do not send updated props which is linked the git issue):
Passing Props to Leaving Children?
Now the question is, why aren't updated props being passed to the leaving element? Well, how would it receive props? Props are passed from a parent component to a child component. If you look at the example JSX above, you can see that the leaving element is in a detached state. It has no parent and it is only rendered because the is storing it in its state.
I have to further research and understand the way parent and children components communicate. I'm still new to web development but things are slowly starting to make sense.
------------------
Initial question asked:
When you are attempting to inject the state to the children of your through React.cloneElement, the leaving component is not one of those children.
I have a small app that contains a nav menu and routes nested between a switch container. Every click on a nav item, I check if the index is higher or lower to set the animation state (Left or right) accordingly and then store the new index of the clicked nav item.
Everytime I switch routes, the animation applied is either fade-left or fade-right. Now this all works fine, but the only problem is when the class switches to right from left the EXIT animation applied uses the previous class given.
I understand why this is happening, It's because the exit class used is the one initially set, and when the new class is applied only on the second change does the new exit take affect. The transition documentation is very limited and I couldn't find anything.
const { Component } = React;
const { TransitionGroup, CSSTransition } = ReactTransitionGroup;
const { HashRouter, Route, NavLink, Switch, withRouter } = ReactRouterDOM;
var icons = [
{icon: 'far fa-address-book active', path: '/'},
{icon: 'fab fa-linkedin', path: '/linked'},
{icon: 'fas fa-gamepad', path: '/hobbies'}
];
const Nav = props => {
return (
<div className="nav">
<ul>
{
icons.map((icon, index) => {
return (
<li
key={index}
onClick={() => props.clicked(index)}
className={props.active == index ? 'active' : false}>
<NavLink to={icon.path} className="NavLink">
<i className={icon.icon} key={icon.icon}></i>
</NavLink>
</li>
);
})
}
</ul>
</div>
);
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentViewIndex: 0,
animationDirection: 'right'
}
this.setIndex = this.setIndex.bind(this);
}
setIndex(index){
const animationDirection = index < this.state.currentViewIndex ? 'left' : 'right';
this.setState({currentViewIndex: index, animationDirection});
}
render () {
const { location } = this.props;
return (
<div className="container">
<Nav active={this.state.currentViewIndex} clicked={() => this.setIndex()} />
<TransitionGroup>
<CSSTransition
key={location.pathname}
classNames={`fade-${this.state.animationDirection}`}
timeout={1000}>
<Switch location={location}>
<Route exact path="/" render={() => <h1>Home</h2>} />
<Route path="/linked" render={() => <h1>Linked</h2>} />
<Route path="/hobbies" render={() => <h1>Hobbies</h2>} />
</Switch>
</CSSTransition>
</TransitionGroup>
</div>
);
}
}
const AppWithRouter = withRouter(App);
ReactDOM.render(<HashRouter><AppWithRouter /></HashRouter>, document.querySelector('#root'));
The CSS classes applied to transitions:
.fade-left-enter {
position: unset;
transform: translateX(-320px);
}
.fade-left-enter.fade-left-enter-active {
position: unset;
transform: translateX(0);
transition: transform .5s ease-in;
}
.fade-left-exit {
position: absolute;
bottom: 0;
transform: translateX(0);
}
.fade-left-exit.fade-left-exit-active {
transform: translateX(320px);
transition: transform .5s ease-in;
}
.fade-right-enter {
position: unset;
transform: translateX(320px);
}
.fade-right-enter.fade-right-enter-active {
position: unset;
transform: translateX(0);
transition: transform .5s ease-in;
}
.fade-right-exit {
position: absolute;
bottom: 0;
transform: translateX(0);
}
.fade-right-exit.fade-right-exit-active {
transform: translateX(-320px);
transition: transform .5s ease-in;
}
The reason for your problem is that the exiting component is already detached and therefor does not get any updates. You can find a very good explanation of your problem here.
You can use the prop childFactory from <TransitionGroup> to solve this:
childFactory
You may need to apply reactive updates to a child as it is exiting.
This is generally done by using cloneElement however in the case of an
exiting child the element has already been removed and not accessible
to the consumer.
If you do need to update a child as it leaves you can provide a
childFactory to wrap every child, even the ones that are leaving.
Try the following code changes in your render method:
render () {
const { location } = this.props;
const classNames = `fade-${this.state.animationDirection}`; // <- change here
return (
<div className="container">
<Nav active={this.state.currentViewIndex} clicked={() => this.setIndex()} />
<TransitionGroup
childFactory={child => React.cloneElement(child, { classNames })} // <- change here
>
<CSSTransition
key={location.pathname}
classNames={classNames} // <- change here
timeout={1000}
>
<Switch location={location}>
<Route exact path="/" render={() => <h1>Home</h2>} />
<Route path="/linked" render={() => <h1>Linked</h2>} />
<Route path="/hobbies" render={() => <h1>Hobbies</h2>} />
</Switch>
</CSSTransition>
</TransitionGroup>
</div>
);
}

Resources