Toggle functionality in React - javascript

So at the beginning I know this code is messy. I have a div that I wanna copy, when I click on the div, it opens up a Content and it copies that div. The thing I can't seem to accomplish is this, In that copied version of div/Image, I want to have a toggle functionality as in a regular one. If you don't fully understand it here is the sandbox https://codesandbox.io/s/epic-bassi-ogzjq. Thank you
const data= [
{
id:1,
img:"./images/icons/folder.png",
name:"Small Projects"
}
]
function App() {
const [isProjectOpen, setIsProjectOpen] = useState(false)
const project= useRef(null)
const [smallProject, setSmallProject] =useState()
const [active, setActive] = useState()
const closeProject = () =>{
setIsProjectOpen(false)
}
const proj = data.find(projects => projects.id===smallProject)
const handleProject=(e)=>{
if(project.current && !project.current.contains(e.target)){
closeProject()
}
}
useEffect(()=>{
document.addEventListener('click', handleProject)
return() =>{
document.removeEventListener('click', handleProject)
}
},[])
return (
<>
{data.map((projects)=>{
return(
<>
{active === project.id && (
<div
onClick={()=>setIsProjectOpen(!isProjectOpen)}
className='smallProject__container'
key={projects.id}
ref={project} >
<img
onClick={()=>setSmallProject(projects.id)}
className="smallProject__img"
src={projects.img}/>
<h4
onClick={()=>setSmallProject(projects.id)}
>{projects.name} </h4>
{isProjectOpen ? <SmallProject /> :null}
</div>)}
</>
)
})}
{smallProject && (
<div
onClick={()=>setIsProjectOpen(!isProjectOpen)}
className={"smallProjects__container " + ((isProjectOpen) ? "active": "")} >
<img
classsName='copy__img'
src={proj.img}/>
</div>
)}
</>
);
}
export default App;

Related

How to modify react button "More"?

I have the following React component:
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const onClickButtonChange = () => {
let cardMore = document.querySelector(".card_more");
let cardMain = document.querySelector(".card_main");
cardMore.style.display = "block";
cardMain.style.display = "none";
};
return (
<div>
{data ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
<button onClick={onClickButtonChange}>More</button>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
My implementation of the More button needs to display additional features (the card_more block). Right now this function only works on the very first element. I understand that in React this can most likely be done more correctly, but I don’t know how, so I use CSS styles.
P.S Edited:
I tried to use React features, maybe someone can tell me or does it make sense?
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
const [show, setShow] = useState(false);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const handleMore = async () => {
if (show === true) {
setShow(false);
} else if (show === false || !data) {
const r = await fetch(url);
const newData = await r.json();
setData(newData);
setShow(true);
}
};
return (
<div>
{data && show ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
<button onClick={handleMore}>More</button>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
Youre right, this isn't the way you should do it in React. But your problem in your onClickButtonChange-Function is that youre only getting one element with document.querySelector(".card_more") and everytime you call it you get the same element back (No matter on which card you call it)
What you need to do is: Identify the single component elements. Thats most likely solved by passing a id/key value down via props and then putting this id on a parent-element (e.g. div.card) and you give it an id:
<div className="card card_main" id={props.keyvalue}>
....
</div>
And then in your onClickButtonChange-Function you call:
let cardMore = document.querySelector(`#${props.keyvalue} .card_more`);
...
This should give you the right element.

useState in multiple variants loop not working - React with NextJs

I have multiple variants in my one component, and I want that if I click on any variant it should add active class to it and removes active class from another. But I am stuck how it can happen using state in multiple variants loop.
Here is my code:
import { useState } from "react";
const Variants = () => {
// Sample Variants Object - But in real it's coming from Wordpress back-end GraphQL
const vats = {
"Tech": ['3G', '5G'],
"Color": ['Red', 'Gray']
}
const [selectedTech, techToChange] = useState(null);
return (
<div>
{vats && (
<div>
{Object.keys(vats).map((key, value) => (
<div key={key}>
<div><b>{key}</b></div>
{vats[key].map((val) => (
<div key={val} onClick={() => techToChange('active')}>
<label
className="cursor-pointer bg-yellow-100"
>
{val} - {selectedTech}
</label>
</div>
))}
<hr/>
</div>
))}
</div>
)}
<hr />
</div>
)
}
You can see in my code, there are 2 variants named Tech & Color, For example; If I click on 3G of tech, it should add active class to it and removes active class from 5G & If I click on Red it should add active class to it and removes active class from Gray. Can someone please help me to do it? I am stuck
You're just setting selectedTech to 'active'–this is just a string and doesn't create any sort of relationship between the tech clicked and the selectedTech in state.
To fix this, you need to set the selectedTech to the actual val of the one you clicked. To add the variant separation you want, the state can mimic the shape of your variants and be an object. So instead of setting selectedTech directly, you can set selectedTech[variant] to the value you clicked.
And then, with a little evaluation, you can print out the string, active when you click on one.
import { useState } from "react";
export default () => {
// Sample Variants Object - But in real it's coming from Wordpress back-end GraphQL
const vats = {
Tech: ["3G", "5G"],
Color: ["Red", "Gray"]
};
const initialState = Object.fromEntries(Object.keys(vats).map((key)=> [key, null])); const [selectedTech, techToChange] = useState(initialState);
return (
<div>
{vats && (
<div>
{Object.keys(vats).map((key, value) => (
<div key={key}>
<div>
<b>{key}</b>
</div>
{vats[key].map((val) => (
<div key={val} onClick={() => techToChange((c) => ({...c, [key]: val}))}>
<label className="cursor-pointer bg-yellow-100">
{val} - {selectedTech[key] === val ? "active" : null}
</label>
</div>
))}
<hr />
</div>
))}
</div>
)}
<hr />
</div>
);
};
CodeSandbox: https://codesandbox.io/s/inspiring-kare-hsphp?file=/src/App.js
useMap from react-use would fit nicely in to your existing code:
const [vats, {set, setAll, remove, reset}] = useMap({
"Tech": ['3G', '5G'],
"Color": ['Red', 'Gray']
}
...
{Object.keys(vats).map((key, value) => (
...
<div key={val} onClick={() => set(key, val)}>
Here is the return code. You need to pass the val to set the state of selectedTech state variable and then compare the actual value with it to have the active class set.
const [selectedTech, techToChange] = useState([]);
const selectOps = (key,val) => {
let existing = selectedTech;
let idx = existing.findIndex(i => i.key===key);
console.log(idx);
if(idx > -1){
existing.splice(idx,1);
console.log(idx, existing);
}
techToChange([...existing, {key,val}]);
}
return (
<div>
{vats && (
<div>
{Object.keys(vats).map((key, value) => (
<div key={key}>
<div><b>{key}</b></div>
{vats[key].map((val) => (
<div key={val} onClick={() => selectOps(key,val)}>
<label
className={`cursor-pointer bg-yellow-100 ${selectedTech.some(s => s.val===val) ? 'active' : undefined}`}
>
{val}
</label>
</div>
))}
<hr/>
</div>
))}
</div>
)}
<hr />
</div>
)
This is an example of using useRef and some other stuff that you might find useful:
import React, { useState, useRef, useEffect} from "react";
const Variants = () => {
// Sample Variants Object - But in real it's coming from Wordpress back-
end GraphQL
const vats = {
"Tech": ['3G', '5G'],
"Color": ['Red', 'Gray']
}
const labelRef = useRef();
const [selectedTech, techToChange] = useState("");
const [selectedColor, colorToChange] = useState("");
useEffect(()=>{
console.log(selectedColor);
if(labelRef.current.innerHTML.trim() === selectedColor) {
labelRef.current.className ="other class";
}
else {
labelRef.current.className ="cursor-pointer bg-yellow-100";
}
},
[selectedColor, labelRef])
useEffect(()=>{
console.log(selectedTech);
if(labelRef.current.innerHTML.trim() === selectedTech) {
labelRef.current.className ="other class";
}
else {
labelRef.current.className ="cursor-pointer bg-yellow-100";
}
},
[selectedTech, labelRef])
return (
<div>
{vats && (
<div>
{Object.keys(vats).map((key, value) => (
<div key={key}>
<div><b>{key}</b></div>
{vats[key].map((val) => (
<div key={val} onClick={(e) => {
if(key==="Tech") {
//console.log(e.target.innerHTML);
techToChange(e.target.innerHTML.trim());
labelRef.current = e.target;
//console.log(val.trim(),selectedTech.trim());
}
else if(key==="Color")
{
colorToChange(e.target.innerHTML.trim())
labelRef.current = e.target;
}
}
}>
<label ref={labelRef}
className="cursor-pointer bg-yellow-100"
>{val}
</label>
</div>
))}
<hr/>
</div>
))}
</div>
)}
<hr />
</div>
)
}
export default Variants;

Warning: Expected server HTML to contain a matching nav in div

I am using next.js and I get the following error: "Warning: Expected server HTML to contain a matching nav in div".
Here is my code:
export default function Member() {
const router = useRouter();
const {
isAuthenticated,
authState,
profileState
} = useContext(AuthContext);
let profileArray = profileState.savedProfile;
const redirect = () => {
if (typeof window !== "undefined") {
router.push("/");
}
};
return (
<>
{!isAuthenticated() ? (
redirect()
) : (
<>
<Navigation />
<main className="main">
{{ /* Code that uses let profileArray */ }}
</main>
</>
)}
</>
);
}
I think the error occurs because of the React.Fragment - because when I replace the React.Fragments with f.ex. div, I get a different error "Warning: Expected server HTML to contain a matching div in div".
I just don't know how to solve this issue. I already tried to use useEffect, but then I get a server error.
Any suggestions? I'm new to next.js, so any help with code improvement is appreciated! Thanks a lot!
EDIT
A similar error occurs on other pages - only the warning differs f.ex. "Warning: Expected server HTML to contain a matching div in button". I think this is related to the AvatarDropdown, which is part of the Navigation. The code:
const AvatarDropdown = () => {
const node = useRef();
const { authState, isAuthenticated, logout } = useContext(AuthContext);
const [dropdownOpen, setDropdownOpen] = useState(false);
const router = useRouter();
const member = () => {
router.push("/member");
};
const dropdownItems = [
{ title: "My Account", onClick: () => member() },
{title: "Log Out", onClick: logout},
];
const handleClick = (e) => {
if (node.current && !node.current.contains(e.target)) {
setDropdownOpen(false);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClick);
return () => {
document.removeEventListener("mousedown", handleClick);
};
}, []);
return (
<div ref={node}>
{isAuthenticated() ? (
<button
onClick={() => setDropdownOpen(!dropdownOpen)}
>
<div>
<p>{authState.name}</p>
<ExpandMoreIcon />
</div>
</button>
) : (
<Link href="/login">
<button>
<p>LOGIN</p>
</button>
</Link>
)}
{dropdownOpen && (
<div>
<DropdownContent dropdownItems={dropdownItems} />
</div>
)}
</div>
);
};
redirect does not return any html node. Any side effect code should be executed in useEffect hook.
This is recommended. Try this..
export default function Member() {
const router = useRouter();
const {
isAuthenticated,
authState,
profileState
} = useContext(AuthContext);
let profileArray = profileState.savedProfile
React.useEffect(()=> {
if(!isAuthenticated()){
router.push('/')
}
},[])
if(!isAuthenticated()){
return <p>Loading..</p>
}
return (
<>
<Navigation / >
<main className = "main">
//code that uses let profileArray
</main>
</>
);
}
U can use Redirect component from 'react-router-dom'.

How to refactor an if else if with previous state when using useState Hook?

I have 2 details tag, each has a control to toggle it on/off. Code snippet here. Clicking Control A should toggle on/off page A, clicking Control B should toggle on/off page B.
I did it with an if else if plus 2 useState, this would not be feasible when there are multiple details. How can I refactor the code such that maybe the if else if can be avoided and it detects which Control I click in a cleverer way?
Page.js
const Page = ({ name, isOpen, setIsOpen }) => {
return (
<>
<details
open={isOpen}
onToggle={(e) => {
setIsOpen(e.target.open);
}}
>
<summary>Page {name} title</summary>
<div>Page {name} contents</div>
</details>
</>
);
};
export default Page;
Control.js
const Control = ({ toggle }) => {
return (
<>
<a onClick={() => toggle("A")} href="#/">
Control A
</a>
<br />
<a onClick={() => toggle("B")} href="#/">
Control B
</a>
</>
);
};
App.js
export default function App() {
const [isOpenA, setIsOpenA] = useState(false);
const [isOpenB, setIsOpenB] = useState(false);
const toggle = (name) => {
if (name === "A") {
setIsOpenA((prevState) => !prevState);
} else if (name === "B") {
setIsOpenB((prevState) => !prevState);
}
};
return (
<div className="App">
<Control toggle={toggle} />
<Page name={"A"} isOpen={isOpenA} setIsOpen={setIsOpenA} />
<Page name={"B"} isOpen={isOpenB} setIsOpen={setIsOpenB} />
</div>
);
}
You can use an array to represent open ones
const [openPages, setOpenPages] = useState([])
And to toggle filter the array
const toggle = (name) => {
if(openPages.includes(name)){
setOpenPages(openPages.filter(o=>o!=name))
}else{
setOpenPages(pages=>{ return [...pages,name]}
}
}
I would personally use an object as a map for your toggles as in something like:
const [isOpen, _setIsOpen] = useState({});
const setIsOpen = (pageName,value) => _setIsOpen({
...isOpen,
[pageName]: value
});
const toggle = (name) => setIsOpen(name, !isOpen[name]);
and then in the template part:
<Page name={"A"} isOpen={isOpen["A"]} setIsOpen={toggle("A")} />
In this way you can have as many toggles you want and use them in any way you want
I think this would be quite cleaner, also you should put the various page names in an array and iterate over them as in
const pageNames = ["A","B"];
{
pageNames.map( name =>
<Page name={name} isOpen={isOpen[name]} setIsOpen={toggle(name)} />)
}
At least that's how I would go about it
Adithya's answer worked for me.
For future reference, I put the full working code here. The onToggle attribute in Page.js is not needed. All required is passing correct true/false to open={isOpen} in Page.js.
App.js:
export default function App() {
const [openPages, setOpenPages] = useState([]);
const toggle = (name) => {
if (openPages.includes(name)) {
setOpenPages(openPages.filter((o) => o !== name));
} else {
setOpenPages((pages) => {
return [...pages, name];
});
}
};
return (
<div className="App">
<Control toggle={toggle} />
<Page name={"A"} isOpen={openPages.includes("A")} />
<Page name={"B"} isOpen={openPages.includes("B")} />
<Page name={"C"} isOpen={openPages.includes("C")} />
</div>
);
}
Page.js
const Page = ({ name, isOpen }) => {
return (
<>
<details open={isOpen}>
<summary>Page {name} title</summary>
<div>Page {name} contents</div>
</details>
</>
);
};
Control.js remains the same.

How to pass HTML attributes to child component in React?

I have a parent and a child component, child component has a button, which I'd like to disable it after the first click. This answer works for me in child component. However the function executed on click now exists in parent component, how could I pass the attribute down to the child component? I tried the following and it didn't work.
Parent:
const Home = () => {
let btnRef = useRef();
const handleBtnClick = () => {
if (btnRef.current) {
btnRef.current.setAttribute("disabled", "disabled");
}
}
return (
<>
<Card btnRef={btnRef} handleBtnClick={handleBtnClick} />
</>
)
}
Child:
const Card = ({btnRef, handleBtnClick}) => {
return (
<div>
<button ref={btnRef} onClick={handleBtnClick}>Click me</button>
</div>
)
}
In general, refs should be used only as a last resort in React. React is declarative by nature, so instead of the parent "making" the child disabled (which is what you are doing with the ref) it should just "say" that the child should be disabled (example below):
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({isDisabled, onButtonClick}) => {
return (
<div>
<button disabled={isDisabled} onClick={onButtonClick}>Click me</button>
</div>
)
}
Actually it works if you fix the typo in prop of Card component. Just rename hadnlBtnClick to handleBtnClick
You don't need to mention each prop/attribute by name as you can use javascript Object Destructuring here.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = (props) => {
return (
<div>
<button {...props}>Click me</button>
</div>
)
}
You can also select a few props and use them differently in the child components. for example, see the text prop below.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card text="I'm a Card" isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({text, ...restProps}) => {
return (
<div>
<button {...restProps}>{text}</button>
</div>
)
}

Categories

Resources