I've been scratching my head over this for ages now.
I am trying to change the background colour of a specific button that is in a nested array.
I have an array of names in pairs that I loop over twice using a map, once to get the pair and once again to get the value. I output and assign the values to a button for each and am displaying the pairs together (E.g. each pair is indexed 0 and 1).
When I click on the button I wish to change only the background colour of the selected button. Currently all the buttons change colour. The issue being is that the state of the buttons effects all of them when I use a boolean to define the selection.
The handler I am using to do this also adds the value of the button to an array to be passed into global state later on as well.
Any help with this would be greatly greatly appreciated as I can't seem to find a way past it. Thanks!
import React, { Component } from "react";
import "../../App.scss";
import { Link } from "react-router-dom";
import Button from "../Button/Button";
class Matches extends Component {
constructor(props) {
super(props);
this.state = {
champ: [],
winningPlayers: [],
selected: false,
};
this.handleAddWinners = this.handleAddWinners.bind(this);
this.handleRound = this.handleRound.bind(this);
}
// Adds winners to a local array which is then sent
// to the global array using the handleNextRound action.
handleAddWinners = (e) => {
const winner = e.target.value;
const { champ } = this.state;
const { round } = this.props;
if (round !== 3) {
this.setState({
selected: !false,
winningPlayers: [...this.state.winningPlayers, winner],
});
} else {
this.setState({ champ: [...champ, winner] });
}
};
handleRound = () => {
const { round, handleNextRound, handleChampion } = this.props;
round !== 3 ? handleNextRound(this.state) : handleChampion(this.state);
this.setState({ winningPlayers: [] });
};
render() {
const { pairs, round, handleClear, roundWinners, champion } = this.props;
const { winningPlayers, selected, champ } = this.state;
const semi = roundWinners[0];
const final = roundWinners[1];
const champName = champion.map((item) => item);
const reset =
round !== 4 ? "block__reset__tournament" : "block__reset__new-game";
const newGame = `${round !== 4 ? "Reset" : "New Game?"}`;
const buttonClick = `${selected ? "selected" : "block__player"}`;
return (
<>
<div classname="container__wrapper">
<div className="container__tournament">
{round === 1 ? (
<section className="block__round ">
{pairs.map((item, index) => (
<div className="pairs" key={index}>
{item.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e)}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : round === 2 ? (
<section className="block__round ">
{semi.map((names, index) => (
<div className="pairs" key={index}>
{names.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e, "value")}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : round === 3 ? (
<section className="block__round ">
{final.map((names, index) => (
<div className="pairs" key={index}>
{names.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e, "value")}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : (
<section className="block__champion">
<p className="champion__greeting">
Congratulations
<br />
<span className="champion__name">{champName}!</span>
<br /> You've won the whole shebang!
</p>
</section>
)}
<Button
buttonClass={`${
round !== 4 ? "block__next-round" : "button__notActive"
}`}
label={`${round !== 3 ? "Next Round" : "See Winner"}`}
handleClick={this.handleRound}
disabled={disabled}
/>
<Link to={"/"} className={reset}>
<Button
buttonClass={reset}
handleClick={handleClear}
label={newGame}
/>
</Link>
</div>
</div>
</>
);
}
}
export default Matches;
This is the component that is handling most of this.
First I would like to say that you should always avoid from using array's index as keys. That is, unless your array is always at the same size and order.
Having said that - what you want to do is to know which button was selected - right?
So you need to store the last button that was selected. Because you don't use any ids anywhere, you can use the index of the pair and the index of the button to know which button was clicked.
Here's an example - I've changed only the round1 and the state code.
import React, { Component } from "react";
import "../../App.scss";
import { Link } from "react-router-dom";
import Button from "../Button/Button";
class Matches extends Component {
constructor(props) {
super(props);
this.state = {
champ: [],
winningPlayers: [],
selected: null,
};
this.handleAddWinners = this.handleAddWinners.bind(this);
this.handleRound = this.handleRound.bind(this);
}
// Adds winners to a local array which is then sent
// to the global array using the handleNextRound action.
handleAddWinners = (e, pairIndex, itemIndex) => {
const winner = e.target.value;
const { champ } = this.state;
const { round } = this.props;
if (round !== 3) {
this.setState({
selected: `${pairIndex}-${itemIndex}`,
winningPlayers: [...this.state.winningPlayers, winner],
});
} else {
this.setState({ champ: [...champ, winner] });
}
};
handleRound = () => {
const { round, handleNextRound, handleChampion } = this.props;
round !== 3 ? handleNextRound(this.state) : handleChampion(this.state);
this.setState({ winningPlayers: [] });
};
render() {
const { pairs, round, handleClear, roundWinners, champion } = this.props;
const { winningPlayers, selected, champ } = this.state;
const semi = roundWinners[0];
const final = roundWinners[1];
const champName = champion.map((item) => item);
const reset =
round !== 4 ? "block__reset__tournament" : "block__reset__new-game";
const newGame = `${round !== 4 ? "Reset" : "New Game?"}`;
const buttonClick = `${selected ? "selected" : "block__player"}`;
return (
<>
<div classname="container__wrapper">
<div className="container__tournament">
{round === 1 ? (
<section className="block__round ">
{pairs.map((item, pairIndex) => (
<div className="pairs" key={pairIndex}>
{item.map((names, itemIndex) => (
<Button
key={itemIndex}
handleClick={(e) => this.handleAddWinners(e, pairIndex, itemIndex)}
label={names}
buttonClass={`${pairIndex}-${itemIndex}` === selected ? '<enterYourBackgroundClass' : buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : round === 2 ? (
<section className="block__round ">
{semi.map((names, index) => (
<div className="pairs" key={index}>
{names.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e, "value")}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : round === 3 ? (
<section className="block__round ">
{final.map((names, index) => (
<div className="pairs" key={index}>
{names.map((names, index) => (
<Button
key={index}
handleClick={(e) => this.handleAddWinners(e, "value")}
label={names}
buttonClass={buttonClick}
value={names}
/>
))}
</div>
))}
</section>
) : (
<section className="block__champion">
<p className="champion__greeting">
Congratulations
<br />
<span className="champion__name">{champName}!</span>
<br /> You've won the whole shebang!
</p>
</section>
)}
<Button
buttonClass={`${
round !== 4 ? "block__next-round" : "button__notActive"
}`}
label={`${round !== 3 ? "Next Round" : "See Winner"}`}
handleClick={this.handleRound}
disabled={disabled}
/>
<Link to={"/"} className={reset}>
<Button
buttonClass={reset}
handleClick={handleClear}
label={newGame}
/>
</Link>
</div>
</div>
</>
);
}
}
export default Matches;
Related
I am currently filtering all my products by their categories in frontend only.
Here is the code in Product.tsx:
import React, { useContext, useState, useEffect } from "react";
import "../styles/Product.css"
import { Link } from 'react-router-dom';
import { AllProductsContext } from "../App";
export const Product = () => {
const allProducts = useContext(AllProductsContext);
const [filters, setFilters] = useState<string[]>([]);
const [checked, setChecked] = useState<string[]>([]);
const handleFilters = (filters: string[]) => {
setFilters(filters);
};
const categories = allProducts?.allProducts.map(product => product.category);
let uniqueCategories = categories?.filter(function (item, pos) {
return categories.indexOf(item) === pos;
})
const handleToggle = (category: string) => {
const currentIndex = checked.indexOf(category);
const newChecked = [...checked];
if (currentIndex === -1) {
newChecked.push(category)
}
else {
newChecked.splice(currentIndex, 1);
}
setChecked(newChecked);
handleFilters(newChecked)
}
useEffect(() => {
console.log(filters);
}, [filters]);
return (
<div className='wrapper--allProducts'>
<div>
{uniqueCategories && uniqueCategories.map((category, index) => (
<div key={index}>
<input
onChange={() => handleToggle(category)}
type="checkbox"
id={category}
name={category}
checked={checked.indexOf(category) === -1 ? false : true}
className="category-inputs"
/>
<label htmlFor={category}>{category}</label>
</div>
))}
</div>
<div className='container--all-product-items'>
{filters.length === 0 && allProducts?.allProducts.map((product, index) => (
<div className='container--product-item' key={index}>
<Link to={`/${product._id}`}><img className='product-image' src={product.pictures[0]} alt={product.name} /></Link>
<br />
<Link to={`/${product._id}`}>{product.name}</Link>
<p><strong>Price:</strong> ${product.price}</p>
<p><strong>Manufacturer:</strong> {product.manufacturer}</p>
<p><strong>Weight:</strong> {product.weight} gr.</p>
<p><strong>Category:</strong> {product.category}</p>
<p className='description'><strong>Description:</strong> {product.description}</p>
</div>
))}
{filters.length > 0 && filters.map((category: string) => (
allProducts?.allProducts.map((product, index) => {
if (product.category === category) {
return (
<div className='container--product-item' key={index}>
<Link to={`/${product._id}`}><img className='product-image' src={product.pictures[0]} alt={product.name} /></Link>
<br />
<Link to={`/${product._id}`}>{product.name}</Link>
<p><strong>Price:</strong> ${product.price}</p>
<p><strong>Manufacturer:</strong> {product.manufacturer}</p>
<p><strong>Weight:</strong> {product.weight} gr.</p>
<p><strong>Category:</strong> {product.category}</p>
<p className='description'><strong>Description:</strong> {product.description}</p>
</div>
)
}
})
))}
</div>
</div>
)
}
I want to change the return input to:
<select>
{uniqueCategories?.map(category => (
<option>{category}</option>
))}
</select>
with all the functionalities instead. However, the option tag does not have the attribute "checked", so how do I move over the handleToggle function to a dropdown list instead of checkbox input?? I.e these attributes to the select/option:
onChange={() => handleToggle(category)}
type="checkbox"
id={category}
name={category}
checked={checked.indexOf(category) === -1 ? false : true}
className="category-inputs"
You have at least two options to solve this problem
Use native option tag with selected attribute
<select id="mySelect">
{myList.map(it => {
const isSelected = // determine here is checked or not
return (<option value={it} selected={isSelected}>3</option>)
})}
</select>
Create your own custom select with checkboxes (or use ui library) like here How to use Checkbox inside Select Option
I have a list of dynamically generated items with information about real estate objects. Every item has a button. When clicked a contact form pops up. However when a button is clicked the event fires on all of the items in the list instead of only on that single item? I solved it with javascript event delegation but that's not the react-way to handle this. What is the best way to do this in react? I'm using React v18, React-Hooks and Redux-Toolkit. Data is fetched from a mongoDB database using Express.
Thanks you!
// Items list with pagination and map component
// Map component also contains the button!
const Content = () => {
const [show, setShow] = useState(false)
const [currentNumber, setCurrentNumber] = useState(1)
const [newList, setNewList] = useState([])
const [errorMessage, setErrorMessage] = useState(false)
const realestates = useSelector(state => state.realestate)
const { loading, realestate, error } = realestates
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchData())
if (realestate) {
setShow(true)
}
if (error) {
setErrorMessage(true)
}
}, [dispatch, error, realestate])
const pageNumbers = []
const resultsPerPage = 4
const pages = Math.ceil(realestate.length / resultsPerPage)
for (let i = 1; i <= pages; i++) {
pageNumbers.push(i)
}
const pagination = (number) => {
setCurrentNumber(number)
}
const slicedList = useCallback(() => {
const data2 = realestate.slice(((currentNumber - 1) * resultsPerPage), (currentNumber * resultsPerPage))
setNewList(data2)
}, [currentNumber, realestate])
useEffect(() => {
slicedList()
}, [slicedList])
return (
<div className="content2">
{errorMessage ? <div>{error}</div> : ""}
//List item
{show ? newList.map(item => {
const { _id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price } = item;
return (
<div className="content2_property" key={_id}>
<div className="content2_castleImageBox"><img src={img} alt="" className="content2_castleImage" /></div>
<div className="content2_info">
<div className="title"><h5>{name}</h5></div>
<div className="location">
<div><span>Region:</span> {region}</div>
<div><span>Departement:</span> {departement}</div>
<div><span>City:</span> {city}</div>
</div>
<div className="icons">
<div className="icon">{bedrooms}<img src={bedroomsIcon} alt="" /></div>
<div className="icon">{bathrooms} <img src={bathroomsIcon} alt="" /></div>
<div className="icon">{livingspace}<img src={livingspaceIcon} alt="" /></div>
<div className="icon">{area}ha<img src={areaIcon} alt="" /></div>
</div>
<div className="price"><span>Price:</span> {item.price === 'Not for Sale' ? price : `$${price},-`}</div>
</div>
<Map region={region} map={map} />
</div>
)
}) : <Loader />}
// Pagination buttons
<div className="btns">
{pageNumbers.map((number, index) => {
return (number === currentNumber) ? <button className="paginationBtn active" onClick={() => pagination(number)} key={index} >{number}</button> :
<button className="paginationBtn" onClick={() => pagination(number)} key={index} >{number}</button>
})}
</div>
</div>
)
}
export default Content
Map component with button
const Map = ({ region, map }) => {
const [showRegionName, setShowRegionName] = useState(false)
const handleMouseOver = () => {
setShowRegionName((prev) => !prev);
}
const makeEnquiry = () => {
//show contact form
}
return (
<div className="mapEnquiryBox">
<div className="map" onMouseEnter={handleMouseOver} onMouseLeave={handleMouseOver}>
<img src={map} alt="map" />
<div className={showRegionName ? "regionName" : "regionName hide"} >{region}</div>
</div>
<button className="enquiry" onClick={makeEnquiry}>Make enquiry</button>
</div>
)
}
export default Map
I think, the issue is with the key in the component.
This is how React differentiated between the components.
This is how I recently made my pagination:
Parent
{pageNumbersArray.map(pageNumber => (
<PaginationButton
key={pageNumber}
active={pageNumber === currentPage}
disabled={false}
onClick={() => handlePageChange(pageNumber)}
title={pageNumber}
/>
))}
Pagination Button
export default function PaginationButton({title, onClick, active, disabled}) {
return (
<button onClick={disabled ? null : onClick}>
<span>
{title}
</span>
</button>
);
}
This might come in handy for you.
Problem solved. I had to split the items list into separate components. One for the list container , one for the list items and one for every single list item. In the single item component I managed the state of that particular item, so when clicking a button the event only fires on that particular item.
Like so:
The list container
<div className="content2">
{errorMessage ? <div>{error}</div> : ""}
<ListItems newList={newList} show={show}/>
<div className="btns">
{pageNumbers.map((number, index) => {
return (number === currentNumber) ? <button className="paginationBtn active" onClick={() => pagination(number)} key={index} >{number}</button> :
<button className="paginationBtn" onClick={() => pagination(number)} key={index} >{number}</button>
})}
</div>
</div>
The list items
const ListItems = ({ newList, show }) => {
return (
<>
{show ? newList.map(item => {
const { _id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price } = item;
return (
<SingleProperty id={_id} area={area} bathrooms={bathrooms} bedrooms={bedrooms} city={city}
departement={departement} region={region} img={img} livingspace={livingspace} map={map} name={name} price={price}/>
)
}) : < Loader />}
</>
)
}
An the single item component
const SingleProperty = ({ id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price}) => {
const [ showForm, setShowForm ] = useState(false)
return (
<div className={!showForm ? "content2_property" : "content2_property enlarge"} key={id}>
<div className="content2_property_castleImageBox"><img src={img} alt="castle" className="content2_property_castleImage" /></div>
<div className="content2_property_info">
<div className="title"><h5>{name}</h5></div>
<div className="location">
<div><span>Region:</span> {region}</div>
<div><span>Departement:</span> {departement}</div>
<div><span>City:</span> {city}</div>
</div>
<div className="icons">
<div className="icon">{bedrooms}<img src={bedroomsIcon} alt="" /></div>
<div className="icon">{bathrooms} <img src={bathroomsIcon} alt="" /></div>
<div className="icon">{livingspace}<img src={livingspaceIcon} alt="" /></div>
<div className="icon">{area}ha<img src={areaIcon} alt="" /></div>
</div>
<div className="price"><span>Price:</span> {price === 'Not for Sale' ? price : `$${price},-`}</div>
</div>
<Map region={region} map={map} setShowForm={setShowForm}/>
</div>
)
}
I have a component A, which displays contents of a component B conditionally. Component B is contains a list of items, and when one clicks one of the items in the list, a new layout is supposed to be fired up showing details of the item. When i try to pass the props to switch to a new layout on a component B list item, i get an error toggleSearchType is not a function . Any assistance or recommendation on what i might be doing wrong will be appreciated.
My index file looks like this :
const PatientSearch: React.FC<PatientSearchProps> = ({ closePanel }) => {
const { t } = useTranslation();
const [searchType, setSearchType] = useState<SearchTypes>(SearchTypes.BASIC);
const toggleSearchType = (searchType: SearchTypes) => {
setSearchType(searchType);
};
return (
<>
<Overlay header={t('addPatientToList', 'Add patient to list')} closePanel={closePanel}>
<div className="omrs-main-content">
{searchType === SearchTypes.BASIC ? (
<BasicSearch toggleSearchType={toggleSearchType} />
) : searchType === SearchTypes.ADVANCED ? (
<PatientScheduledVisits toggleSearchType={toggleSearchType} />
) : searchType === SearchTypes.SCHEDULED_VISITS ? (
<AdvancedSearch toggleSearchType={toggleSearchType} />
) : null}
</div>
</Overlay>
</>
);
};
The searchtypes are as below :
export enum SearchTypes {
BASIC = 'basic',
ADVANCED = 'advanced',
SCHEDULED_VISITS = 'scheduled-visits'
}
My component A looks like this :
import React, { useEffect, useMemo, useState } from 'react';
interface BasicSearchProps {
toggleSearchType: (searchMode: SearchTypes) => void;
}
const BasicSearch: React.FC<BasicSearchProps> = ({ toggleSearchType }) => {
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState<any>(null);
const customRepresentation = '';
return (
<div className={searchResults?.length ? styles.lightBackground : styles.resultsContainer}>
{searchResults?.length ? (
<div className={styles.resultsContainer}>{<SearchResults toggleSearchType={searchResults} patients={searchResults} />}</div>
) : (
<div>
<div className={styles['text-divider']}>{t('or', 'Or')}</div>
<div className={styles.buttonContainer}>
<Button
kind="ghost"
iconDescription="Advanced search"
renderIcon={Search16}
onClick={() => toggleSearchType(SearchTypes.ADVANCED)}>
{t('advancedSearch', 'Advanced search')}
</Button>
</div>
</div>
)}
</div>
);
};
export default BasicSearch;
Component B looks like this :
interface SearchResultsProps {
patients: Array<any>;
hidePanel?: any;
toggleSearchType: (searchMode: SearchTypes) => void;
}
function SearchResults({ patients, toggleSearchType }: SearchResultsProps ) {
const fhirPatients = useMemo(() => {
return patients.map((patient) => {
const preferredAddress = patient.person.addresses?.find((address) => address.preferred);
});
}, [patients]);
return (
<>
{fhirPatients.map((patient) => (
<div key={patient.id} className={styles.patientChart} onClick={() => toggleSearchType(SearchTypes.SCHEDULED_VISITS)} >
<div className={styles.container}>
<ExtensionSlot
extensionSlotName="patient-header-slot"
state={{
patient,
patientUuid: patient.id,
// onClick: onClickSearchResult,
}}
/>
</div>
</div>
))}
</>
);
}
}
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;
I have an array of job descriptions that I want to hide a part of each description and show it completely when a button is clicked using React hooks.
I am iterating over the array( consists of id and description) to show all the descriptions as a list in the component. There is a button right after each paragraph to show or hide the content.
readMore is used to hide/show the content and
activeIndex is used to keep track of clicked item index.
This is what I have done so far:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [readMore, setReadMore] = useState(false);
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{readMore ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
setActiveIndex(index);
if (activeIndex === id) {
setReadMore(!readMore);
}
}}
>
{readMore ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
The problem is that when I click one button it toggles all the items in the list.
I want to show/hide content only when its own button clicked.
Can somebody tell me what I am doing wrong?
Thanks in advance.
Your readMore state is entirely redundant and is actually causing the issue. If you know the activeIndex, then you have all the info you need about what to show and not show!
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndex === index ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
if (activeIndex) {
setActiveIndex(null);
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Edit: The aforementioned solution only lets you open one item at a time. If you need multiple items, you need to maintain an accounting of all the indices that are active. I think a Set would be a perfect structure for this:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndices, setActiveIndices] = useState(new Set());
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndices.has(index) ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
const newIndices = new Set(activeIndices);
if (activeIndices.has(index)) {
newIndices.delete(index);
} else {
newIndices.add(index);
}
setActiveIndices(newIndices);
}}
>
{activeIndices.has(index) ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Try this
{readMore && (activeIndex === id) ? description : `${description.substring(0, 250)}...`}
function Destination() {
const travels = [
{
title: "Home"
},
{
title: "Traveltype",
subItems: ["Local", "National", "International"]
},
{
title: "Contact",
subItems: ["Phone", "Mail", "Chat"]
}
];
const [activeIndex, setActiveIndex] = useState(null);
return (
<div className="menu-wrapper">
{travels.map((item, index) => {
return (
<div key={`${item.title}`}>
{item.title}
{item.subItems && (
<button
onClick={() => {
if (activeIndex) {
if (activeIndex !== index) {
setActiveIndex(index);
} else {
setActiveIndex(null);
}
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? `Hide` : `Expand`}
</button>
)}
{activeIndex === index && (
<ul>
{item.subItems &&
item.subItems.map((subItem) => {
return (
<li
key={`li-${item.title}-${subItem}`}
>
{subItem}
</li>
);
})}
</ul>
)}
</div>
);
})}
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>