How can I toggle a class and change the CSS in Nextjs? - reactjs

I'm working on a Next.js project where the menu opens with a <p> toggling the menu class. I managed to do this, but when I add the class in the CSS it doesn't take into account both classes.
This is my code:
Component
import { useState } from "react";
import styles from "../styles/modules/header.module.scss";
export default function Component() {
const [isModal, setIsModal] = useState(false);
return (
<div>
<p onClick={() => setIsModal(!isModal)}>Menu</p>
<div className={`${isModal && "nav-open"} ${styles.ModalContainer}`}>
Content
</div>
</div>
);
}
SCSS
.ModalContainer {
position: absolute;
left: -100vw;
&.nav-open {
left: 0;
}
}
When I inspect the code I can see that it adds the class when I click on the menu button, but can't see the expected changes. Does someone have a solution?

You need to use the class from your scoped Sass module file, in this case styles["nav-open"]. Simply setting "nav-open" will refer to a global class, which probably doesn't exist.
export default function Component() {
const [isModal, setIsModal] = useState(false);
const contentClassname = isModal
? `${styles["nav-open"]} ${styles.ModalContainer}`
: styles.ModalContainer;
return (
<div>
<p onClick={() => setIsModal(!isModal)}>Menu</p>
<div className={contentClassname}>Content</div>
</div>
);
}

Related

React how to add class to parent element when child element is clicked

This is my code.
export default MainContent = () => {
handleClick = (e) => {
// This is where I got confused
};
return (
<>
<div>
<div onClick={handleClick}>1</div>
</div>
<div>
<div onClick={handleClick}>2</div>
</div>
<div>
<div onClick={handleClick}>3</div>
</div>
</>
);
}
What I want is to add a class to parent div when child element is clicked. I couldn't use useState() since I only need one element to update. Couldn't use setAttribute since it changes the same element. Is there any solution for that?
I take it you want to apply the class only to direct parent of clicked child.
create a state to oversee different clicked child div
apply the class only to direct parent of clicked* child div based on the state
make use of clsx npm package (since we don't wanna overwrite parent div styling)
you may see the working examples here: https://codesandbox.io/s/happy-babbage-3eczt
import { useState } from "react";
import "./styles.css";
import styled from "styled-components";
import classnames from "clsx";
export default function App() {
const [styling, setstyling] = useState({
status: false,
from: "",
style: ""
});
function handleClick(childNo) {
setstyling({ status: true, from: childNo, style: "applyBgColor" });
}
return (
<div className="App">
<Styling>
<div
className={
styling?.status && styling?.from == "child-1"
? classnames("indentText", styling?.style)
: "indentText"
}
>
<div
className="whenHoverPointer"
onClick={() => handleClick(`child-1`)}
>1</div>
</div>
<div
className={
styling?.status && styling?.from == "child-2"
? styling?.style
: ""
}
>
<div
className="whenHoverPointer"
onClick={() => handleClick(`child-2`)}
>2</div>
</div>
</Styling>
</div>
);
}
const Styling = styled.div`
.indentText {
font-style: italic;
}
.applyBgColor {
background-color: grey;
}
.whenHoverPointer {
cursor: pointer;
}
`;
function Item({ children }) {
const [checked, isChecked] = useState(false);
const onClick = () => isChecked(true);
return (
<div {...(isChecked && { className: 'foo' })}>
<button type="button" onClick={onClick}>{children}</button>
</div>
);
}
function MainContent() {
return [1, 2, 3].map(n => <Item key={n}>{n}</Item>);
}
I think theirs something wrong, useState and JSX will update related part, react will handling that itself, but base on logic, may you need to prevent re-render to stop issue, for example, here on handleClick, will keep re-render in each state since function will re-addressed in memory each update..
any way, base on your question, you can do that by this:
const handleClick = useCallback((e) => {
e.target.parentElement.classList.add('yourClass')
}, []);
But I believe its a Bad solution.
What I recommend is solve issue by state to keep your react life cycle is fully work and listen to any update, also you can use ref to your wrapper div and add class by ref.

How can i style specific button with css modules?

I am using css modules in react because i want to have css scoped to specific component.I have Person.js and App.js file. I want only the button in the Person.js to have background color of red.
So i do
Person.js
import styles from './person.module.css';
const Person = () => {
return (
<div>
<button className={styles.button}>Person compo button</button>
</div>
)
}
export default Person;
person.module.css
.button {
background-color: red;
}
App.js
import './App.css';
import Person from './Person';
function App() {
return (
<div className="App">
<button className="button">App compo button</button>
<Person></Person>
</div>
);
}
export default App;
and now only the - Person compo button is with background color red. But i am using css class here.
What if i want to style the button in the Person.js file without using the class
so i will have in person.module.css only button styling
button {
background-color: red;
}
how can i make now the button in my Person.js file to take this style from the person.module.css ?
One way would be to attach a class to the Person div:
const Person = () => {
return (
<div className={styles.person}>
<button>Person compo button</button>
</div>
)
}
And in your person.module.css, reference all the buttons inside the .person class:
.person button {
background-color: red;
}

React backgroundImage doesn't consistently work

When my component is used inside another component, the backgroundImage inline style works as expected. However when used inside an react-multi-carousel the inline styles are no longer working.
Dev tools shows that the styles are in effect, just not visible on the screen for some reason.
The backgroundImage is a url, however hardcoded images also do not work.
Any help would be greatly appreciated.
import { Product } from "../../types";
interface IMasonryPostProps {
product: Product;
showOverlay?: boolean;
}
export default function MasonryPost({
product,
showOverlay = true,
}: IMasonryPostProps) {
const backgroundImage = {
backgroundImage: `url('${product.image}')`,
color: "red",
};
const style = { ...backgroundImage, ...product.style };
const overlay = showOverlay ? "overlay" : "";
console.log(style);
return (
<a
className={`masonry-post ${overlay}`}
href={product.image}
style={style}
>
<div className="product-text">
<div className="category-container">{product.category}</div>
<div>
<h2 className="product-name">{product.title}</h2>
<span className="product-price">{product.price}</span>
</div>
</div>
</a>
);
}

React useMeasure not working with nextJS?

I'm currently trying to animate a div so that it slides from bottom to top inside a card.
The useMeasure hook is supposed to give me the height of the wrapper through the handler I attached to it : <div className="desc-wrapper" {...bind}>
Then I am supposed to set the top offset of an absolutely positionned div to the height of its parent and update this value to animate it.
The problem is that when logging the bounds returned by the useMeasure() hook, all the values are at zero...
Here is a link to production exemple of the panel not being slided down because detected height of parent is 0 : https://next-portfolio-41pk0s1nc.vercel.app/page-projects
The card component is called Project, here is the code :
import React, { useEffect, useState } from 'react'
import './project.scss'
import useMeasure from 'react-use-measure';
import { useSpring, animated } from "react-spring";
const Project = (projectData, key) => {
const { project } = projectData
const [open, toggle] = useState(false)
const [bind, bounds] = useMeasure()
const props = useSpring({ top: open ? 0 : bounds.height })
useEffect(() => {
console.log(bounds)
})
return (
<div className="project-container">
<div className="img-wrapper" style={{ background: `url('${project.illustrationPath}') no-
repeat center`, backgroundSize: project.portrait ? 'contain' : 'cover' }}>
</div>
<div className="desc-wrapper" {...bind} >
<h2 className="titre">{project.projectName}</h2>
<span className="description">{project.description}</span>
<animated.div className="tags-wrapper" style={{ top: props.top }}>
</animated.div>
</div>
</div>
);
};
export default Project;
Is this a design issue from nextJS or am I doing something wrong ? Thanks
I never used react-use-measure, but in the documentations, the first item in the array is a ref and you are suppose to use it this way.
function App() {
const [ref, bounds] = useMeasure()
// consider that knowing bounds is only possible *after* the view renders
// so you'll get zero values on the first run and be informed later
return <div ref={ref} />
}
You did...
<div className="desc-wrapper" {...bind} >
Which I don't think is correct...

How to create reusable custom modal component in React?

I have a problem with the concept of modals in React. When using server side rendered templates with jQuery I was used to have one empty global modal template always available (included in base template that was always extended). Then when making AJAX call I just populated modal..something like this:
$('.modal-global-content').html(content);
$('.modal-global').show();
So how do I make this concept in React?
There are a few ways of doing this. The first involves passing in the modal state from a parent component. Here's how to do this - first with the parent App.js component:
// App.js
import React from "react";
import Modal from "./Modal";
const App = () => {
const [showModal, updateShowModal] = React.useState(false);
const toggleModal = () => updateShowModal(state => !state);
return (
<div>
<h1>Not a modal</h1>
<button onClick={toggleModal}>Show Modal</button>
<Modal canShow={showModal} updateModalState={toggleModal} />
</div>
);
}
export default App;
And here's the Modal.js child component that will render the modal:
// Modal.js
import React from "react";
const modalStyles = {
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
background: "blue"
};
const Modal = ({ canShow, updateModalState }) => {
if (canShow) {
return (
<div style={modalStyles}>
<h1>I'm a Modal!</h1>
<button onClick={updateModalState}>Hide Me</button>
</div>
);
}
return null;
};
export default Modal;
This way is perfectly fine, but it can get a bit repetitive if you're reusing the modal in many places throughout your app. So instead, I would recommend using the context API.
Define a context object for your modal state, create a provider near the top of your application, then whenever you have a child component that needs to render the modal, you can render a consumer of the modal context. This way you can easily nest your modal deeper in your component tree without having to pass callbacks all the way down. Here's how to do this - first by creating a context.js file:
// context.js
import React from "react";
export const ModalContext = React.createContext();
Now the updated App.js file:
// App.js
import React from "react";
import { ModalContext } from "./context";
import Modal from "./Modal";
const App = () => {
const [showModal, updateShowModal] = React.useState(false);
const toggleModal = () => updateShowModal(state => !state);
return (
<ModalContext.Provider value={{ showModal, toggleModal }}>
<div>
<h1>Not a modal</h1>
<button onClick={toggleModal}>Show Modal</button>
<Modal canShow={showModal} updateModalState={toggleModal} />
</div>
</ModalContext.Provider>
);
}
export default App;
And lastly the updated Modal.js file:
// Modal.js
import React from "react";
import { ModalContext } from "./context";
const modalStyles = {
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
background: "blue"
};
const Modal = () => {
return (
<ModalContext.Consumer>
{context => {
if (context.showModal) {
return (
<div style={modalStyles}>
<h1>I'm a Modal!</h1>
<button onClick={context.toggleModal}>Hide Me</button>
</div>
);
}
return null;
}}
</ModalContext.Consumer>
);
};
export default Modal;
Here's a Codesandbox link with a working version using context. I hope this helps!
One way you can solve this problem by using css and JSX.
this is the app and i can have anything like a button a link anything
Lets assume we have a link (react-router-dom) which redirects us to
a DeletePage
The Delete Page renders a Modal
You will provide the title and the actions of the Modal as props
const App = () => {
return(
<Link to="/something/someid">SomeAction</Link>
)
}
const DeletePage = () => {
return(
<Modal
title="Are you sure you want to delete this"
dismiss={() => history.replace("/")}
action={() => console.log("deleted") }
/>
)
}
Modal
const Modal = (props) => {
return(
<div>
<div className="background" onClick={props.dismiss}/>
<h1>{props.title}</h1>
<button onClick={props.dismiss}>Cancel</button>
<button onClick={props.action}>Delete</button>
</div>
)
}
set the z-index of the modal a high number
position: fixed of the modal component
when the user will click on the background the model will go away (
many ways to implement that like with modal state, redirect, etc i
have taken the redirect as one of the ways )
cancel button also has the same onClick function which is to dismiss
Delete button has the action function passed through props
this method has a flaw because of css because if your parent component
has a position property of relative then this will break.
The modal will remain inside the parent no matter how high the z-index is
To Save us here comes React-Portal
React portal creates a 'portal' in its own way
The react code you might have will render inside DOM with id of #root ( in most cases )
So to render our Modal as the top most layer we create another
DOM element eg <div id="modal"></div> in the public index.html file
The Modal react component code will slightly change
const Modal = (props) => {
return ReactDOM.createPortal(
<div>
<div className="background" onClick={props.dismiss}/>
<h1>{props.title}</h1>
<button onClick={props.dismiss}>Cancel</button>
<button onClick={props.action}>Delete</button>
</div>
),document.querySelector("#modal")
}
rest is all the same
Using React-Portal and Modal Generator
I have been toiling my days finding a good, standard way of doing modals in react. Some have suggested using local state modals, some using Modal Context providers and using a function to render a modal window, or using prebuilt ui libraries like ChakraUI that provides it's own Modal component. But using these can be a bit tricky since they tend to overcomplicate a relatively easy concept in web ui.
After searching for a bit, I have made peace with doing it the portal way, since it seems to be the most obvious way to do so. So the idea is, create a reusable modal component that takes children as props and using a local setState conditionally render each modal. That way, every modal related to a page or component is only present in that respective component.
Bonus:
For creating similar modals that uses the same design, you can use a jsx generator function that takes few colors and other properties as its arguments.
Working code:
// Generate modals for different types
// All use the same design
// IMPORTANT: Tailwind cannot deduce partial class names sent as arguments, and
// removes them from final bundle, safe to use inline styling
const _generateModal = (
initialTitle: string,
image: string,
buttonColor: string,
bgColor: string = "white",
textColor: string = "rgb(55 65 81)",
buttonText: string = "Continue"
) => {
return ({ title = initialTitle, text, isOpen, onClose }: Props) => {
if (!isOpen) return null;
return ReactDom.createPortal(
<div className="fixed inset-0 bg-black bg-opacity-80">
<div className="flex h-full flex-col items-center justify-center">
<div
className="relative flex h-1/2 w-1/2 flex-col items-center justify-evenly rounded-xl lg:w-1/4"
style={{ color: textColor, backgroundColor: bgColor }}
>
<RxCross2
className="absolute top-0 right-0 mr-5 mt-5 cursor-pointer text-2xl"
onClick={() => onClose()}
/>
<h1 className="text-center text-3xl font-thin">{title}</h1>
<h3 className="text-center text-xl font-light tracking-wider opacity-80">
{text}
</h3>
<img
src={image}
alt="modal image"
className="hidden w-1/6 lg:block lg:w-1/4"
/>
<button
onClick={() => onClose()}
className="rounded-full px-16 py-2 text-xl text-white"
style={{ backgroundColor: buttonColor }}
>
{buttonText}
</button>
</div>
</div>
</div>,
document.getElementById("modal-root") as HTMLElement
);
};
};
export const SuccessModal = _generateModal(
"Success!",
checkimg,
"rgb(21 128 61)" // green-700
);
export const InfoModal = _generateModal(
"Hey there!",
infoimg,
"rgb(59 130 246)" // blue-500
);
export const ErrorModal = _generateModal(
"Face-plant!",
errorimg,
"rgb(190 18 60)", // rose-700
"rgb(225 29 72)", // rose-600
"rgb(229 231 235)", // gray-200
"Try Again"
);

Resources