Conditional link styling React - javascript

I want my nav bar to style the page title I'm in, im using React and Tailwind CSS, for example, just make the title yellow when im on the selected path.
My logic to achieve that would be this but isn't working:
<div className={active ? "text-yellow-400" : undefined}
My rout code:
const LinkItem = ({href, path, children, ...props}) => {
const active = path === href
return (
<NextLink href={href}>
<div className={active ? "text-yellow-400" : undefined}
{...props}
>
{children}
</div>
</NextLink>
)
}
Nav bar code:
const Navbar = props => {
const {path} = props
return (
<LinkItem href="/page1" path={path}>
Page 1
</LinkItem>
)
}

Instead of undefined use either null or a empty string ""
Aswell. Use useState (not really needed in this scenario, but its always best to use in practice)
https://reactjs.org/docs/hooks-state.html

Well at the end the problem was the path variable which was undefined, and wasn't able to match href, so the condition never met.
Solution: call the path from useRouter hook with parameter .asPath, this gives me back the parameter which i stored in my path variable.
Code:
import NextLink from 'next/link'
import {useRouter} from "next/router";
const LinkItem = ({href, children, ...props}) => {
const path = useRouter().asPath
const active = path === href
return (
<NextLink href={href}>
<div className={active ? "<styles here>" : "<styles here>"}
{...props}
>
{children}
</div>
</NextLink>
)
}
Nav bar code:
const Navbar = props => {
const {path} = props
return (
<LinkItem href="/page1" path={path}>
Page 1
</LinkItem>
<LinkItem href="/page2" path={path}>
Page 2
</LinkItem>
)
}

Related

NextJS: sidebar routing - currently clicked link

I recently asked this question, and found help to make my sidebar links respond by displaying a component in the main div of my page.
Now I want to figure out how to use this where I have more than one option in my sidebar. I added library and tasks, but when I click library - it reveals the library component in the main div (I want this), but when I then click tasks, it reveals both library and tasks components. I only want the tasks component to show in that event.
What is the js command to setState to false if the link is not the most recently clicked link?
function Sidebar({ toggleLibrary, toggleTasks }) {
// const { me, loading } = useMe()
return (
<Stack spacing="1" >
<NavButton label="Tasks" fontWeight="normal" onClick={toggleTasks} />
<NavButton label="Deals" fontWeight="normal" onClick={toggleLibrary}/>
</Stack>
)
}
const DashBase = () => {
const isDesktop = useBreakpointValue({ base: false, lg: true })
// the menu component should initially be hidden
const [showLibrary, setShowLibrary] = React.useState(false)
const [showTask, setShowTask] = React.useState(false)
// state setter to switch between our `true` and `false` states
const toggleLibrary = () => setShowLibrary(!showLibrary)
const toggleTasks = () => setShowTask(!showTask)
const router = useRouter()
return (
<Flex
>
{isDesktop ? <Sidebar toggleLibrary={toggleLibrary} toggleTasks={toggleTasks} /> : <Navbar />}
<Container py="8" flex="none">
{showLibrary ? <Library /> : '' }
{showTask ? <Tasks /> : '' }
Currently the code has an independent state for each item you want to toggle. While you could enqueue state updates to toggle all other states false when a new item is toggled true, this is rather unmaintainable code.
If the goal is to only ever render a single active item then I'd suggest just using a single active state and set the value in a single callback passed to the Sidebar component.
Example:
const DashBase = () => {
const isDesktop = useBreakpointValue({ base: false, lg: true });
// nothing initially is active
const [showActive, setShowActive] = React.useState(null);
// state setter to switch between our `null` and "active" id states
const toggleActive = (key) => setShowActive(
active => active === key ? null : key
);
const router = useRouter()
return (
<Flex>
{isDesktop ? <Sidebar toggleActive={toggleActive} /> : <Navbar />}
<Container py="8" flex="none">
{showActive === 'library' && <Library />}
{showActive === 'tasks' && <Tasks />}
...
...
function Sidebar({ toggleActive }) {
return (
<Stack spacing="1" >
<NavButton
label="Tasks"
fontWeight="normal"
onClick={() => toggleActive("tasks")}
/>
<NavButton
label="Deals"
fontWeight="normal"
onClick={() => toggleActive("library")}
/>
</Stack>
);
}

page crash error because of the delay in data

I am facing an issue and i think a lot but not able to find any solution please help Note: I have removed some html code because i know the issue was not there So first let explain what the issue is i am desctructuring loading and product which take time to resolve initially when i first created it was not giving error loading become true and the loading component render as soon as the loading become false i can access the variable product and then render the data from it..but before some time i just add a feature of login and i have not even touch this page what happening now is until the value of product and loading is resolved the value of both variables is undefined i find it using console.log() using
import React,{useEffect} from "react";
import { getSingleProduct } from '../action/productAction'
import { useSelector,useDispatch } from 'react-redux'
import { Link } from "react-router-dom";
import { change_img } from "./main";
import { useParams } from "react-router-dom";
const ProductPage = () => {
const dispatch = useDispatch();
const {product,loading} = useSelector(state => state.productDetail)
let { id } = useParams();
console.log("The data of the product is",product)
console.log("The value of the laoding ",loading)
useEffect(()=>{
dispatch(getSingleProduct(id));
},[dispatch,id]);
var iloading =true;
return (
<>
{loading ? (
<h1>Loading...</h1>
) : (
<div>
<div className="card">
<div className="row no-gutters">
<aside className="col-md-6">
<h2 className="title">{product.name}</h2>
<div className="mb-3">
<var className="price h4">Price: $ {product.price}</var>
</div>
<p>
{product.description}
</p>
</main>
</div>
</div>
</div>
)}
</>
);
};
export default ProductPage;
Looks like you are not formating the JSX in the return part Try this simplest form after imports :
const ProductPage = () => {
const dispatch = useDispatch()
const { product, loading } = useSelector((state) => state.productDetail)
let { id } = useParams()
useEffect(() => {
dispatch(getSingleProduct(id))
}, [dispatch, id])
return (
<>
{loading ? (
<h1>Loading...</h1>
) : (
<>
{' '}
<h2 className="title">{product.name}</h2>
</>
)}
</>
)
}
export default ProductPage
If that doesn't work then it means there is a problem in the getSingleProduct or in the related reducer, If gives you the product name then means your code is not formatted correctly. Try to fix this then.
Edit: Also, I have noticed there is no handling if the server does not give the data or if loading and product are undefined then your component will also crash, You can handle this like :
<>
{loading ? (
<h1>Loading...</h1>
) : product ? (
<>
{' '}
<h2 className="title">{product.name}</h2>
</>
) : (
<> No data from Server</>
)}
</>

React - toggle text and class in an HTML element?

I am trying to create a system where I can easily click a given sentence on the page and have it toggle to a different sentence with a different color upon click. I am new to react native and trying to figure out the best way to handle it. So far I have been able to get a toggle working but having trouble figuring out how to change the class as everything is getting handled within a single div.
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<div className="textline" onClick={() => setStatus(!status)}>
{`${status ? 'state 1' : 'state 2'}`}
</div>
);
};
How can I make state 1 and state 2 into separate return statements that return separate texts + classes but toggle back and forth?
you can just create a component for it, create a state to track of toggle state and receive style of text as prop
in React code sandbox : https://codesandbox.io/s/confident-rain-e4zyd?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function ToggleText({ text1, text2, className1, className2 }) {
const [state, toggle] = useState(true);
const className = `initial-style ${state ? className1 : className2}`;
return (
<div className={className} onClick={() => toggle(!state)}>
{state ? text1 : text2}
</div>
);
}
in React-Native codesandbox : https://codesandbox.io/s/eloquent-cerf-k3eb0?file=/src/ToggleText.js:0-465
import React, { useState } from "react";
import { Text, View } from "react-native";
import styles from "./style";
export default function ToggleText({ text1, text2, style1, style2 }) {
const [state, toggle] = useState(true);
return (
<View style={styles.container}>
<Text
style={[styles.initialTextStyle, state ? style1 : style2]}
onPress={() => toggle(!state)}
>
{state ? text1 : text2}
</Text>
</View>
);
}
This should be something you're looking for:
import React from "react"
const Sentence = ({ className, displayValue, setStatus }) => {
return (
<div
className={className}
onClick={() => setStatus((prevState) => !prevState)}
>
{displayValue}
</div>
);
};
const ButtonExample = () => {
const [status, setStatus] = React.useState(false);
return status ? (
<Sentence
className="textLine"
displayValue="state 1"
setStatus={setStatus}
/>
) : (
<Sentence
className="textLineTwo"
displayValue="state 2"
setStatus={setStatus}
/>
);
};
You have a Sentence component that takes in three props. One for a different className, one for a different value to be displayed and each will need access to the function that will be changing the status state. Each setter from a hook also has access to a function call, where you can get the previous (current) state value, so you don't need to pass in the current state value.
Sandbox

Search bar stopping props from changing

On my site, the <ArticleList> is supposed to update when one navigates between columns. This works when you go from the home page to a column, or from an article to a column. But if you go from column to column, it doesn't work. The page doesn't update at all, but the url changes. The links to each column stay the same, as they are part of the <Layout> component, which every page has.
Edit
I figured out now that I can just use <a> and omit <Link> entirely, but this would slow down the page navigation.
Edit 2
This is part of my <Layout> component where I render the links to the columns:
<nav className={layout.columnContainer}>
{columns.map(({ id, name }) =>
this.props.currentColumn ? (
<a key={id} href={`/columns/${name}`}>
{name}
</a>
) : (
<Link key={id} href="/columns/[name]" as={`/columns/${name}`}>
<a>{name}</a>
</Link>
),
)}
</nav>
Edit 3
My minimal reproducible example is on GitHub, and I get the same unexpected results.
Edit 4
I found that the reason it wasn't working was I implemented a search bar that put the children prop in a state and modified this.
Constructor:
constructor(props) {
super(props);
this.searchArticlesKeyType = this.searchArticlesKeyType.bind(this);
this.state = {displayedMain: props.children};
}
Inside the render method are the column links (nav) and the problematic search input element.
<nav className={layout.columnContainer}>
{
columns.map(({id, name}) => (
<Link key={id} href="/columns/[name]" as={`/columns/${name}`}><a>{name}</a></Link>
))
}
</nav>
<div className={layout.search}>
<input type="search" name="q" onKeyUp={this.searchArticlesKeyType} />
</div>
async searchArticlesKeyType(e) {
// Some code
this.setState({
displayedMain: <ArticleList articles={JSON.stringify(filteredArticles)}/>
// More code
});
}
I think your main issue here is the way you're implementing the search feature, you don't want to store components in state instead you need to pass the search text to the articlelist component and do the filtering there.
There are several ways to implement communication between 2 unrelated components, it could be via context, redux, or even make a portal in the layout to render the seach input from the column component, but in this case I think the best option is to store the search text in the url:
First make the input event update the url using next/router, your layout will look like this:
import { useRouter } from 'next/router'
...
function Layout(props) {
const {columns} = props;
const { push, asPath, query } = useRouter()
const searchArticlesKeyType = (e) => {
const q = e.target.value;
const [url] = asPath.split('?');
push(`${url}?q=${q}`, undefined, { shallow: true });
}
return (
<div>
...
<div>
<input type="search" name="q" defaultValue={query.q} onKeyUp={searchArticlesKeyType} />
</div>
...
</div>
)
}
And then you do the filtering in articlelist component
import Link from "next/link";
import { useRouter } from 'next/router'
export default function ArticleList(props) {
const { query } = useRouter();
const q = query.q || "";
const filteredArticles = props.articles.filter(
(item) => item.title.includes(q) || item.body.includes(q)
);
return (
<ul className="grid">
{filteredArticles.map((item) => (
<div key={item.id}>
<Link
key={item.id}
href="/articles/[title]"
as={`/articles/${item.title}`}
>
<a>
<p>
<strong>{item.title}</strong>
</p>
<p>{item.body.substring(0, 100)}</p>
</a>
</Link>
</div>
))}
</ul>
);
}

How to Change a css property based on a state of another component

I'm building a web page with gatsby which is based in react, and I need my nav component changes his sticky position to relative or auto, every time that I open the modal of the gallery component..but I don't know how to approach and solve the problem. The nav component belongs to the layout component which is Gallery's parent component...Here are the components involved:
nav component:
import React, { Component } from 'react'
import { Location } from '#reach/router'
import { Link } from 'gatsby'
import { Menu, X } from 'react-feather'
import Logo from './Logo'
import './Nav.css'
export class Navigation extends Component {
state = {
active: false,
activeSubNav: false,
currentPath: false
}
componentDidMount = () =>
this.setState({ currentPath: this.props.location.pathname })
handleMenuToggle = () => this.setState({ active: !this.state.active })
// Only close nav if it is open
handleLinkClick = () => this.state.active && this.handleMenuToggle()
toggleSubNav = subNav =>
this.setState({
activeSubNav: this.state.activeSubNav === subNav ? false : subNav
})
render() {
const { active } = this.state,
{ subNav } = this.props,
NavLink = ({ to, className, children, ...props }) => (
<Link
to={to}
className={`NavLink ${
to === this.state.currentPath ? 'active' : ''
} ${className}`}
onClick={this.handleLinkClick}
{...props}
>
{children}
</Link>
)
return (
<nav className={`Nav ${active ? 'Nav-active' : ''}`}>
<div className="Nav--Container container">
<Link to="/" onClick={this.handleLinkClick}>
<div style={{ width: `40px`, margin:`0 20px`}}>
<Logo />
</div>
</Link>
<div className="Nav--Links">
<NavLink to="/">Home</NavLink>
<NavLink to="/contact/">Contacto</NavLink>
<div className={`Nav--Group ${this.state.activeSubNav === 'about' ? 'active' : '' }`} >
<span className={`NavLink Nav--GroupParent ${
this.props.location.pathname.includes('about') ||
this.props.location.pathname.includes('team') ||
this.props.location.pathname.includes('news')
? 'active'
: ''
}`}
onClick={() => this.toggleSubNav('about')}
>
Nosotros
</span>
<div className="Nav--GroupLinks">
{subNav.map( (link, index)=> (
<NavLink
to={link.link}
key={'posts-subnav-link-' + index}
className="Nav--GroupLink">{link.name}</NavLink>
))}
</div>
</div>
</div>
<button
className="Button-blank Nav--MenuButton"
onClick={this.handleMenuToggle}
>
{active ? <X /> : <Menu />}
</button>
</div>
</nav>
)
}
}
export default ({ subNav }) => (
<Location>{route => <Navigation subNav={subNav} {...route} />}</Location>
)
the default position property is set to sticky in the nav.css file I want remove that and change it
dynamically depending of the modal gallery state, open or close.
this is my gallery component:
import React, { useState, useCallback } from "react";
import Gallery from "react-photo-gallery";
import Carousel, { Modal, ModalGateway } from "react-images";
const PhotoGallery = ({photos}) => {
const [currentImage, setCurrentImage] = useState(0);
const [viewerIsOpen, setViewerIsOpen] = useState(false);
const openLightbox = useCallback((event, { photo, index }) => {
setCurrentImage(index);
setViewerIsOpen(true);
}, []);
const closeLightbox = () => {
setCurrentImage(0);
setViewerIsOpen(false);
};
return(
<div>
<Gallery photos={photos} onClick={openLightbox} />
<ModalGateway>
{viewerIsOpen ? (
<Modal onClose={closeLightbox}>
<Carousel
currentIndex={currentImage}
views={photos.map(x => ({
...x,
srcset: x.srcSet,
caption: x.title
}))}
/>
</Modal>
) : null}
</ModalGateway>
</div>
)
}
export default PhotoGallery
the problem is that when the modal is open the nav still sticky and does not allow me access to the modal controls, like close and expand...and I need to change that.
There are a few approaches to this.
Old school classname toggling
Pass a prop down to the child component that reflects the state. On the child, use that prop to conditionally render one or more classes that represent the desired presentation.
Assign styles via style prop
This is similar to #1, but eliminates a layer of abstraction. Instead of assembling a class list you just assemble the CSS styles you'd like to apply as an object.
const Component = ({ someState }) =>
<div style={someState ? { border: "5px solid red" } : { color: "#999" }}>
Some Text
</div>
Use a CSS-in-JS library
The downside of the above approach is that you wind up duplicating styles for each instance of your element on the page. CSS-in-JS libraries solve this by extracting your styles into an automatically generated class and applying the class to your component instead. I prefer Emotion, but there are others.
Using Emotion you're able to accept a className prop from the parent that override the defaults set by the child. This inversion-of-control is really powerful and solves many of the shortcomings with early CSS-in-JS approaches.
const ParentComponent = () => {
const [someState] = useState(false)
return <ChildComponent css={{ color: someState ? "blue" : "red" }} />
}
const ChildComponent = ({ className }) =>
<div
css={{
color: "#000",
border: "4px solid currentColor"
}}
className={className}
>
Some Text
</div>
In the above example, className is assigned by Emotion using the generated class name assigned based on the css prop passed to ChildComponent inside of ParentComponent. The result of this would be a div with a blue border and blue text when someState is false (default). When someState is switched to true, the border and text will be red. This is because the styles passed in via className will override the styles assigned directly via css in Emotion.

Categories