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
Related
I have a react app that I am working on, and currently, I have a custom-built dropdown that I want to open/close when a user clicks on the trigger(the arrow button), close it when a user selects an option, or close it when a user clicks outside the displayed component.
Here is my code:
For the sake of simplicity, I only added the code that I want help with.
class NavBar extends Component {
constructor(props) {
super(props);
this.state = {
showCurrencies: false,
};
this.handleShowCurrencies = this.handleShowCurrencies.bind(this);
}
componentDidMount() {
this.currencyRef = createRef();
}
componentDidUpdate(prevProps, prevState) {
if (this.state.showCurrencies) return this.currencyRef.current.focus();
}
handleShowCurrencies = () => {
this.setState({
showCurrencies: !this.state.showCurrencies,
});
};
render() {
<div className="currency-switch" onClick={this.handleShowCurrencies}>
{currencySymbol}
<span>
<button>
<img src={`${process.env.PUBLIC_URL}/images/arrow.png`} />
</button>
</span>
</div>
{this.state.showCurrencies ? (
<div
className="dropdown"
tabIndex={"0"}
ref={this.currencyRef}
onBlur={this.handleShowCurrencies}
>
{currencies?.map((currency) => (
<div
key={currency.symbol}
className={`dropdown-items ${currencySymbol === currency.symbol ? "selected" : "" }`}
onClick={() => changeCurrencySymbol(currency.symbol)}
>
{`${currency.symbol} ${currency.label}`}
</div>
))}
</div>
) : null}
}
Currently, directing focus to a div element is working fine, and clicking outside the element as well. However, clicking back on the trigger or even selecting an option is not closing the div element. It seems like it is rendering twice(take a closer look on the console): https://drive.google.com/file/d/1ObxU__SbD_Upxr6qcy5eYO4LSy6Mzq9C/view?usp=sharing
Why is that happening? How can I solve it?
P.S: I don't often ask on StackOverflow, so am not familiar with the process. Please bear with me. If you need any other info, I will be more than happy to provide it.
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
I'm making a chrome extension using react. In my app I'm using the Rsuite Dropdown component. I'm using nested menus with lots of items, so on some of the menus the list of items in the dropdown extends past the screen. The issue is that I hover over the menu, the dropdown opens, it extends past the screen, the window scrolls to the bottom of the dropdown, my mouse is now shifted off of the menu selector so the dropdown closes and the window scrolls back up. My goal is to have the dropdown open without the window scrolling down to the bottom.
Gif of problem
My Code:
I just have one component in my App.js, which is just a dropdown of all my bookmarks
<Dropdown title="Bookmarks">
{bookmarks.map((mark) => {
return (mark.children === undefined ? <BookmarkItem Bookmark={mark}/> : <BookmarkFolder Bookmarks={mark}/>)
})}
</Dropdown>
For all the bookmarks, I check if its a folder or an actual bookmark, if its a folder, I insert a folder component (which does this exact thing recursively)
const BookmarkFolder = ({Bookmarks}) => {
return (
<Dropdown.Menu title={Bookmarks.title}>
{Bookmarks.children.map((mark) => {
return (mark.children === undefined ? <BookmarkItem Bookmark={mark}/> : <BookmarkFolder Bookmarks={mark}/>)
})}
</Dropdown.Menu>
);
};
If its a bookmark, I display the bookmark title as a dropdown item
/* Limit title length to 25 characters */
const limitLength = (title) => {
if (title.length > 25) {
return `${title.substring(0, 22)}...`;
}
else {
return title;
}
}
return (
<Dropdown.Item>
{limitLength(Bookmark.title)}
</Dropdown.Item>
);
I've been trying to programmatically scroll the page up when a bookmark item renders:
useEffect(() => {
window.setTimeout(() => window.scrollTo(0,0), 500);
}, []);
But this isn't working.
Help is greatly appreciated!
i want to parse data from child to parent i have try solution from question
How to parse data from child to parent using reactjs?
I print that state and what appears is the state of the previous action, not the state of the last action
I tried to implement this to bring up content based on the menu that was clicked
example:
i have 3 menu
- A
- B
- C
when i click first time at the menu, for example A. the state in console is '', Then Second time i click B, the state in console is A
this is my code
PARENT
changeMenu= (menu) =>{
this.setState({
menu: menu
});
console.log('menu',menu); // Show State
}
render(){
return (
<LeftMenuMycommission active="0" menu = {(value) => this.changeMenu(value)}/>
CHILD
menuClick = (menu_name, active) =>{
this.setState({
menu: menu_name,
})
this.props.menu(this.state.menu);
}
render (){
render (
<ul>
<li ><a onClick={this.menuClick.bind(this, "A")}><i className={"fa fa-circle"}></i> A</a></li>
<li ><a onClick={this.menuClick.bind(this, "B")}><i className={"fa fa-circle"}></i> B</a></li>
<li ><a onClick={this.menuClick.bind(this, "C")}><i className={"fa fa-circle"}></i> C</a></li>
</ul>
Can anyone help me to find the problem?
Any help would be appreciated thank you :)
It's not guaranteed that state is updated immediately. You would need to use callback function and then call your parent method to pass the child component state to the parent correctly:
menuClick = (menu_name, active) =>{
this.setState({
menu: menu_name,
}, () => {
this.props.menu(this.state.menu);
})
}
Or, componentDidUpdate will do the same job:
componentDidUpdate() {
this.props.menu(this.state.menu) // only called after component is updated
}
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.