Add active border to only a single item on click - javascript

Clicking on an article I want to add a 'selected' border around it:
const [selected, setSelected] = useState(false);
const handleClick = (evt) => {
setSelected(true)
};
<article
onClick={handleClick}
className={`${selected ? "border-red-500" : ""} ... `}
>
This works great for a single article but how would I go about it if I had multiple articles? I only want to have one selected at a time. This method would obviously add a border around every article when clicked.

If you want to use multiple articles, You can use map to render and setSelected is id/index of article:
const handleClick = (id) => {
setSelected(id !== selected ? id : "")
};
listArticles.map((item, index) => (
<article
key = {item.id} // or index
onClick={() => handleClick(item.id)} //or index
className={`${selected === item.id ? "border-red-500" : ""} ... `} // or index
>
))

This usually looks something like this:
const [selected, setSelected] = useState(false);
list.map((body, i) => <article key={i}
onClick={() => setSelected(i)}
className={`${selected == i ? "border-red-500" : ""} ... `}
>{body}</article>)
But it depends a little on how you store, render, and identify your list of things (in this case articles).

Related

How to test If onClick calllback renders isactive state when tite is clicked?

I tried to write test for the onClick callback in this component:
const Accordion = ({ children, text, color, activeColor }) => {
const [isActive, setIsActive] = useState(false)
return (
<Styled.Accordion color={color}>
<Styled.Title
variant="h5"
as="div"
onClick={() => setIsActive(!isActive)}
color={isActive ? activeColor : color}
>
{text}
<Styled.Icon isActive={isActive}>
<Icon name="arrowUp" />
</Styled.Icon>
</Styled.Title>
{isActive && <Styled.Content>{children}</Styled.Content>}
</Styled.Accordion>
)
}
basically I wrote it like this:
describe(`Accordion`, () => {
it(`fires isActive state when title is clicked`, () => {
const isActive = jest.fn()
render(<Accordion onClick={isActive} />)
fireEvent.click(isActive)
expect(isActive).toHaveBeenCalledWith(false)
})
But it fails. Could you please advise what is wrong with it? I would like to write a test to check if isActive state renders, after clicking on the accordion's title.
First of all, you are setting the color property. Looks like it should be inside the style property.
Add data-testId property to your Styled.Ttile:
<Styled.Title data-testId="accordion-title">
Then you can check if the style was changed after clicking. This is more like the direction than the exact solution.
it(`marks accordion as active after click`, () => {
const { getByTestId } = render(<Accordion activeColor="red" />);
const accordionTitle = getByTestId('accordion-title');
fireEvent.click(accordionTitle)
expect(accordionTitle).toHaveStyle({color: 'red'});
});

Creating like button for multiple items

I am new to React and trying to learn more by creating projects. I made an API call to display some images to the page and I would like to create a like button/icon for each image that changes to red when clicked. However, when I click one button all of the icons change to red. I believe this may be related to the way I have set up my state, but can't seem to figure out how to target each item individually. Any insight would be much appreciated.
`
//store api data
const [eventsData, setEventsData] = useState([]);
//state for like button
const [isLiked, setIsLiked] = useState(false);
useEffect(() => {
axios({
url: "https://app.ticketmaster.com/discovery/v2/events",
params: {
city: userInput,
countryCode: "ca",
},
})
.then((response) => {
setEventsData(response.data._embedded.events);
})
.catch((error) => {
console.log(error)
});
});
//here i've tried to filter and target each item and when i
console.log(event) it does render the clicked item, however all the icons
change to red at the same time
const handleLikeEvent = (id) => {
eventsData.filter((event) => {
if (event.id === id) {
setIsLiked(!isLiked);
}
});
};
return (
{eventsData.map((event) => {
return (
<div key={event.id}>
<img src={event.images[0].url} alt={event.name}></img>
<FontAwesomeIcon
icon={faHeart}
className={isLiked ? "redIcon" : "regularIcon"}
onClick={() => handleLikeEvent(event.id)}
/>
</div>
)
`
Store likes as array of ids
const [eventsData, setEventsData] = useState([]);
const [likes, setLikes] = useState([]);
const handleLikeEvent = (id) => {
setLikes(likes.concat(id));
};
return (
<>
{eventsData.map((event) => {
return (
<div key={event.id}>
<img src={event.images[0].url} alt={event.name}></img>
<FontAwesomeIcon
icon={faHeart}
className={likes.includes(event.id) ? "redIcon" : "regularIcon"}
onClick={() => handleLikeEvent(event.id)}
/>
</div>
);
})}
</>
);
Your issue is with your state, isLiked is just a boolean true or false, it has no way to tell the difference between button 1, or button 2 and so on, so you need a way to change the css property for an individual button, you can find one such implementation by looking Siva's answer, where you store their ids in an array

Editing an input field among many in React

check this code please https://stackblitz.com/edit/react-koqfzp?file=src/Section.js
Everytime i add an item, i'm also adding an random number that i want to edit. The number is rendered at a MUI Text field component.
<TextField
type="number"
variant="standard"
aria-readonly={edit === true ? false : true}
value={edit === true ? value : numbers[i]}
onChange={(e) => setValue(e.target.value)}
/>
And the buttons are rendered based on edit's state, like this:
{edit === true ? (
<button onClick={() => saveEdit(i)}>Save</button>
) : (
<button onClick={() => setEdit(true)}>Edit</button>
)}
And it's working, i can edit it and rerender with the new value, the problem is that when i click the edit button, every field receives the new input value and the save button shows up in every field, though when i save it, it returns to it's original value. How i can do what i'm doing but to just one specific field?
The problem is that you are using setEdit as a boolean
You can define it as the index of the array to edit with -1 as starting value
const [edit, setEdit] = useState(-1)
...
{edit === i ? (
<button onClick={() => saveEdit(i)}>Save</button>
) : (
<button onClick={() => setEdit(i)}>Edit</button>
)}
I recommend creating another component Exemple:Item with it has it's own states edit,value states
And pass the needed props to it which are the value,numbers,saveEdit(index,newValue),removeItem(index) and also the index
saveEdit has to be changed in section by passing the index and the newValue
I hope this clear for you
add the map values in a component and add the states inside the component so you will have multiple states for each component not just 1
parent component
{section.items.map((item, i) => (
<Component key={i} item={item}/>
)}
child component
const Component = ({ section, addItem, removeItem}) => {
const [newItem, setNewItem] = useState('');
const [edit, setEdit] = useState(false);
const [value, setValue] = useState(0);
const [numbers, setNumbers] = useState(section.number);
const handleChange = (e) => {
setNewItem(e.target.value);
};
return (
<TextField
type="number"
variant="standard"
aria-readonly={edit === true ? false : true}
value={edit === true ? value : numbers[i]}
onChange={(e) => setValue(e.target.value)}
/>
)

React. How to change state only to current element after .map method?

When all list items are rendered I want that after click only clicked list item has been changed its style.
const [currentStyle, setCurrentStyle] = useState();
array.map(val => (<li style={currentStyle}>{val.name}</li>)
I advise you to make another component for you items, that way it will be easier to manage state for each item
for instance :
function YourComponent() {
...
const [currentStyle, setCurrentStyle] = useState();
...
array.map(val => (<ItemComponent style={currentStyle}>{val.name}</ItemComponent>)
...
}
function ItemComponent({style, children}) {
const [changeStyle, setChangeStyle] = useState(false)
return (
<li onClick={() => {setChangeStyle(true)}} style={changeStyle ? style : null}>{children}</li>
)
}
The better way to do that has already been shared by Jimmy.
but if you are lazy you can do something like.
Note: Both approaches are different. If you manage that via subcomponents you will have to add extra logic to keep track of clicked items in the parent. (Still recommended)
But if you use this one you can have the clicked Items track in the component itself (clickedItems)
const [currentStyle, setCurrentStyle] = useState();
const [clickedItems, setClickedItems] = useState([]);
array.map((val, index) => (<li onClick={() =>
setClickedItems(clickedItems.find(i => i===index) ? clickedItems : [...clickedItems, index])}
style={clickedItems.find(i => i===index) ? currentStyle : null}>{val.name}
</li>)

Setting "active" className for the menu user selected in React

Just like the screenshot above, I am using Semantic-UI where the selected menu gets highlighted.
As my code below, I've set state to whichever menu the user clicks on. My code below works fine, but I think this is inefficient ways to write code since I am basically calling two functions everytime I render just to change the switch out the class names.
Would there be any better way to achieve this?
Please advise.
const Navigation = () => {
const [selected, setSelected] = useState("Comments");
const onSelection = (e) => {
setSelected(e.target.textContent);
};
const commentActive = () => {
return selected === "Comments" ? "active" : "";
};
const searchActive = () => {
return selected === "Search" ? "active" : "";
};
return (
<div className="ui secondary pointing menu">
<a className={`item ${commentActive()}`} onClick={(e) => onSelection(e)}>
Comments
</a>
<a className={`item ${searchActive()}`} onClick={(e) => onSelection(e)}>
Search
</a>
</div>
);
};
I think you shouldn't hardcode the boolean conditions selected === 'Comments' as its bug prone, because if you decide to change the anchor's context without changing the condition you may have a bug: target.textContent !== 'Comments';
Instead use enums:
const NAV_SECTIONS = {
COMMENTS: "comments",
SEARCH: "search",
};
const Navigation = () => {
const [selected, setSelected] = useState(NAV_SECTIONS.COMMENTS);
return (
<div className="ui secondary pointing menu">
<a
className={`item ${selected === NAV_SECTIONS.COMMENTS ? "active" : ""}`}
onClick={() => setSelect(NAV_SECTIONS.COMMENTS)}
>
{/* We changed the content and the code didn't break */}
My Cool Comments
</a>
<a
className={`item ${selected === NAV_SECTIONS.SEARCH ? "active" : ""}`}
onClick={() => setSelect(NAV_SECTIONS.SEARCH)}
>
My Best Search
</a>
</div>
);
};

Categories