How to increase the tabindex in React ul-li search result? - javascript

I used ul li to implement search result:
<ul className="search-result">
<li tabindex="1">title here...</li>
<li tabindex="2">title here...</li>
<li tabindex="3">title here...</li>
.
.
.
</ul>
and this is the style for each item:
.search-result{
li:active, li:focuse {
font-weight: bold;
background: #f0f0f0;
}
}
but I'm trying to add a feature which client navigate between result items by using key-down or key-up buttons on the keyboard. but how can I access to current active tabindex in document to increase or decrease that by JavaScript and not tab button?

Do you want to tab to each <li> (search result) and use the up/down arrow keys to navigate through the list?
You have to be careful when using positive values for tabindex. It should rarely be used because it changes the default browser focus order which most users are used to. The default focus order is DOM order. In your case, if you want the user to tab to each <li> in order, you don't need a positive value for tabindex because your <li> elements are already in the order you want them tabbed to. Just set them all to 0. A value of 0 means that the DOM element should be inserted into the tab order in the normal DOM order.
<ul className="search-result">
<li tabindex="0">title here...</li>
<li tabindex="0">title here...</li>
<li tabindex="0">title here...</li>
.
.
.
</ul>
Now, having said that, elements should only have tabindex="0" if they are interactive elements. An <li> is not normally an interactive element so it'll be confusing to tab to it. What can the user do once they tab to the <li>? Can they press enter or space to select it? If the user can't interact with that element, then it should not be a tab stop.
Typically, the <li> contains things that are interactive, such as links, buttons, checkboxes, etc. Those elements are already tab stops by default and don't need a tabindex.
As far as using up/down arrow keys for navigation, again, you should only be able to arrow to elements that are interactive. I normally have an onkeydown handler on the <ul> and it listens for the arrow keys and adjusts the tabindex for each item. But I only do this when I want my entire list to be one tab stop and the user must arrow up/down to go between each list item. In your case, it sounds like you want both behaviors which is why I asked the question at the beginning of my answer.
If the list is treated as one tab stop, then all <li> elements will have a tabindex="-1" except for the <li> that has focus. It will have tabindex="0". That way the user can tab to the list as a whole and the focus will go to the list item that last had focus. The user can then up/down through the list.
When the user presses up/down, all you have to do is change tabindex from 0 to -1 for the currently focused list item and change tabindex from -1 to 0 for the list item you're moving focus to, and then call focus() on the newly focused element.

You need store current tab index in state and update it in keydown eventListener
import "./styles.css";
import { useState, useEffect } from "react";
import users from "./data";
export default function App() {
const [searchValue, setSearchValue] = useState("");
const [currentTab, setCurrentTab] = useState(0);
const searchResults = users.filter(
(user) => searchValue.trim() && user.includes(searchValue.trim())
);
useEffect(() => {
const handleKeyDown = (e) => {
const keyCode = e.keyCode;
if (keyCode == 38 && searchResults.length - 1 > currentTab)
setCurrentTab(currentTab + 1);
if (keyCode == 40 && currentTab >= 1) setCurrentTab(currentTab - 1);
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [searchResults, currentTab]);
return (
<div>
<input
type="text"
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
setCurrentTab(0);
}}
/>
<ul className="search-result">
{searchResults.map((result, i) => (
<li className={currentTab === i ? "active" : ""}>{result}</li>
))}
</ul>
</div>
);
}
I created a sandbox, you can check it

Related

prevent menu component from re-rendering

https://codesandbox.io/s/little-thunder-so1omh?file=/src/menu/menu.scss
this is my problem. when I refresh, menu opens and closes for a moment
I want to prevent this from re-render.
this my console.log when i refresh every time:
false 'open'
menu.jsx:23 item
menu.jsx:25 rerendered
menu.jsx:22 true 'open'
menu.jsx:23 undefined 'item'
menu.jsx:25 rerendered
I went through the code, its not the render that is causing the it to open and close, its the .collapse class animation
you can verify the case by using a ref
// style modification
.hidden {
visibility: hidden !important
}
// this is a flag to detect atleast one click
// on the menu item
// this will be false when app loads the first time
// then after user click on the menu, it will set to true
let isSelectedOnce = React.useRef(false);
const handleDropDown = (id) => {
setItemPressed(id);
if (itemPressed !== id) {
setOpen(true);
} else {
setOpen((pre) => !pre);
}
if (!isSelectedOnce.current) {
isSelectedOnce.current = true;
}
};
return (
...
<ul
className={`collapse ${
open && itemPressed === "menu" ? "show" : ""
} ${!isSelectedOnce.current ? "hidden" : ""}`}
>
<li>Menu Category</li>
<li>products list</li>
<li>Add product</li>
</ul>
</li>
I think you need to keep the menu items collapsed to start with
Hope this helps you in finding a better solution
Edit: more about when to use refs
this is not a bug but a feature, react after 16.3.0. will render twice as for development mode. see the issue on react repo below, happy coding, you doing great!
https://github.com/facebook/react/issues/15074
There was an issue in SCSS. menu opens and closes is fixed now you can check
https://codesandbox.io/s/eager-microservice-b7p4gc?file=/src/menu/menu.scss

Limiting the number of checkboxes selected in react

currently I have a signup form with 5 options but I'm trying to find a way to limit so the user can only select 2 options and in case the user selects a third option the first one would be unchecked, I had found a way of doing this in plain js but I haven't found a react way of doing it. This is what I have so far, would it be better to handle with plain js instead of react?
{iconsPool.map((src, index) => (
<Box className="test">
<input type="checkbox" className="iconsCheckbox" id={iconsPool.id} />
<label for={iconsPool.id}>
<img className="signupIcons" src={iconsPool[index].src} key={index} />
</label>
{console.log(iconsPool)}
</Box>
))}
This can be implemented with a state as an array with 2 elements.
Two items of the state Array will represent the index of selected items.
If an checkbox is clicked, that checkbox and the one clicked right before will be checked. (Therefore unchecking the one that was clicked even before that)
This can be done by pushing the index of newly clicked checkbox into the head of array, and removing the last item of the array.
When an checked checkbox is clicked again, (therefore it should be unchecked,) the index of the checkbox is searched from the state array, and removed by replacing that value with undefined
Below is code, as an example
...
const [checkedItems, setCheckedItems] = useState([undefined,undefined])
// When an Item is clicked
const onClickItem = (itemIndex:number, isSelected: boolean) =>{
if(isSelected){
setCheckedItems(c=>([itemIndex,c[0]]))
} else {
if(itemIndex === checkedItems[0]){
setCheckedItems(c=>([undefined,c[1]]))
} else if(itemIndex === checkedItems[1]){
setCheckedItems(c=>([c[0],undefined]))
}
}
}

Dynamically set useState for multiple sub menus

I'm working on creating a multi level menu and I have sucessfully created a toggle which opens the sub menu on click. The issue I am having however is on click, all of the sub menus are opening. Here is my code so far:
Function
const [isSubOpen, setIsSubOpen] = useState(false)
const toggleSubMenu = (index, e) => {
e.preventDefault()
console.log(index.key)
let test = e.currentTarget.nextElementSibling.id
console.log(test)
if (test == index.key) {
setIsSubOpen(!isSubOpen)
}
}
Menu
<ul>
<li>
<a href={item.url}
onClick={toggleSubMenu.bind(this, { key })}
>
</a>
</li>
</ul>
Sub menu
<div id={key} css={isSubOpen ? tw`block` : tw`hidden`}></div>
Using a single boolean for all of them will cause them all to open and close whenever the state changes. If you want to keep it all in one state, you can use an array or an object to manage each sub-menu. An array would be easiest, so I'll show an example of how that would work.
Your state would be an array consisting of booleans. Each index would represent a sub-menu, false would be closed and true would be open. So if you click to open the first sub-menu at index 0, you would set the array to [true, false].
// Initialize the state with `false` for each sub-menu
const [subMenuState, setSubMenuState] = useState([false, false])
const toggleSubMenu = (e, i) => {
e.preventDefault()
// Clone the array
const newState = subMenuState.slice(0)
// Toggle the state of the clicked sub-menu
newState[i] = !newState[i]
// Set the new state
setSubMenuState(newState)
}
Whenever you call toggleSubMenu, you would pass the index as the second parameter like so:
<ul>
<li>
<a href="#" onClick={e => toggleSubMenu(e, 0)}>
Link 1
</a>
</li>
<li>
<a href="#" onClick={e => toggleSubMenu(e, 1)}>
Link 2
</a>
</li>
</ul>
Then reference that index in the sub-menu to see whether or not it's open:
<div css={subMenuState[0] ? tw`block` : tw`hidden`}>Sub-menu 1</div>
<div css={subMenuState[1] ? tw`block` : tw`hidden`}>Sub-menu 2</div>
I'm not sure what the use case is here, but with most menus you want to close the other active sub-menus. For example, if sub-menu 1 is open and you click to open sub-menu 2, you want sub-menu 1 to close and sub-menu 2 to open. Here's how you would achieve that effect:
const [subMenuState, setSubMenuState] = useState([false, false])
const toggleSubMenu = (e, i) => {
e.preventDefault()
// Clone the array
const clone = subMenuState.slice(0)
// Reset all sub-menus except for the one that clicked
const newState = clone.map((val, index) => {
if(index === i) {
return val
}
return false
})
newState[i] = !newState[i]
setSubMenuState(newState)
}
Let me know if you have any questions. Hope this was helpful!

How do i get refs value from dynamically generated array in React

I got my data from firebase, looped through it and displayed it on the frontend. Now I am trying to get the refs value of the already displayed value when I click on it. For example, when I click on Dino, i should be able to see the value 'Dino' on my console tab of Chrome browser.
Here is a link to the picture of the array list displayed on react frontend
<ul>
<li onClick={this.handleSubmit}>
{
Object.keys(this.props.group).map(function(keyName, keyIndex) {
return(
<div key={keyIndex}>{keyName}</div>
)
})
}
</li>
</ul>
Assuming that those "dino, magic & muu" are the keys in this.props.group you need to add an onClick handler to that div, so:
<div key={keyIndex} onClick={() => console.log(keyName)}>{keyName}</div>
You'll need to bind a click event to each div within the li. I would actually rewrite it like so:
<ul>
{
Object.keys(this.props.group).map( (keyName, keyIndex) => {
return(
<li key={keyIndex} onClick={this.handleSubmit.bind(this, keyName}>
{keyName}
</li>
)
})
}
</ul>
Then your handleSubmit function will get the keyName as a param:
handleSubmit(keyName){
// Do something...
}

on item click, items re-renders and scroll comes to the top which is undesired

I have made a component where I am rendering grids of items. On clicking one item, the item is being selected. However there are many items present so there is scroll bar. Whenever I click on an Item, the component is re-rendered (as I am putting the selectedItem in my state), which further re-renders all the other items. But when I click an item after scrolling to the bottom (or middle), the component renders to the top, however I want that to remain on the position it was being clicked.
The components are as follows :
Full-Screen (made using react-portal, contains onClick and changes its state)
--TilesView (all tiles wrapper which renders all the tiles and has an ajax call)
--all Tiles (single tile element)
The part code is as follows :
FullScreen:
componentDidMount() {
if (this.props.selectedPost) {
this.setState({
selectedPost: {
[this.props.selectedPost[0]]: true
}
});
}
}
render() {
const that = this;
//Todo: User fullpage header when space is updated
return (
<Portal container={() => document.querySelector('body')}>
<div className={styles.container}>
<FullPageForm onHide={that.props.onCancel} closeIcnLabel={'esc'} bgDark={true}>
<FullPageForm.Body>
<span className={styles.header}>{'Select Post'}</span>
<div className={styles.body}>
<ExistingAssets onCreativeTileClicked={this.handlePostClick}
selectedCreatives={this.state.selectedPost}
showSelectedTick/>
</div>
</FullPageForm.Body>
</FullPageForm>
</div>
</Portal>
);
}
handlePostClick = (adCreativeAsset, id) => {
event.preventDefault();
this.setState({
selectedPost: {
[id]: adCreativeAsset
}
});
}
In my handlePostClick, I tried doing event.preventDefault() but it didn't work. I have no clue why this is happening, thanks in advance.
Try changing your handlePostClick definition to
handlePostClick = (e, adCreativeAsset, id) => {
e.preventDefault();
//blah blah what you want
}
and in your JSX change onCreativeTileClicked={this.handlePostClick} to onCreativeTileClicked={this.handlePostClick.bind(this)}.
The event you were prevent-defaulting (stopping propagation in real terms) isn't the real event coming from the click but a synthetic one that can be summoned to fill in for an event when there isn't one. You need to stop propagation for the real event.
Hope this helps.

Categories