Conditioning a specific item from a mapped dynamic array in React JS - reactjs

I want to have an edit mode to each field in a div that is mapped out from an array that I fetch from firbase. I succeeded doing that by conditioning the rendered field to the value of a boolean (editField) which I then manipulate using useState, like so:
in the functions seen up there I can manipulate the value of editTitle, so as to switch between the two functions by double clicking or clicking a button, and also update the field value in Firebase. as such:
this all works fine. HOWEVER,
if there are more that one divs rendered from the tasks[], then thay are obviously all effected to the flipping of editTitle's value from false to true, and by double clicking one field, all fields of same name in all divs swithc to edit mode. as such:
what can I do to target only the field in the task I want to edit? I've tried using the elemnt.id and index in some way bat can't seem to come up with the correct method...
const ifEditTitleIsTrue = (element, index) => {
return (
<div>
<input
type="text"
defaultValue={element.Title}
onChange={(e) => setUpdatedTitle(e.target.value)}
/>
<button className="exit__editmode-btn btn" onClick={exitEditMode}>
X
</button>
<button
className="update__edit-btn btn"
id="updateTitle"
onClick={(e) => updateField(e, element.id)}
>
ok
</button>
</div>
);
};
// if editTitle = false (default):
const ifEditTitleIsFalse = (element, index) => {
return (
<h3
id={index}
className="task-title"
onDoubleClick={() => setEditTitle(true)}
>
{element.Title}
</h3>
);
};
// edit mode for inCharge field
const ifEditInChargeIsTrue = (element, index) => {
return (
<div>
{
<GetCollaboratorsForEditMode
catchValueInCharge={catchValueInCharge}
/>
}
<button className="exit__editmode-btn btn" onClick={exitEditMode}>
X
</button>
<button
className="update__edit-btn btn"
id="updateInCharge"
onClick={(e) => updateField(e, element.id)}
>
ok
</button>
</div>
);
};
{tasks[0] &&
tasks.map((element, index) => (
<div id={element.id} className="task" key={element.id}>
{editTitle
? ifEditTitleIsTrue(element, index)
: ifEditTitleIsFalse(element, index)}

You need to keep track of what element is in edit mode. You can do it by storing the element id in your editTitle state, instead of just a boolean
const ifEditTitleIsFalse = (element, index) => {
...
onDoubleClick={() => setEditTitle(element.id)}
...
};
The condition to render an element in edit mode or view mode would change to:
{editTitle === element.id
? ifEditTitleIsTrue(element, index)
: ifEditTitleIsFalse(element, index)}

I've solved it!!!
insted of EditTitle being a boolean, it's just an empty string.
then the condition is editTitle === index ? some function : some othe function;
and the doubleclick is (()=> setEditTitle(index)).

Related

React Mui Autocomplete resets scroll after selecting values

So I'm trying to set up a mui-autocomplete component with additional buttons (Clear all (clear all values and close dropdown) + Apply (set value and close dropdown)) using ListboxComponent.
Issues:
when selecting options from the bottom of the list, the scroll position is reset to the top
cannot close the dropdown programmatically
Here is the ListboxComponent
ListboxComponent={(listBoxProps) => {
return (
<div>
<ul {...listBoxProps} />
<div>
<button
onMouseDown={(event) => {
// Disable blur
event.preventDefault();
}}
onClick={() => {
// clear values
setSelected([]);
}}
>
Clear All
</button>
<button
onMouseDown={(event) => {
// Disable blur
event.preventDefault();
}}
onClick={() => {
// apply value
}}
>
Apply
</button>
</div>
</div>
);
The options are rendered as follows:
renderOption={(optionProps, option, optionState) => {
return (
<li {...optionProps}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
checked={optionState.selected}
/>
{option}
</li>
);
}}
So I'm using state to keep track of saving the selected values:
const [selectedResult, setSelected] = useState([]);
And when the option is selected - the state is updated
onChange={(event, selectedOptions) => {
setSelected(selectedOptions);
}}
But when the state changes, the component is re-rendered and the scroll is reset. It also seems that I can't use local variables to store the intermediate result, as the state won't update and the checkbox won't update.
StackBlitz link
Is there anything I can do to achieve this?

An element is not removed from the array, how to fix it?

I have data that I get from api and through the map() method I display these "cards", each card has an image when clicked on which this image should receive an additional class. I implemented this by adding the index of the card to an array and now I can assign new classes to them, but I can't delete them
P.S. I have strict mode enabled, if it is disabled it removes extra classes on all other cards except the one I clicked on
//On the first click, it adds to the array, on the second click, it must delete it (index is written to the array)
function toggleFavoriteChanel(index) {
setFavorite(prevState => {
let returnArray = prevState;
if(prevState.includes(index)){
console.log(prevState)
console.log(index)
return returnArray.splice(prevState.indexOf(index), 1)
}else{
// here are 3 dots
return [..returnArray, index]
}
})
}
// <img src={star} alt="star".../>
{Array.isArray(props.visibleData) ? props.visibleData.map((chanel, index) => {
return (
<>
<div className="chanel__item" key={index}>
<img src={star} alt="star" onClick={() => props.toggleFavoriteChanel(index)} id={index} className={`star ${props.favorite.includes(index) ? 'active' : ''}`} />
<NavLink
onClick={() => props.updateData(index)}
end
style={{ textDecoration: 'none' }}
to='/ChanelPage'>
<img src={chanel.image} alt="" className="chanel__img" />
<div className="chanel__title"><div className="chanel__item-number">{index + 1}. </div>{chanel.name_ru}</div>
</NavLink>
</div>
</>
)
}) : null}
The issue is that you are setting favorite to the return value of splice, which is an array containing the deleted elements (from MDN docs on splice). What you want instead is to return returnArray after calling splice on it.
Just change this line in toggleFavoriteChanel:
return returnArray.splice(prevState.indexOf(index), 1)
to:
returnArray.splice(prevState.indexOf(index), 1);
return returnArray;
While the above should fix your issue, I would recommend approaching this problem in a different way if you are just trying to toggle a CSS class in response to clicking (assuming you don't need a list of the favorited cards at a higher level).
The approach is to define a component for the card and hold the isFavorite (clicked) state locally rather than in an array in an ancestral component.
Here's a rough example:
function Card(props) {
const [isFavorite, setIsFavorite] = React.useState(false);
return (
<div className="chanel__item">
<img
src={star}
alt="star"
onClick={() => setIsFavorite(prev => !prev)}
id={props.index}
className={`star ${isFavorite ? 'active' : ''}`}
/>
<NavLink
onClick={() => props.updateData(props.index)}
end
style={{ textDecoration: 'none' }}
to='/ChanelPage'>
<img src={chanel.image} alt="" className="chanel__img" />
<div className="chanel__title"><div className="chanel__item-number">{props.index + 1}. </div>{props.chanel.name_ru}</div>
</NavLink>
</div>
)
}

React JS mapped array - render a single item

I have a React front end, which renders an array of rows based on objects from an API. I am mapping the object array, which works as intended. If the condition is met, an icon button is displayed. If the condition of the child elements below (sector.properties.length === 0) is true multiple times, multiple buttons will be rendered in the row. I am trying to only display a single button if the condition is true, but am struggling to figure it out.
{data.sectors.map((sector, index) => (
<SingleLineCell
key={`${data.productName}Product${index}`}
>
{sector.properties.length === 0 && (
<button
type="button"
onClick={() =>
showModal('DeleteData', {
form: data,
onConfirmDelete: () => {
onConfirmDelete(data);
}
})
}
>
<IconDelete responsive />
<span className="sr-only">Delete {data.productName} Product</span>
</button>
)}
</SingleLineCell>
))}
So this is what is currently rendered. I want to only render the first button, even when the condition is true multiple times:
If the map is just used to display this button then instead of map use first some to check if the data satisfies the condition and then just print the button.
{data.sectors.some((sector) => sector.properties.length === 0) && (
<SingleLineCell
key={`${data.productName}Product`}
>
<button
type="button"
onClick={() =>
showModal('DeleteData', {
form: data,
onConfirmDelete: () => {
onConfirmDelete(data);
}
})
}
>
<IconDelete responsive />
<span className="sr-only">Delete {data.productName} Product</span>
</button>
</SingleLineCell>
)}

Accessible dynamically created unordered list

I am currently tasked with making a current dynamic search accessible. Currently the search field all seems to work and announce. However when triggered the list propagates but does not seem to interact with the screen reader. I did not create this component just tasked with making it a11y compatible.
For example if I type the letter "d" in the search field the list shows all elements that match that. But on arrowDown the screen reader does not announce the value to select. I am curious if there is something I am missing here to allow that to happen, or perhaps my screen reader is just not as advanced as others.
The code is in React and as follows:
<div id={id} className={classNames.join(" ")} onClick={focusInput}>
<a
className="search-icon"
onClick={(ev) => {
onClickLookupIcon();
ev.stopPropagation();
}}
/>
<div className={"styled-scrollbar " + itemsClassNames.join(" ")}>{itemEls}</div>
{results.length > 0 && (
<ul className="auto-complete" style={{ top: allowMultiple ? "42px" : "22px" }}>
{results.map((it, i) => (
<li
key={i}
className={i === selectedSearchIndex ? "selected" : ""}
//use mousedown so that this triggers before the "blur" event on the input which would trigger selecting the `selectedSearchIndex` item instead of the clicked item
onMouseDown={(ev) => {
addOrReplaceItem({ type: "valid", value: it });
resetLookup();
ev.stopPropagation();
}}
>
<SearchDelegate {...it} />
</li>
))}
</ul>
)}
</div>
Expanding some code to see more re {itemEls}
const itemEls = value.map((it) => {
const itemId = it.type === "valid" ? getRecordId(it.value) : it.newItemId;
return currEditingRecord === itemId ? (
inputEl
) : (
<div
key={itemId}
className="hg-lookup-item-wrapper"
onClick={(ev) => {
ev.stopPropagation();
//edit the clicked-on item unless we're already editing it
if (currEditingRecord === itemId) return;
setSearchString(it.type === "valid" ? getItemSearchText(it.value) : it.searchString);
setCurrEditingRecord(itemId);
//select all text when clicking on existing item
setTimeout(() => inputRef.current && inputRef.current.select());
}}
>
{it.type === "valid" ? (
<>
<Delegate item={it.value} />
<a className="hg-remove-icon" onClick={doRemove(itemId)} />
</>
) : (
<PendingResult<T>
key={it.newItemId}
loadMatches={loadMatches}
searchString={it.searchString}
onValueSelected={(val) => {
onChange(
value.map((v) =>
v.type === "valid" || v.newItemId !== it.newItemId || valueExists(val)
? v
: { type: "valid", value: val },
),
);
}}
/>
)}
</div>
);
});
and lastly the inputEl
const inputEl = (
<div key="INPUT" className="hg-lookup-item-wrapper">
<AutosizeInput
id={`${id}-input`}
type="text"
ref={inputRef}
aria-autocomplete="list"
aria-controls={`${id}-listbox`}
value={searchString || ""}
onBlur={() => {
takeSelectedResultOrSetupPendingResult(false);
resetLookup();
}}
autoComplete={"off"}
{...{ disabled }}
onChange={(ev) => {
if (!allowMultiple && isAddingNewRecord() && value.length === 1) return;
setSearchString(ev.currentTarget.value);
}}
onKeyDown={onKeyDown}
/>
</div>
);
on mouseDown the screen reader does not announce the value to select
So you're using a mouse in addition to a screen reader? While there are some users that have that combination, the majority of screen reader users use only the keyboard and not a mouse. Does your component work with just a keyboard? Can you tab to it and the label for the component is read (WCAG 4.1.2). If you type d and a list appears below it, can you arrow down to an item and press enter to select it (WCAG 2.1.1)?
I would initially focus on making sure everything works from a keyboard first, and then augment that work to ensure things are announced by a screen reader. For example, typing d shows a list of options. Are the number of items in the list announced?
While your object might not be a combobox, you should look at the combobox pattern, especially the "autocomplete" options.
https://www.w3.org/TR/wai-aria-practices/#combobox
And check out example 1 on:
https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html#ex1_label

Adding a specific styling to a specific item of a mapped array in React

const allTodos = [{id: 1, name: 'whatever user type'}, { }, { }, .... { }] // Defined as an array using setState in other file. Imported in this file as allTodos using props.
export const Todos = (props) => {
props.allTodos.map((prev) => {
return (
<div id="item_container">
<button type='button' className = 'check_button'
onClick = {() => setActiveTodos(prev.id)}>
<img src = {check} alt="" id = "check_img"></img>
</button>
<li id="li_item">{prev.name}</li>
</div>
)}
}
Now, the question is I want to add some specific style to the element(Button) clicked upon on. The styling I want to add is just change the background of the Button clicked upon.
I tried using conditional to set className but the styling was added to every item. So, it didn't work out.
conditional class - selecting particular class based on condition (in this case - if the activeTodos state == current index)
props.allTodos.map((prev, i) => {
<button type = 'button' key ={i}
className= {`${prev.id == activeTodos ? "active" : "inactive"}
onClick={e => setActiveTodos(prev.id)}}
</button>
}
There is some combinations (eg. There can be selected only 1 item per time or 4)
If You wan't 4 items selected (eg from 6) - then there is possiblity to hold active ids in array.

Resources