How to style Next.js active link with Styled Components - javascript

I'm trying to add a style to the active link with styled components.
I have a navbar and depending on which link is currently active, the respective link will have a style added to it
Thanks
import React from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { bool } from 'prop-types';
import StyledSidebar from '../../styles/header/styled.sidebar'
import StyledLink from '../../styles/header/styled.links'
export default function Sidebar({ open }) {
const router = useRouter()
return (
<StyledSidebar open={open}>
<div>
<ul>
<StyledLink><Link href="/" className={router.pathname == "/" ? 'active' : ''}>HOME</Link></StyledLink>
<StyledLink><Link href="/destination" className={router.pathname == "/destination" ? 'active' : ''}>DESTINATION</Link></StyledLink>
<StyledLink><Link href="/crew" className={router.pathname == "/crew" ? 'active' : ''}>CREW</Link></StyledLink>
<StyledLink><Link href="/technology" className={router.pathname == "/" ? 'active' : ''}>TECHNOLOGY</Link></StyledLink>
</ul>
</div>
</StyledSidebar>
)
}
Sidebar.propTypes = {
open: bool.isRequired,
};

Check this https://codesandbox.io/s/exciting-kilby-eb3pj?file=/src/App.js:0-584 link where you see this logic live
Pass the props to styled-components as normal props restructure and use it for conditionally render the styles
import styled, { css } from "styled-components";
// props which are received are destructured
const Button = styled.button(
({ someprops }) => css`
${console.log(someprops)}
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
// Conditionally render the styles inside style
color: ${someprops === "root" ? "red" : "green"};
border: 2px solid ${someprops === "root" ? "red" : "green"};
`
);
export default function App() {
return (
<div>
<Button someprops="root">Themed</Button>
</div>
);
}

Related

How to render different style css on same component?

I have react component which is a button and I render this component three times. I want to add some CSS on the second component but I don't know how. I tried to add some class names, but then I can't figure it out where to put this style in the CSS.
I can change css in element.style in dev tools but can't in project.
import './App.css';
import './flow.css';
import './neonButton.css';
import GlowBox from './GlowBox';
import NavBar from './NavBar';
function App() {
return (
<div>
<div className='divBut'>
<NavBar></NavBar>, <NavBar className='drugi'></NavBar>,<NavBar></NavBar>
</div>
<GlowBox></GlowBox>
</div>
);
}
export default App;
import styled from 'styled-components';
const NavBar = (props) => {
return (
<div>
<Container>
<a class='neon'>Neon</a>
</Container>
</div>
);
};
const Container = styled.div`
background-color: transparent;
`;
export default NavBar;
I try to add props to component
<!-- begin snippet: js hide: false console: true babel: false -->
and then add a type to a component like this
const NavBar = (type) => {
return (
<div>
<Container>
<a class={`neon ${type}`}>Neon</a>
</Container>
</div>
);
};
<NavBar></NavBar>, <NavBar type='drugi'></NavBar>,<NavBar></NavBar>
but nothing is change.
You have props that you don't use, this is a good simple read on How to Pass Props to component, you can adjust this to other needs, this is example...:
import styled from 'styled-components';
const NavBar = ({class}) => {
return (
<div>
<Container>
<a class={class}>Neon</a>
</Container>
</div>
);
};
const Container = styled.div`
background-color: transparent;
`;
export default NavBar;
...
import './App.css';
import './flow.css';
import './neonButton.css';
import GlowBox from './GlowBox';
import NavBar from './NavBar';
function App() {
const NavStyles = {
className1: 'neon',
className2: 'drugi'
};
return (
<div>
<div className='divBut'>
<NavBar class={NavStyles.className1}></NavBar>, <NavBar class={NavStyles.className2}></NavBar>,<NavBar class={NavStyles.className1}></NavBar>
</div>
<GlowBox></GlowBox>
</div>
);
}
export default App;
Edit: Given that you have edited your question I have new information for you.
1.) You can't use the reserved word class in React, because class means something different in Javascript than it does in html. You need to replace all instances of class with className.
2.) Did you notice how in the devtools on your button it says the className says: neon [object object]?
You should use a ternary operator to handle the cases where you don't pass the type prop.
ex.) class={neon ${props?.type !== undefined ? type ''}}
3.) You are trying to apply a className to a component, which does not work. The className attribute can only be applied directly to JSX tags like h1, div, etc. Use a different prop name, then you can use that to decide the elements className.

How can I make this scroll To Top upon re-rendering while keeping the headers reactive functionality?

This is alot of code but it is the minimal way that I could think of reproducing my problem.
view in sandbox: https://codesandbox.io/s/silly-kare-j0kmz
I would like the header bar to hide upon scrolling. The problem is everytime I click on a box to go to another route, or click header to come back to the home route, the scroll bar stays where it was before. That is everytime I move from route to route, the scrollbar does not move to the top.
I was able to fix this problem with the scrollToTop code, But in order to make it scroll to the top, I need to remove "overflow-y" from App.css, which stops my header from reacting onScroll.
I came to realize this is because window is perhaps a BOM object and only targets the browser window itself, not div class which I have assigned onScroll to.
So it seems I can do either OR, but not both functions together. I would like both to happen, The scrolToTop on location change AND to keeep the Header Reacting to the onScroll method. How can I do that?
App.js ---------------------------------------------------
Provides routing to First.js and Second.js. Also has the onScroll method. That is when you scroll up, the header appears, and when you scroll up the header disapears. Along with some routing to 2 simple components.
import './App.css';
import {useState, useEffect, useRef} from 'react';
import { Route, Routes} from 'react-router-dom';
import Second from "./Second/Second";
import First from "./First/First";
import Header from './Header/Header';
import ScrollToTop from './ScrollToTop/ScrollToTop'
function App() {
const prevScrollY = useRef(0);
const [goingUp, setGoingUp] = useState(true);
const [HeaderisVisible, setHeaderIsVisible] = useState(0);
const onScroll = (e) => {
const currentScrollY = e.target.scrollTop;
if (prevScrollY.current < currentScrollY && goingUp) {
setGoingUp(false);
}
if (prevScrollY.current > currentScrollY && !goingUp) {
setGoingUp(true);
}
prevScrollY.current = currentScrollY;
console.log(goingUp, currentScrollY);
};
return (
<div className="App" onScroll = {onScroll}>
<ScrollToTop/>
<Routes>
<Route path = '/' exact element = {<First GoingUp = {goingUp}/>}/>
<Route path = '/second' element = {<Second GoingUp = {goingUp}/>} />
<Route path = '*'>
NOT FOUND
</Route>
</Routes>
</div>
);
}
export default App;
Header.js -------------------------------------------------
Header takes props from the state initialized in App.js containing a true or flase variable. and uses that in a conditional to either show or hide the header. Also on clicking the header you go back to the home page.
import './Header.css';
import {useState, useEffect} from 'react';
import {Link} from 'react-router-dom';
function Header(props) {
const [HeaderisVisible, setHeaderIsVisible] = useState(0);
useEffect(() => {
if(props.GoingUp == true){
setHeaderIsVisible(0);
}else{
setHeaderIsVisible(-199);
}
}, [props.GoingUp]);
return (
<Link to = '/'><div className = "Header"
style = {{
top: `${HeaderisVisible}px`
}}>
</div> </Link>
);
}
export default Header;
First.js --------------------------------------------------
First is a simple component that just displays some divs. Each black div will route the the second page.
import './First.css';
import {Link} from 'react-router-dom';
import Header from '../Header/Header';
function First(props) {
return (
<div className="First">
<Header GoingUp = {props.GoingUp}/>
<Link to = '/second'><div className = "entity"></div></Link>
<Link to = '/second'><div className = "entity"></div></Link>
<Link to = '/second'><div className = "entity"></div></Link>
<Link to = '/second'><div className = "entity"></div></Link>
<Link to = '/second'><div className = "entity"></div></Link>
</div>
);
}
export default First;
Second.js -------------------------------------------------
Second is a simple component that just displays some red divs.
import './Second.css';
import { Route, Routes, Link} from 'react-router-dom';
import Header from '../Header/Header';
function Second(props) {
return (
<div className="Second">
<Header GoingUp = {props.GoingUp}/>
<div className = "entity2"></div>
<div className = "entity2"></div>
<div className = "entity2"></div>
<div className = "entity2"></div>
<div className = "entity2"></div>
</div>
);
}
export default Second;
ScrollToTop.js --------------------------------------------
Gets the location via the url search path, and scrolls to the top of the page on every search.
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function ScrollToTop(props) {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}
First.css
-----------------------------------
.entity{
height: 200px;
margin: auto;
width: 200px;
border: 2px solid black;
background-color: black;
margin-top: 200px;
}
Second.css
-------------------------------------
.Second{
background-color: lightgreen;
}
.entity2{
height: 200px;
margin: auto;
width: 200px;
border: 2px solid black;
background-color: red;
margin-top: 200px;
}
Header.css
------------------------------------
.Header{
background-color: brown;
height: 200px;
position: fixed;
width: calc(100% - 17px);
}
App.css
-------------------------------------
html{
margin: 0;
}
body{
margin: 0;
}
.App{
overflow-y: auto;
background-color: lightblue;
height: 100vh;
}
I was able to solve my problem by replacing window.scrollTo(...) with the following: props.refProp.current.scrollTop = 0; inside of scrollToTop function.
export default function ScrollToTop(props) {
const { pathname } = useLocation();
useEffect(() => {
props.refProp.current.scrollTop = 0;
}, [pathname]);
return null;
}

Assign styled-component CSS property based on props passed to component

I have two components TextField and Label.
The TextField is passing the prop req to the Label. I want to modify the styled-component based on the req prop being passed in. Here is my current code that is not working.
No errors are being reported to the console.
TextField.js
import React, {Component} from 'react';
import styled from 'styled-components';
import Label from '../Label/Label';
const Wrapper = styled.div`
display: flex;
flex-direction: column;
margin: 16px 8px 8px 8px;
`;
const Input = styled.input`
border-bottom: 1px solid rgba(0, 0, 0, .23);
&:focus {
border-bottom: 1px solid #2196f3;
}
`;
class TextField extends Component {
render() {
const {
label,
req = true,
} = this.props;
return (
<Wrapper>
<Label req={req} text={label}/>
<Input type={'textfield'}/>
</Wrapper>
);
}
}
export default TextField;
Label.js
import React, {Component} from 'react';
import styled from 'styled-components';
const LabelBase = styled.label`
color: rgba(0, 0, 0, .54);
font-size: 1rem;
line-height: 1;
&:after {
content: ${props => props.req ? '*' : ''};
}
`;
class Label extends Component {
render() {
const {
req,
text,
} = this.props;
return (
<LabelBase req={req}>{text}</LabelBase>
);
}
}
export default Label;
You say you want to style the component based on the ref prop, but it seems that you're using that prop as a boolean to add text, not styles so I just went with a simplified solution for that since psuedo-selectors like :after aren't supported in React's JS styles. There are other ways around that if need be, but I think you can just do the following. However, I've included a way to pass styles to the child component as well for your reference:
class Label extends React.Component {
render() {
const {
req,
text,
moreStyles
} = this.props;
const styles = {
"color": "rgba(0, 0, 0, .54)",
"fontSize": "1rem",
"lineHeight": 1
}
return (
<div style={{...styles, ...moreStyles}}>{text + (req ? '*' : '')}</div>
);
}
}
ReactDOM.render(<Label text="test" req="Yes" moreStyles={{"backgroundColor": "blue", "border": "1px solid black"}}/>, document.getElementById("root"));
<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="root"></div>

Cannot create custom style with React and Bootstrap

I'm using react-bootstrap NPM package to make my React components look properly. I need to customize some of them, so I'm following the official React-Bootstrap documentation, but the code throws the error index.jsx:5 Uncaught TypeError: Cannot read property 'addStyle' of undefined.
This is my custom Button component code:
import React from "react";
import Button from 'react-bootstrap/lib/Button';
import bootstrapUtils from 'react-bootstrap/lib/utils/bootstrapUtils';
bootstrapUtils.addStyle(Button, 'custom');
export default class MediaItemButton extends React.Component {
render() {
return (
<div>
<style type="text/css">{`
.btn-custom {
background-color: purple;
color: white;
}
`}</style>
<Button bsStyle="primary">Custom</Button>
</div>
);
}
}
Change to this:
import { bootstrapUtils } from 'react-bootstrap/lib/utils';
You can do this as well:
import React from "react";
import Button from 'react-bootstrap/lib/Button';
import bootstrapUtils from 'react-bootstrap/lib/utils/bootstrapUtils';
bootstrapUtils.addStyle(Button, 'custom');
export default class MediaItemButton extends React.Component {
render() {
var styles={
"backgroundColor" : "purple",
"color" : "white"
};
return (
<div>
<Button style={styles} bsStyle="primary">Custom</Button>
</div>
);
}
}
bootstrapUtils is a named export and not a default therefore you need to use {} around it.
Try using:
import { bootstrapUtils } from 'react-bootstrap/lib/utils/bootstrapUtils';

react-router: How to disable a <Link>, if its active?

How can I disable a <Link> in react-router, if its URL already active? E.g. if my URL wouldn't change on a click on <Link> I want to prevent clicking at all or render a <span> instead of a <Link>.
The only solution which comes to my mind is using activeClassName (or activeStyle) and setting pointer-events: none;, but I'd rather like to use a solution which works in IE9 and IE10.
You can use CSS's pointer-events attribute. This will work with most of the browsers. For example your JS code:
class Foo extends React.Component {
render() {
return (
<Link to='/bar' className='disabled-link'>Bar</Link>
);
}
}
and CSS:
.disabled-link {
pointer-events: none;
}
Links:
pointer-events CSS property
How to disable HTML links
The How to disable HTML links answer attached suggested using both disabled and pointer-events: none for maximum browser-support.
a[disabled] {
pointer-events: none;
}
Link to source: How to disable Link
This works for me:
<Link to={isActive ? '/link-to-route' : '#'} />
I'm not going to ask why you would want this behavior, but I guess you can wrap <Link /> in your own custom link component.
<MyLink to="/foo/bar" linktext="Maybe a link maybe a span" route={this.props.route} />
class MyLink extends Component {
render () {
if(this.props.route === this.props.to){
return <span>{this.props.linktext}</span>
}
return <Link to={this.props.to}>{this.props.linktext}</Link>
}
}
(ES6, but you probably get the general idea...)
Another possibility is to disable the click event if clicking already on the same path. Here is a solution that works with react-router v4.
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';
class SafeLink extends Component {
onClick(event){
if(this.props.to === this.props.history.location.pathname){
event.preventDefault();
}
// Ensure that if we passed another onClick method as props, it will be called too
if(this.props.onClick){
this.props.onClick();
}
}
render() {
const { children, onClick, ...other } = this.props;
return <Link onClick={this.onClick.bind(this)} {...other}>{children}</Link>
}
}
export default withRouter(SafeLink);
You can then use your link as (any extra props from Link would work):
<SafeLink className="some_class" to="/some_route">Link text</SafeLink>
All the goodness of React Router NavLink with the disable ability.
import React from "react"; // v16.3.2
import { withRouter, NavLink } from "react-router-dom"; // v4.2.2
export const Link = withRouter(function Link(props) {
const { children, history, to, staticContext, ...rest } = props;
return <>
{history.location.pathname === to ?
<span>{children}</span>
:
<NavLink {...{to, ...rest}}>{children}</NavLink>
}
</>
});
React Router's Route component has three different ways to render content based on the current route. While component is most typically used to show a component only during a match, the children component takes in a ({match}) => {return <stuff/>} callback that can render things cased on match even when the routes don't match.
I've created a NavLink class that replaces a Link with a span and adds a class when its to route is active.
class NavLink extends Component {
render() {
var { className, activeClassName, to, exact, ...rest } = this.props;
return(
<Route
path={to}
exact={exact}
children={({ match }) => {
if (match) {
return <span className={className + " " + activeClassName}>{this.props.children}</span>;
} else {
return <Link className={className} to={to} {...rest}/>;
}
}}
/>
);
}
}
Then create a navlink like so
<NavLink to="/dashboard" className="navlink" activeClassName="active">
React Router's NavLink does something similar, but that still allows the user to click into the link and push history.
const [isActive, setIsActive] = useState(true);
<Link to={isActive ? '/link-to-route' : null} />
you can try this, this worked for me.
Create a slim custom component like this below, you can also apply styling & css if you want as well maybe play with the opacity and pointer events none etc... or you can set the "to" to null when disabled from props
type Props = { disabled?: boolean;} & LinkProps;
const CustomLinkReactRouter = (props: Props) => {
const { disabled, ...standardProps } = props;
return <Link {...standardProps} onClick={e => disabled && e.preventDefault()}/>
}
export default CustomLinkReactRouter;
Based on nbeuchat's answer and component - I've created an own improved version of component that overrides react router's Link component for my project.
In my case I had to allow passing an object to to prop (as native react-router-dom link does), also I've added a checking of search query and hash along with the pathname
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Link as ReactLink } from 'react-router-dom';
import { withRouter } from "react-router";
const propTypes = {
to: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]),
location: PropTypes.object,
children: PropTypes.node,
onClick: PropTypes.func,
disabled: PropTypes.bool,
staticContext: PropTypes.object
};
class Link extends Component {
handleClick = (event) => {
if (this.props.disabled) {
event.preventDefault();
}
if (typeof this.props.to === 'object') {
let {
pathname,
search = '',
hash = ''
} = this.props.to;
let { location } = this.props;
// Prepend with ? to match props.location.search
if (search[0] !== '?') {
search = '?' + search;
}
if (
pathname === location.pathname
&& search === location.search
&& hash === location.hash
) {
event.preventDefault();
}
} else {
let { to, location } = this.props;
if (to === location.pathname + location.search + location.hash) {
event.preventDefault();
}
}
// Ensure that if we passed another onClick method as props, it will be called too
if (this.props.onClick) {
this.props.onClick(event);
}
};
render() {
let { onClick, children, staticContext, ...restProps } = this.props;
return (
<ReactLink
onClick={ this.handleClick }
{ ...restProps }
>
{ children }
</ReactLink>
);
}
}
Link.propTypes = propTypes;
export default withRouter(Link);
Another option to solve this problem would be to use a ConditionalWrapper component which renders the <Link> tag based on a condition.
This is the ConditionalWrapper component which I used based on this blog here
https://blog.hackages.io/conditionally-wrap-an-element-in-react-a8b9a47fab2:
const ConditionalWrapper = ({ condition, wrapper, children }) =>
condition ? wrapper(children) : children;
export default ConditionalWrapper
This is how we have used it:
const SearchButton = () => {
const {
searchData,
} = useContext(SearchContext)
const isValid = () => searchData?.search.length > 2
return (<ConditionalWrapper condition={isValid()}
wrapper={children => <Link href={buildUrl(searchData)}>{children}</Link>}>
<a
className={`ml-auto bg-${isValid()
? 'primary'
: 'secondary'} text-white font-filosofia italic text-lg md:text-2xl px-4 md:px-8 pb-1.5`}>{t(
'search')}</a>
</ConditionalWrapper>
)
}
This solution does not render the Link element and avoids also code duplication.
If it fits your design, put a div on top of it, and manipulate the z-index.

Categories

Resources