React-hooks handling mutliple buttons state on click - javascript

I have 5 buttons in my app which I would like to change the background color based on button states, so now when I click one button it affects all the buttons (toggle-class), not only that I need to change the button color but also I need to hide and show data for each button, so I am using condition rendering, the default tab is social media. so, for example, u click button 1 it changes the background color and it shows div withe information, etc
Here is what I have so far
import React, { useState, useEffect, useRef } from 'react';
function Mata() {
const [isBlack, setIsBlack] = useState(0);
const [tab, setTab] = useState('socialmedia');
const handleBtn1 = (e) =>{
e.preventDefault();
setIsBlack(!isBlack);
setTab('data1);
}
const handleBtn2 = (e) =>{
e.preventDefault();
setIsBlack(!isBlack);
setTab('data2');
}
const handleBtn3 = (e) =>{
e.preventDefault();
setIsBlack(!isBlack);
setTab('data3');
}
const handleBtn4 = (e) =>{
e.preventDefault();
setIsBlack(!isBlack);
setTab('data4');
}
const handleBtn5 = (e) =>{
e.preventDefault();
setIsBlack(!isBlack);
setTab('data5');
}
return (
<div className="container">
<button style={{ backgroundColor: isBlack ? '#262626' : '#F3F3F3'}} className={`btn1 ${isBlack && activeTab}`} onClick={handleBtn1}>btn1</button>
<button style={{ backgroundColor: isBlack ? '#262626' : '#F3F3F3'}} className={`btn2 ${isBlack && activeTab}`} onClick={handleBtn2}>btn2</button>
<button style={{ backgroundColor: isBlack ? '#262626' : '#F3F3F3'}} className={`btn3 ${isBlack && activeTab}`} onClick={handleBtn3}>btn3</button>
<button style={{ backgroundColor: isBlack ? '#262626' : '#F3F3F3'}} className={`btn4 ${isBlack && activeTab}`} onClick={handleBtn4}>btn4</button>
<button style={{ backgroundColor: isBlack ? '#262626' : '#F3F3F3'}} className={`btn5 ${isBlack && activeTab}`} onClick={handleBtn5}>btn5</button>
{tab === 'socialmedia' && <>
....data
</div>
{tab === 'data1' && <>
....data
</div>
............
..........
</div>
)
}
export default Mata
What do I need to change to get this working?

You need individual state for each button. I suggest using a map to store a button id and a boolean value for whether it is "black" or not, i.e. the click handler simply toggles a boolean value. I don't know if it was a typo in copy/pasting code to SO, but the react state needs to be declared in in the functional component body.
const [isBlack, setIsBlack] = useState({});
You can also use a single click handler by converting it to a curried callback, taking and enclosing in scope the button id. This uses a functional state update to shallowly copy existing state and updates the value of the enclosed button id.
const handleBtn = btnId => e => {
e.preventDefault();
setIsBlack(state => ({
...state,
[btnId]: !state[btnId],
}));
};
Complete code
function Mata() {
const [activeTab, setActiveTab] = useState("activeTab");
const [isBlack, setIsBlack] = useState({});
const handleBtn = btnId => e => {
e.preventDefault();
setIsBlack(state => ({
...state,
[btnId]: !state[btnId]
}));
};
return (
<div className="container">
<button
style={{ backgroundColor: isBlack["btn1"] ? "#262626" : "#F3F3F3" }}
className={`btn1 ${isBlack["btn1"] && activeTab}`}
onClick={handleBtn("btn1")}
>
btn1
</button>
<button
style={{ backgroundColor: isBlack["btn2"] ? "#262626" : "#F3F3F3" }}
className={`btn2 ${isBlack["btn2"] && activeTab}`}
onClick={handleBtn("btn2")}
>
btn2
</button>
<button
style={{ backgroundColor: isBlack["btn3"] ? "#262626" : "#F3F3F3" }}
className={`btn3 ${isBlack["btn3"] && activeTab}`}
onClick={handleBtn("btn3")}
>
btn3
</button>
<button
style={{ backgroundColor: isBlack["btn4"] ? "#262626" : "#F3F3F3" }}
className={`btn4 ${isBlack["btn4"] && activeTab}`}
onClick={handleBtn("btn4")}
>
btn4
</button>
<button
style={{ backgroundColor: isBlack["btn5"] ? "#262626" : "#F3F3F3" }}
className={`btn5 ${isBlack["btn5"] && activeTab}`}
onClick={handleBtn("btn5")}
>
btn5
</button>
</div>
);
}
There is a lot of repeated code, so a more DRY version where active tab and buttons are passed as props.
function Mata({ activeTab = '', buttons }) {
const [isBlack, setIsBlack] = useState({});
const handleBtn = btnId => e => {
e.preventDefault();
setIsBlack(state => ({
...state,
[btnId]: !state[btnId]
}));
};
return (
<div className="container">
{buttons.map(btn => (
<button
style={{ backgroundColor: isBlack[btn] ? "#262626" : "#F3F3F3" }}
className={`btn1 ${isBlack[btn] && activeTab}`}
onClick={handleBtn(btn)}
>
{btn}
</button>
))}
</div>
);
}
Used as such
const buttons = ["btn1", "btn2", "btn3", "btn4", "btn5"];
...
<Mata buttons={buttons} />
Edit
Seems you are really creating a "tab manager". I suggest lofting state to the parent and converting Mata to a "dumb" component that simply renders the "tab" buttons. Takes 3 props: an active tab index, array of buttons, and a state update callback.
function Mata({ activeTab = -1, buttons, setActiveTab }) {
return (
<div className="container">
{buttons.map((btn, i) => {
const isActive = i === activeTab;
return (
<button
key={btn.id}
style={{ backgroundColor: isActive ? "#262626" : "#F3F3F3" }}
className={`${btn.id} ${isActive && activeTab}`}
onClick={() => setActiveTab(i)}
>
{btn.id}
</button>
);
})}
</div>
);
}
Example tabs data
const tabs = [
{ id: "btn1", data: "data1" },
{ id: "btn2", data: "data2" },
{ id: "btn3", data: "data3" },
{ id: "btn4", data: "data4" },
{ id: "btn5", data: "data5" }
];
Example usage
<Mata activeTab={activeTab} buttons={tabs} setActiveTab={setActiveTab} />
{activeTab === -1 ? (
<div>Social Media</div>
) : (
<div>{tabs[activeTab].data}</div>
)}
Adding "Icons"
Similar to Choosing the Type at Runtime
If SVG icons are not already react components, wrap them into a simple functional component
const Icon1 = () => <svg>...</svg>;
Add an icon field to the tabs data and set the value to the icon component
const tabs = [
{ id: "btn1", data: "data1", icon: Icon1 },
{ id: "btn2", data: "data2", icon: Icon2 },
{ id: "btn3", data: "data3", icon: Icon3 },
{ id: "btn4", data: "data4", icon: Icon4 },
{ id: "btn5", data: "data5", icon: Icon5 }
];
And destructure and rename to render
function Mata({ activeTab = -1, buttons, setActiveTab }) {
return (
<div className="container">
{buttons.map((btn, i) => {
const isActive = i === activeTab;
const { icon: Icon, id } = btn; // <-- rename icon -> Icon
return (
<button
key={id}
style={{ backgroundColor: isActive ? "#262626" : "#F3F3F3" }}
className={`${id} ${isActive && activeTab}`}
onClick={() => setActiveTab(i)}
>
<Icon /> {id} // <-- render icon component
</button>
);
})}
</div>
);
}

Why are you doing this
const [isBlack, setIsBlack] = useState(0);
instead of doing this ?
const [isBlack, setIsBlack] = useState(false);
Also to make use of useState you have to edit your code like the following, as hooks can only be called inside of the body of a function component.
import React, { useState, useEffect, useRef } from "react";
function Mata() {
const [isBlack, setIsBlack] = useState(false); // correction here
const handleBtn1 = e => {
e.preventDefault();
setIsBlack(!isBlack);
};
const handleBtn2 = e => {
e.preventDefault();
setIsBlack(!isBlack);
};
const handleBtn3 = e => {
e.preventDefault();
setIsBlack(!isBlack);
};
const handleBtn4 = e => {
e.preventDefault();
setIsBlack(!isBlack);
};
const handleBtn5 = e => {
e.preventDefault();
setIsBlack(!isBlack);
};
return (
<div className="container">
<button
style={{ backgroundColor: isBlack ? "#262626" : "#F3F3F3" }}
className={`btn1 ${isBlack && activeTab}`}
onClick={handleBtn1}
>
btn1
</button>
<button
style={{ backgroundColor: isBlack ? "#262626" : "#F3F3F3" }}
className={`btn2 ${isBlack && activeTab}`}
onClick={handleBtn2}
>
btn2
</button>
<button
style={{ backgroundColor: isBlack ? "#262626" : "#F3F3F3" }}
className={`btn3 ${isBlack && activeTab}`}
onClick={handleBtn3}
>
btn3
</button>
<button
style={{ backgroundColor: isBlack ? "#262626" : "#F3F3F3" }}
className={`btn4 ${isBlack && activeTab}`}
onClick={handleBtn4}
>
btn4
</button>
<button
style={{ backgroundColor: isBlack ? "#262626" : "#F3F3F3" }}
className={`btn5 ${isBlack && activeTab}`}
onClick={handleBtn5}
>
btn5
</button>
</div>
);
}
export default Mata;

Related

hide button from specific pages

I have a project that contains three pages, the first one owns the index number “0”, the second one owns the index number “1” and the third one owns the index number “2”.
And I have a Next button, and I have a Preview button, and I used a dynamic method to have a single button that just changes its address according to the address passed,
And I want to hide the "previous" button from the first page, and I want to hide the "Next" button from the last page.
how can i solve the problem ?
import { Button } from '#chakra-ui/button';
import React from 'react';
export const StepperButton: React.FC<{ title: string, num: number; onClick: (...args: any) => void }> = ({ title, num, onClick }) => {
const [show, setShow] = React.useState(false);
const disabledButton = () => {
if (title === 'Previous' && num === 0) {
return true;
}
}
const hideButton = () => {
if (title === 'Next' && num === 2 || title === 'Previous' && num === 0) {
return false;
}
}
return <>
<Button
style={{
width: '244.71px',
height: '41.41px',
backgroundColor: '#FF8C1E',
borderRadius: '8px',
fontWeight: '600',
fontSize: '14px',
lineHeight: '21px',
color: '#FFFFFF',
textTransform: 'uppercase'
}}
isDisabled={disabledButton()}
// onClick={()=>{ onClick; onOpen; }}
onClick={() => { onClick(); hideButton(); }}
>
{title}</Button>
</>
}
You can use ternary operator in return. Like that.
return <>
{ (num === 0 && title === "Previous") || (num === 2 && title === "Next")
?
""
:
(
<Button
style={{
width: '244.71px',
height: '41.41px',
backgroundColor: '#FF8C1E',
borderRadius: '8px',
fontWeight: '600',
fontSize: '14px',
lineHeight: '21px',
color: '#FFFFFF',
textTransform: 'uppercase'
}}
isDisabled={disabledButton()}
// onClick={()=>{ onClick; onOpen; }}
onClick={() => { onClick(); hideButton(); }}
>
{title}</Button>
)
}
</>

File upload with react and typescript

I am trying to build a simple single select upload component. On click of a button that has a hidden input field, I open the file dialog then I select a file. The placeholder changes to the file name and then a change and clear button. Everything works fine, but on click of clear the file dialog, I dont want it to open on clear. It should open when "Choose file to upload" and "Change" is clicked. Can someone help?.
I am using material UI for the same
Sandbox: https://codesandbox.io/s/react-hook-upload-oxqdp2?file=/src/Upload.tsx:0-1784
import * as React from "react";
import { Button } from "#material-ui/core";
import { useState } from "react";
interface UploaderProps {
fileType?: string | AcceptedFileType[];
}
enum AcceptedFileType {
Text = ".txt",
Gif = ".gif",
Jpeg = ".jpg",
Png = ".png",
Doc = ".doc",
Pdf = ".pdf",
AllImages = "image/*",
AllVideos = "video/*",
AllAudios = "audio/*"
}
export const Upload = (props: UploaderProps) => {
const { fileType } = props;
const acceptedFormats: string | AcceptedFileType[] =
typeof fileType === "string"
? fileType
: Array.isArray(fileType)
? fileType?.join(",")
: AcceptedFileType.Text;
const [selectedFiles, setSelectedFiles] = useState<File | undefined>(
undefined
);
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedFiles(event?.target?.files?.[0]);
};
const onUpload = () => {
console.log(selectedFiles);
};
return (
<>
<Button
variant="contained"
component="label"
style={{ textTransform: "none" }}
>
<input
hidden
type="file"
accept={acceptedFormats}
onChange={handleFileSelect}
/>
{!selectedFiles?.name && <span> Choose file to upload</span>}
{selectedFiles?.name && (
<>
<span style={{ float: "left" }}> {selectedFiles?.name}</span>
<span style={{ padding: "10px" }}> Change</span>
<span onClick={() => setSelectedFiles(undefined)}>Clear</span>
</>
)}
</Button>
<Button
color="primary"
disabled={!selectedFiles}
style={{ textTransform: "none" }}
onClick={onUpload}
>
Upload
</Button>
</>
);
};
You should prevent default behavior of event. It worked for me like this:
<span onClick={(e) => { e.preventDefault(); setSelectedFiles(undefined); }}>Clear</span>
I would use useRef hook to refer to the hidden input field, something like this for example:
import * as React from 'react';
import Button from '#mui/material/Button';
const AcceptedFileType = {
Text: '.txt',
Gif: '.gif',
Jpeg: '.jpg',
Png: '.png',
Doc: '.doc',
Pdf: '.pdf',
AllImages: 'image/*',
AllVideos: 'video/*',
AllAudios: 'audio/*',
};
export default function Upload({ fileType }) {
const fileRef = React.useRef();
const acceptedFormats =
typeof fileType === 'string'
? fileType
: Array.isArray(fileType)
? fileType?.join(',')
: AcceptedFileType.Text;
const [selectedFiles, setSelectedFiles] = React.useState();
const handleFileSelect = (event) => {
setSelectedFiles(event?.target?.files?.[0]);
};
const onUpload = () => {
console.log(selectedFiles);
};
const onClear = () => {
setSelectedFiles(undefined);
};
const onUpdate = (event) => {
if (event.target.textContent.trim().toLowerCase() === 'change') {
onClear();
fileRef.current.click();
return;
}
if (event.target.textContent.trim().toLowerCase() === 'clear') {
onClear();
return;
}
};
return (
<>
<input
ref={fileRef}
hidden
type="file"
accept={acceptedFormats}
onChange={handleFileSelect}
/>
{!selectedFiles?.name && (
<Button
variant="contained"
component="label"
style={{ textTransform: 'none' }}
onClick={() => fileRef.current?.click()}
>
Choose file to upload
</Button>
)}
{selectedFiles?.name && (
<Button
variant="contained"
component="label"
style={{ textTransform: 'none' }}
onClick={onUpdate}
>
<span style={{ float: 'left' }}> {selectedFiles?.name}</span>
<span style={{ padding: '10px' }}> Change</span>
<span>Clear</span>
</Button>
)}
<Button
color="primary"
disabled={!selectedFiles}
style={{ textTransform: 'none' }}
onClick={onUpload}
>
Upload
</Button>
</>
);
}
See working demo: https://stackblitz.com/edit/react-dmzlsq?file=demo.js

How to prevent re-render child component when dispatch redux in it to render parent component?

I have child component and it has a button to dispatch redux a variable to use in parent component.
But I want to still open child component when I click on the button.
Child Component
const ServoActionFill = (e, jammerSelected) => {
dispatch({
type: "HIGHLIGHT_TARGET",
data: {
uniqueId: "546546644",
},
});
}
return <div>
{showPopup == true && target != null && subPopupServo == true &&
<div className="position-fixed d-flex" style={{ top: info.containerPoint.y - 130, left: info.containerPoint.x - 130, zIndex: 1000 }} onMouseLeave={() => disp("servo")}>
<PieMenu
radius="130px"
centerRadius="70px"
centerX={info.containerPoint.x}
centerY={info.containerPoint.y}
>
{/* Contents */}
{jammerList.activeJammers.map(x =>
//StatusName.forEach(y =>
// y == x.latinName ?
<Slice onMouseOver={(e) => ServoActionFill(e, x)} backgroundColor="#6b22f3"><div style={{ fontSize: "11px", fontWeight: "bold" }}>{x.latinName}</div></Slice>
)}
</PieMenu>
</div>
}
Parent Component
const handlePropChange = (prevProps, nextProps) => {
let returnState = true;
// filter targets based on types
if (nextProps.HIGHLIGHT_TARGET) {
filterTargetsBasedOnTypes(nextProps.targetTypeHiddenInfo);
}
};
const LeafletMap = React.memo(({ highlightedTarget }) => {
const [viewport, setViewport] = React.useState(DEFAULT_VIEWPORT)
const { props: { mapUrl } } = useTheme();
return (
<>
<TextField
classes={{ root: "target-search" }}
placeholder="Search Target"
title="searching target based on its id"
variant="filled"
type="search"
onChange={searchTargetHandler}
onKeyPress={(e) => {
if (e.key === 'Enter')
searchTargetBtnHandler()
}}
InputProps={{
classes: {
input: "py-2 text-black non-opacity",
},
endAdornment: <IconButton
className="p-0"
onClick={searchTargetBtnHandler}
>
<AiOutlineSearch />
</IconButton>
}}
/>
</>
);
}, handlePropChange);
const mapStateToProps = state => {
return {
highlightedTarget: state.BaseReducers.highlightedTarget,
};
};
export default connect(mapStateToProps)(LeafletMap);
When dispatch fire in Child component (Highlight_Target), Parent re-render by redux. But I want hold open Child component. Any solution?

React Event Handling For Each Icon

How can I make handleclick operation unique for each icon? For example when click to plus icon, color of all changes to green. But I want only plus icon to change into green.
const [isActive, setIsActive] = useState(false);
const handleClick = () => {
setIsActive((current) => !current);
};
return (
<div className="list-icons">
<FaPlus
className="plus-icon"
style={{
color: isActive ? "green" : "",
}}
onClick={handleClick}
/>
<FaCheck
className="check-icon"
style={{
color: isActive ? "green" : "",
}}
onClick={handleClick}
/>
<FaHeart
className="heart-icon"
style={{
color: isActive ? "green" : "",
}}
onClick={handleClick}
/>
</div>
)
The best way to do that is that you should create an array of object for your icons.
For example
const [icons, setIcons] = useState([
{
id: 1,
icon: FaPlus,
className:"plus-icon",
isActive: false,
},
{
id: 2,
icon: FaCheck,
className:"check-icon",
isActive: false,
},
{
id: 3,
icon: FaHeart,
className:"heart-icon",
isActive: false,
},
]);
const handleClick = (id: number) => {
const newIcons = icons.map((icon) => {
if(icon.id === id) {
return {
...icon,
isActive: !icon.isActive,
}
}
return icon;
});
setIcons(newIcons);
};
return (
<div className="list-icons">
{icons.map((icon) => {
const Icon = icon.icon
return (
(
<Icon
className={icon.className}
style={{
color: icon.isActive ? "green" : "",
}}
onClick={() => handleClick(icon.id}
/>
)
})
</div>
)
You should use an array of boolean:
const [isActive, setIsActive] = useState([false, false, false]);
const handleClick = (index) => {
setIsActive((current) => {
current[index] = !current[index];
return current;
});
};
return (
<div className="list-icons">
<FaPlus
className="plus-icon"
style={{
color: isActive[0] ? "green" : "",
}}
onClick={() => handleClick(0)}
/>
<FaCheck
className="check-icon"
style={{
color: isActive[1] ? "green" : "",
}}
onClick={() => handleClick(1)}
/>
<FaHeart
className="heart-icon"
style={{
color: isActive[2] ? "green" : "",
}}
onClick={() => handleClick(2)}
/>
</div>
)
const icons = ['plus', 'check', 'heart'];
const components = [<FaPlus />, <FaCheck />, <FaHeart />];
const [activeIcons, setActiveIcons] = useState(icons.map(() => false));
const onActiveToggle = (index) => {
setActiveIcons(prev => {
prev[index] = !prev[index];
return prev;
});
}
const iconProps = useMemo(() => {
return icons.map((icon, index) => ({
className: `${icon}-icon`,
style: {{ color: activeIcons[index] ? 'green': 'black' }},
onClick: () => onActiveToggle(index)
})
}, [activeIcons]);
return (
<>
{components.map((Component, index) => (
<Component {...iconProps[index]}/>
))}
</>
);

Component rerendering only after double click

I have a parent component that is passing products down into a subcomponent as state along with the product's filters. For some reason I have to double click the "filters" in order for the parent component to rerender with the filtered products. I understand because it is running asynchronously it is not updating the state immediately, but how can I force the update and rerender to run as soon as I add a filter without using forceUpdate? Is this where redux would come in to play?
Parent component
const [products, setProducts] = React.useState(data.pageContext.data);
const handleCount = () => {
setCount(count + 24);
}
return (
<div style={{width: "100%"}}>
<Header/>
<div style={{display: "flex", flexDirection: "row", justifyContent: "center"}}>
<Sidebar
products={products}
setProducts={setProducts}
baseProducts={data.pageContext.data}
/>
<div style={{display: "flex", flexDirection: "column"}}>
<h1 style={{width: "50%"}}>Cast vinyl</h1>
<h3>Product Count: {products.length}</h3>
<ProductList>
{products.slice(0, count).map(product => {
return (
<a href={`/vinyl/${product.data.sku}`}><div>
{product.data.field_product_image.length > 0 ?
<ProductImage images={data.data.allFiles} sku={`${product.data.sku}`}/> :
<StaticImage src="http://stagingsupply.htm-mbs.com/sites/default/files/default_images/drupalcommerce.png" width={250} alt=""/>}
<h3>{product.data.title}</h3>
<h5>{product.data.sku}</h5>
</div></a>
)
})}
</ProductList>
<h3 onClick={handleCount}>Load more</h3>
</div>
</div>
</div>
)
Child Component
const Sidebar = ({ setProducts, baseProducts }) => {
const [filters, setFilters] = React.useState([]);
const [click, setClick] = React.useState(false);
const handleClick = () => {
setClick(!click);
}
const onChange = (e) => {
if (!filters.includes(e)) {
setFilters([...filters, e])
}
if (filters.length > 0) {
const filteredProducts = baseProducts.filter(product => filters.includes(product.data.field_product_roll_size));
setProducts(filteredProducts);
}
}
const clearFilters = () => {
setFilters([]);
setProducts(baseProducts);
setClick(false);
}
const rollSize = [...new Set(baseProducts.map(fields => fields.data.field_product_roll_size))]
return (
<SidebarContainer>
<h3>Mbs Sign Supply</h3>
<ul>Sub Categories</ul>
<li>Calendered Vinyl</li>
<li>Cast Vinyl</li>
<h3>Filters</h3>
{filters.length > 0 ? <button onClick={clearFilters}>Clear Filters</button> : null}
<li onClick={() => handleClick()}>Roll Size</li>
{/*map through roll size array*/}
{/*each size has an onclick function that filters the products array*/}
{click ? rollSize.sort().map(size => {
return (
<span style={{display: "flex", flexDirection: "row", alignItems: "center", height: "30px"}}>
<Checkbox onClick={() => {onChange(size)}} />
<p >{size}</p>
</span>
)
}) : null}
<li>Width</li>
demo can be found at http://gatsby.htm-mbs.com:8000/cast-vinyl, clicking "Roll Size" from the left and then double clicking a filter
Thanks in advance
All I needed was a little useEffect
React.useEffect(() => {
if (filters.length > 0) {
const filteredProducts = baseProducts.filter(product => filters.includes(product.data.field_product_roll_size));
setProducts(filteredProducts);
}
}, [filters]);

Categories