React Antd dropdown throws if menu is dynamic - reactjs

I am trying to get an Antd dropdown based on an array but it keeps failing with Error: React.Children.only expected to receive a single React element child.
I have seen the other threads about it but I am confused because my menu works if the data is static: Here's a snippet
var items = [
{
key: '0',
label: 'item0',
},
{
key: '1',
label: 'item1',
}
];
var items2 = []
for (let item of items) items2.push({key: items2.length.toString(), label: item.label})
console.log(items, items2, JSON.stringify(items), JSON.stringify(items2), JSON.stringify(items) === JSON.stringify(items2) )
return (<>
<Dropdown menu={{ items }} trigger={['click']}>
<a onClick={(e) => e.preventDefault()}>
{name} <DownOutlined />
</a>
</Dropdown>
<Dropdown menu={{ items2 }} trigger={['click']}>
<a onClick={(e) => e.preventDefault()}>
{name} <DownOutlined />
</a>
</Dropdown>
</>)
As expected, items and items2 are the same by construction, and JSON.stringify(items) === JSON.stringify(items2) ) is true
However, clicking on the first dropdown with items works, while clicking on the second one throws.
How can I solve that issue?

the first works only by coincidence! because the variable name is items and since you have to specify the property name which is also items it works so
{{items}}
is in fact :
{{items:items}}
for the second one you should mention the property items because you have a differently named variable
{{items:items2}}

replace menu={{ items2 }} to menu={{ items: items2 }}
the menu attribute must have the items property
I hope it help

You should write it like this
<Dropdown menu={{ items: items2 }} trigger={["click"]}>
enter image description here
enter image description here

Related

React state open all submenu instead of only the one clicked, want to use only one state for all my menu items

I have a react app where I render this information in a component:
const navigation = [
{
name: "Services",
description: "Our services",
href: "#",
subMenu: [
{ name: "Management" },
{ name: "Development" },
{ name: "Specialists" },
],
},
{
name: "Experts",
description: "our experts",
href: "#",
subMenu: [
{ name: "Engineers" },
{ name: "Project managers" },
{ name: "Developers" },
{ name: "Cloud solutions" },
],
},
So "description" is the "header" for each submenu and "name" is each item in the submenu.
Im setting the following state:
const [ShowSubMenu, setShowSubMenu] = useState(false);
This is where I render the submenu in my component:
{navigation.map((item) => (
<>
<div
onClick={() => setShowSubMenu(!ShowSubMenu)}
className=" border-b-2 justify-between pb-4 pl-2 font-bold flex "
>
<h3 className=" text-roboto">{item.description}</h3>
<div className="border-solid border-b-2"></div>
<ChevronDownIcon
className={ShowSubMenu ? "rotate-90 h-5 w-5" : "h-5 w-5 "}
/>
</div>
<div className=" flex flex-col">
{item.subMenu.map((sm) => (
<div
key={sm.name}
className={!ShowSubMenu ? "hidden" : " pl-2 pt-2"}
>
{sm.name}
</div>
))}
</div>
</>
))}
I get that since my submenu opens depending on state, it will open all the submenus. How do I fix so that it knows which item is clicked and as such only opens the children for that item? I thought that the "key" prop would let it figure this out. I don't want to have an individual useState for each item unless nesseccary. Thanks for help!
TLDR: Click changes state which opens all submenus. Want to open only the submenu for item clicked while only using one useState statement.
for better use of "useState" you can store the name of the item which should be open and handle display with a function that get name of item and expand it if its name matches with one of if conditions inside the function.
for example here you can use this:
const [expandedSubMenu, setExpandedSubMenu] = useState();
and
{navigation.map((item) => (
<>
<div
onClick={() => setExpandedSubMenu(item.description)}
className=" border-b-2 justify-between pb-4 pl-2 font-bold flex "
>
<h3 className=" text-roboto">{item.description}</h3>
<div className="border-solid border-b-2"></div>
<ChevronDownIcon
className={expandedSubMenu === item.description ? "rotate-90 h-5 w-5" : "h-5 w-5 "}
/>
</div>
<div display={expandedSubMenu === item.description ? "flex" : "none"} className=" flex flex-col">
{item.subMenu.map((sm) => (
<div
key={sm.name}
className={expandedSubMenu === item.description ? "hidden" : " pl-2 pt-2"}
>
{sm.name}
</div>
))}
</div>
</>
))}
want to use only one state for all my menu items
That state still needs to in some way be able to indicate which element is "open". A single boolean value doesn't store this information.
You can instead perhaps track an identifier for the record in state. How are your records uniquely identified? The name value? If so, use that as the state value. Something like this:
const [ShowSubMenu, setShowSubMenu] = useState();
Which defaults to undefined, implying that no element is currently "open". Then to set one to "open":
setShowSubMenu(item.name)
And to hide it:
setShowSubMenu(undefined)
Or to toggle in a one-liner, perhaps something like:
setShowSubMenu(ShowSubMenu ? undefined : item.name)
Or when clicking on another menu and wanting to close the currently open one, that might look more like this:
setShowSubMenu(ShowSubMenu === item.name ? undefined : item.name)
There are a variety of tweaks you can make for the individual click handlers you're creating.
Then when displaying the elements, check if ShowSubMenu matches the element:
className={ShowSubMenu === item.name ? "rotate-90 h-5 w-5" : "h-5 w-5 "}
One possible way of doing this could be to create multiple states, one for each dropdown as so,
const [showServices, setShowService] = useState(false);
const [showExperts, setShowExperts] = useState(false);
This however can become tedious as the project scales.
alternatively you can combine these to create one which can handle any boolean value which is added.
const [bool, setBool] = useState({});
setBool({ ...bool, ['services']: true });
setBool({ ...bool, ['experts']: true });
console.log(bool['services'])
This method creates a new key, value pair without mutating the original state of the object. which can then be updated using the same syntax to switch between true or false. and each boolean value can be accessed to handle different components seperately.
I think it is worth mentioning that state can be stored as objects, or as an array and these arrays or objects can be updated using the rest operator in a lot of different ways

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.

Is there a way to add a class to all elements that has a property set to true?

I have an object with a property of questions. questions is an array of 13 objects.Each object has a choices array that have between 2-4 objects and each object has an isCorrect property that is either true or null.
All my questions are being rendered. I have a button that I want to add a classname to each correct answer that the user selected. Do you guys have any suggestions of how I can achieve this?
{quiz.questions.map((question, index) => (
<ContentWrapper key={question._key}>
<h3>
{`${index + 1}. `}
{question.title}
</h3>
{question.choices.map(choice => (
<ChoiceStyle id={"wrapper" + choice._key} key={choice._key}>
<input
type="radio"
id={choice._key}
name={question._key}
onChange={updateScore}
value={choice.isCorrect === null ? "0" : "1"}
/>
<label htmlFor={choice._key}>{choice.title}</label>
</ChoiceStyle>
))}
<ButtonComponent
type="button"
buttonType="second"
title="Sjekk svar"
onClick={() => {
calculateScore();
}}
/>
That button just calculates the score but I might need to add another function to that button? I am also using styled components
You have multiple approaches to do this, the easiest and less invasive way is to add a ternary condition to show/hide the class name based on an isCorrect property.
I'm assuming that quiz is a state that is changed in each onChange to refresh all the real-time changes on the quiz. At this point:
{quiz.questions.map((question, index) => (
<ContentWrapper key={question._key}>
<h3>
{`${index + 1}. `}
{question.title}
</h3>
{question.choices.map(choice => (
<ChoiceStyle id={"wrapper" + choice._key} key={choice._key}>
<input
type="radio"
id={choice._key}
name={question._key}
onChange={updateScore}
className={`${choice.isCorrect ? 'is-valid':''}`}
value={choice.isCorrect === null ? "0" : "1"}
/>
<label htmlFor={choice._key}>{choice.title}</label>
</ChoiceStyle>
))}
<ButtonComponent
type="button"
buttonType="second"
title="Sjekk svar"
onClick={() => {
calculateScore();
}}
/>
The trick is the following ternary condition:
className={`${choice.isCorrect ? 'is-valid':''}`}
The problem that occurs is that the class gets applied right away. I
don't want the correct answer to show up unless the user has clicked
the button component
Then you will need to add a new property to your choice object to check and play with it. Let's assume something like:
const choice={
key: "some key"
isCorrect: true
/// other fields
}
// more code
{quiz.questions.map((question, index) => (
<ContentWrapper key={question._key}>
<h3>
{`${index + 1}. `}
{question.title}
</h3>
{question.choices.map(choice => (
<ChoiceStyle id={"wrapper" + choice._key} key={choice._key}>
<input
type="radio"
id={choice._key}
name={question._key}
onChange={updateScore}
className={`${choice.isValid ? 'is-valid':'' }`}
value={choice.isSelected === null ? "0" : "1"}
/>
<label htmlFor={choice._key}>{choice.title}</label>
</ChoiceStyle>
))}
<ButtonComponent
type="button"
buttonType="second"
title="Sjekk svar"
onClick={() => {
calculateScore();
}}
/>
In each updateScore, you'll need to check if the choice is correct and matches your business logic and add the new property (isValid) to trigger it.
const updateScore =()=>{
// some unknown logic
// some validations and then:
choice.isValid = true,
}
Since (I've assumed) that you are setting the quiz in React's state, the new property will force a refresh of the state, applying the class name in the user clicks.

Display react Bootstrap Popover when click on a cell in react-bootstrap-table 2

Design the section with react-bootstrap-table2. I just want to show popover with options on clicking the particular cells. Is there any way to combine react-bootstrap-table2 and react bootstrap popover.
It is possible using formatter in bootstrap table props.
Reference link :
https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html
Here i my Code
List.tsx
const columns: ColumnProps<User>[] = [ {
dataField: 'action',
text: '',
formatter: ColumnFormatterFactory,
style: { overflow: 'inherit' },
},
];
<BootstrapTable
bootstrap4
bordered={false}
classes="mb-5 table-fit"
columns={columns}
data={this.records}
filter={filterFactory()}
keyField="id"
noDataIndication="No Users yet."
remote={{ sort: true, filter: true }}
rowClasses="clickable-row"
onTableChange={handleTableChange}
/>
ColumnFormatterFactory.tsx
export const ColumnFormatterFactory = (cell: any, row: number, rowIndex: any, formatExtraData:any) => {
return (
<Dropdown>
<Dropdown.Toggle
id={`${row.id}-dropdown`}
>
<span >More</span>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item >one1</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item >two</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item >three</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
);
};
You would need to import all the required components and just simply combine them in the way that works for you. You do not need to use the Button the docs recommmend. I have a working example here. https://codesandbox.io/s/confident-bush-8dxgo it's ugly and I just threw it together but it will give you a good idea of how to combine them.
Next time try and post some code you've tried.

Resources