I'm having a problem with adding overflow hidden to my page in Gatsby with SASS.
can it be done with JS ?
import React, { useState } from "react";
import "./styles.scss";
import Hamburger from "hamburger-react";
import Menu from "../Menu";
export default function TopBar() {
const [isOpen, setOpen] = useState(false);
const toggleMenu = () => {
setOpen(!isOpen);
};
return (
<div className="topBar">
<div className="topbar-container">
<div className="topbar-container__logo">Liza Willow</div>
<span className="topbar-container__menu-btn" onClick={toggleMenu}>
<Hamburger size={29} rounded toggled={isOpen} toggle={setOpen} />
</span>
<Menu isOpen={isOpen} setOpen={setOpen} toggleMenu={toggleMenu} />
</div>
</div>
);
}
You can easily get add a unique class name to the body tag or even apply the styles directly once the menu is open. the code will sit inside the toggleMenu function as it is already used to toggle between the menu states.
I would go with something like this:
const toggleMenu = () => {
// get the body element tag
let body = document.getElementsByTagName("body")[0];
// apply the styles based on menu state
if (!isOpen) body.style.overflow = "hidden";
else body.style.overflow = "auto";
}
Related
For example I have this code.
And I want to use CSS transitionfor Button when showButton and when !showButton. Now it's just removed and add Button when showButton changes.
{showButton && (
<Button
onClick={() => setShowMessage(true)}
size="lg"
>
Show Message
</Button>
)}
Is it possible make by some events or appending classNames like active?
Append the className with the ternary operator.
But, for example, this code will only adjust the class of the button specified (effectively doing the same thing you described, hiding & showing the button):
import React, { useState } from 'react';
export const Component = () => {
const [showButton, setShowButton] = useState(false);
const handleClick = () => {
setShowButton(true);
}
return (
<button
onClick={handleClick}
className={showButton ? 'showButtonClass' : 'hideButtonClass'}
>
Show Message
</button>
);
};
For content to show once the button is clicked, you'll need something like:
import React, { useState } from 'react';
export const Component = () => {
const [showMessage, setShowMessage] = useState(false);
const handleClick = () => {
setShowMessage(true);
}
return (
<div>
<button
onClick={handleClick}
>
Show Message
</button>
{showMessage && <h1>
The message you'll see when clicking!
</h1>}
</div>
);
};
This Footer component creates an array. I'd like to make this array available in a sibling Body component. I'd like to do this by passing the reminders array variable up and over to the Body component by way of their mutual Parent component.
import './style.css'
function Footer({ linkToFooter }){
const [reminder, setReminder] = useState("");
const [reminders, setReminders] = useState([]);
const submitThis = (e) => {
e.preventDefault();
if (reminder){
let newReminder = {
reminder: reminder,
complete: false,
};
setReminders(prevRems => [...prevRems, newReminder])
setReminder('');
} else {
setReminder("oops there's a problem");
}
};
useEffect(
() => { localStorage.setItem('reminders', JSON.stringify(reminders)); },
[reminders]
);
console.log(reminders, "from Footer");
return(
<div className="rm-list-footer">
<form onSubmit={submitThis}>
<input
id='newReminder'
type='text'
value={reminder}
onChange={(e) => setReminder(e.target.value)}
/>
<button onClick={() => linkToFooter(reminders)} type='submit'>+</button>
</form>
</div>
)
}
export default Footer;
This is the Parent function component that I'd like to be the segway between the two child components.
import Header from "./Header";
import Footer from "./Footer";
import Body from "./Body";
function RmList() {
const [reminders, setReminders] = useState([])
return(
<div className="rm-list">
<Header></Header>
<Body reminders={reminders}></Body>
<Footer linkToFooter={setReminders}></Footer>
</div>
);
}
export default RmList;
Finally, my goal is to console.log the array in this sibling Body component every time the button in the Footer component is clicked. The problem is that the array that's logged in the Body is always one element behind what's logged in the Footer with every click.
import React from "react";
function Body({reminders}) {
console.log(reminders, "from Body");
return(
<div className="rm-list-body"></div>
);
}
export default Body;
After researching I thought my problem might have to do with changing this
setReminders([newReminder, ...reminders])
to this
setReminders(prevRems => [...prevRems, newReminder])
but that was to no avail as I can't pass prevRems to the parent.
Your help is kindly appreciated.
If im understanding correctly, you want to be able to set reminders in one child and see reminders in a sibling. if that is correct, reminders as a state should only be in the parent. you can then pass the state and state-setter to the children and access them through props. by re-declaring "reminders" in the child you could have a conflict of state.
also, it is best practice to name your props being passed like this
<Footer setReminders={setReminders}>
so that on larger projects its easy to follow your prop's path
try
import './style.css'
CHANGE >>>> function Footer({ linkToFooter > setReminders }){
const [reminder, setReminder] = useState("");
REMOVE >>>> const [reminders, setReminders] = useState([]);
const submitThis = (e) => {
e.preventDefault();
if (reminder){
let newReminder = {
reminder: reminder,
complete: false,
};
setReminders(prevRems => [...prevRems, newReminder])
setReminder('');
} else {
setReminder("oops there's a problem");
}
};
useEffect(
() => { localStorage.setItem('reminders', JSON.stringify(reminders)); },
[reminders]
);
console.log(reminders, "from Footer");
return(
<div className="rm-list-footer">
<form onSubmit={submitThis}>
<input
id='newReminder'
type='text'
value={reminder}
onChange={(e) => setReminder(e.target.value)}
/>
CHANGE >>>> <button onClick={() => linkToFooter > setReminders(reminders)} type='submit'>+</button>
</form>
</div>
)
}
export default Footer;
let me know if that has helped guide you in the right direction. and hopefully these little tricks will help you keep your code more readable. Good Luck!
So, i have a menu that is opening when i hover over it, but i want it to close when my mouse is outside the menu or the sub-menu items (i got it to close when hovering away from the menu, but then it closes before i can actually use it). Here's the code:
import React from 'react'
import styles from './Dropdown.module.css'
const Dropdown = ({children, className, style, value }) => {
const [active, setActive] = React.useState(false)
const [i, setI] = React.useState()
const anchorRef = React.useRef()
React.useEffect(() => {
anchorRef.current.removeAttribute('href')
},[])
React.useEffect(() => {
if (active) {
setI(styles.active)
}else{
setI(null)
}
},[active])
const stylesDropdown = [styles.li, className]
//the function
function handleActive() {
setActive(!active)
}
return <li className={stylesDropdown.join(' ')} style={style}
//when it's being called
onMouseOver={handleActive} >
<a href='/' className={styles.a} ref={anchorRef}>{value}</a>
<span className={styles.span}>
<i className={`fa-solid fa-sort-down ${i}`}></i>
</span>
<ul className={styles.ul}>
{active && children}
</ul>
</li>
}
export default Dropdown
nvm, solved it. i changed the function from !active to true and on the end of the ul i used this onMouseLeave ={() => setActive(false)
I want to focus input with type file, after user clicked on button, and input was shown. So i have this situation:
function TaskAttachments({ icon, label, taskId }) {
const inputRef = useRef()
const onInputShown = () => {
inputRef.current.focus()
}
return (
<TaskNavElement icon={icon} label={label} cbOnShow={onInputShown}>
<input ref={inputRef} type="file" />
</TaskNavElement>
)
}
Notice, that TaskNavElement has a child, and when it's rendered it's triggering onInputShown, so I'm having correct inputRef.
But seems to using current.focus is not working. What's wrong?
UPDATE
So here is TaskNavElement code:
import React, { useEffect, useRef } from 'react'
import Icon from '../../../../../../../components/UI/Icon/Icon'
import { usePopUp } from '../../../../../../../hooks/usePopUp'
function TaskNavElement({ icon, label, children, cbOnShow }) {
const navElementRef = useRef()
const containerRef = useRef()
const [showPopUp] = usePopUp(navElementRef, containerRef, true)
useEffect(() => {
if (showPopUp) cbOnShow()
}, [showPopUp])
return (
<li className="NavElements-Item">
<div ref={navElementRef}>
<Icon name={icon} size="12" helperClass="Item-Icon" />
<span className="Item-Label">{label}</span>
</div>
{showPopUp && <div ref={containerRef}>{children}</div>}
</li>
)
}
export default TaskNavElement
I'm using this wrapper in case that I have this elements like a nav in some component, and on click on TaskNavElement I'm rendering a child, that's are a popups, but now I need to have ability to upload file, on TaskNavElement clicked.
I am working with a form in react, and what I would like is that when I click a button, I add a new component which is just an input to the screen. It all mostly works, as planned. The issue is with the following: the layout is that I have one main component, which then displays a child component. That child component is called from a map of a useState. (More after code snippet)
This is the code of the main component:
import React, { useState } from "react";
import SingleProfile from "./individual_profile";
const ProfileInformation = (props) => {
console.log("proflie render");
const [ProfilesBoolean, setProfilesBoolean] = useState(false);
const [profiles, setProfiles] = useState(props.Data['profiles'])
const FieldAdd = (event)=>{
event.preventDefault();
const copy = profiles;
copy.push({Network:'',url:''})
return(copy)
}
function CreateInput(){
return profiles.map((data, index) =><SingleProfile index={index} data={data} />)
}
const accordion = (event) => {
const NextElement = event.target.nextElementSibling;
if (!event.target.className.includes("display")) {
NextElement.style.maxHeight = NextElement.scrollHeight + "px";
} else {
NextElement.style.maxHeight = 0;
}
};
return (
<div className="AccordionItem">
<div
className={
ProfilesBoolean ? "AccordionHeader-display" : "AccordionHeader"
}
onClick={(e) => setProfilesBoolean(!ProfilesBoolean)}
id="ProfileForm"
>
Profiles
</div>
<div className="AccordionContent">
<div className="AccordionBody">
{
profiles.map((data, index) => (
<SingleProfile index={index} data={data} />
))
}
<button id="ProfileAdd" onClick={(e) => {setProfiles(FieldAdd(e))}}>
Add a profile
</button>
</div>
</div>
</div>
);
};
export default ProfileInformation;
When I click the button and onClick fires FieldAdd() the useState updates, with a new empty object as expected. However, it does not appear inside my <div className="AccordionBody"> as I would expect it to.
The following code is used to display components, by opening and closing the child div. When it is open is when you see the child components and the add button. If I click the div, to close and then click again to re-open it, the new child component appears.
<div
className={ProfilesBoolean ? "AccordionHeader-display" : "AccordionHeader"}
onClick={(e) => setProfilesBoolean(!ProfilesBoolean)}
id="ProfileForm"
>
Profiles
</div>;
Is it possible to have the child component appear without having to close and re-open the div?
Your clickHandler FieldAdd is incorrect. You are mutating the state directly which will not cause re-render.
use setProfiles to update the state in the clickHandler. Like this
const FieldAdd = (event)=>{
setProfiles(prev => [...prev, {Network:'',url:''}])
}
Trigger the onClick like this
<button id="ProfileAdd" onClick={(e) => {FieldAdd(e)}}>
Add a profile
</button>
...