I'm attempting to hide an item when a user scrolls within a div. The Card component shown is a scrollable component. When the user scrolls within it I want to hide an item. What is the best way to go about this? I'm getting the error: Too many re-renders. Note: Partial code shown
const [scrolling, setScrolling] = useState(false);
const handleScroll = (e) => {
setScrolling(true);
};
return (
<Card onScroll={handleScroll}>
{scrolling ? null : <p> hide me on scroll </p>}
</Card>
);
something you could do is move the scrolling ? null : ... outside of the return and just keep it a variable.
Like this:
const [scrolling, setScrolling] = useState(false);
const handleScroll = (e) => {
setScrolling(true);
};
const item = scrolling ? null : <p> hide me on scroll </p>
return (
<Card onScroll={handleScroll}>
{item}
</Card>
);
Related
First, please check my code.
const [dragItem, setDragItem] = useState(undefined);
const [dragFile, setDragFile] = useState(undefined);
const [targetFolder, setTargetFolder] = useState(undefined);
const handleDragStart = (index) => {
setDragItem(index);
setTargetFolder(undefined);
};
const handleFileDragStart = (index) => {
setDragFile(index);
setTargetFolder(undefined);
}
const handleDrop = (index) => {
setTargetFolder(index);
setIsReady(true);
};
///////////////////////
<div>
{rootFolder?.map((root) => (
<div className={classes.root} key={root.FOLDER_PK}>
<ListItem
button
dense
divider
selected
draggable
onDragStart={() => handleDragStart(root.FOLDER_PK)}
onDrop={() => handleDrop(root.FOLDER_PK)}
onDragOver={(e) => e.preventDefault()}
>
<ListItemIcon>
<Folder />
</ListItemIcon>
<ListItemText/>
<ThreeDotsMenu/>
</ListItem>
</div>
))}
</div>
This code is being rendered like this.
However, when I quickly click and drag the item, there's no problem. However, when I click the item for about 1~2 seconds and drag, It is being dragged like this.
This is a moment when I click 'folder 4' for about 1 second and drag it.
With this comparison, you can see how It's different when I'm click and drag quickly or not.
I'm new to this Browser API, so I need some help. If you want some more information, please add a comment. I'll add my code right away.
Your help will be much appreciated. Thank you!
i want to disable/enable a function, depends on state.
the problem is, i have this function that create some css effect,
which works only in .
it takes the DIV that classNamed "glass" and does some stuff.
this div appears only in , but NOT on Homepage2.
so what happens is when i change state to view Homepage2, i get this:
TypeError: Cannot read property 'style' of null.
so im guessing i need to stop the function?
the App
function App() {
const Theme = useSelector(state => state.ThemeReducer);
return (
<div className="App">
{Theme === "Theme1" &&
<Homepage />
}
{Theme === "Theme2" &&
<Homepage2 />
}
</div>
);
}
Homepage
const Homepage1 () => {
//this is the function i want to stop/start depends on state
document.addEventListener("mousemove", function(e){
const glass = document.querySelector(".glass");
glass.style.left = e.offsetX + "px";
glass.style.top = e.offsetY + "px";
// dont get too much into this function its not importent
return(
<div className="glass" />
<h1>this is HOMEPAGE 1</h1>
<button>this button changing theme with redux</button>
)
hompage 2
const Homepage2 = () => (
<div> homepage 2 </div>
<button>this button changing theme with redux</button>
)
If you want to exit the mousemove handler if glass is null then simply check that condition:
if (!glass) {
return;
}
Context: I am trying to scroll view to props.toBeExpandItem item which keeps changing depending on click event in parent component. Every time a user clicks on some button in parent component I want to show them this list and scroll in the clicked item to view port. Also I am trying to avoid adding ref to all the list items.
I am using react ref and want to add it conditionally only once in my component. My code goes as below. In all cases the option.id === props.toBeExpandItem would be truthy only once in loop at any given point of time. I want to understand will it add any overhead if I am adding ref=null for rest of the loop elements?
export const MyComponent = (
props,
) => {
const rootRef = useRef(null);
useEffect(() => {
if (props.toBeExpandItem && rootRef.current) {
setTimeout(() => {
rootRef.current?.scrollIntoView({ behavior: 'smooth' });
});
}
}, [props.toBeExpandItem]);
return (
<>
{props.values.map((option) => (
<div
key={option.id}
ref={option.id === props.toBeExpandItem ? rootRef : null}
>
{option.id}
</div>
))}
</>
);
};
Depending upon your recent comment, you can get the target from your click handler event. Will this work according to your ui?
const handleClick = (e) => {
e.target.scrollIntoView()
}
return (
<ul>
<li onClick={handleClick}>Milk</li>
<li onclick={handleClick}>Cheese </li>
</ul>
)
I've just started learning React. I'm working on a project for practice. It has two different components Nav & Main.I'm calling these components in App.js. My Nav component has a number of coloured divs which show up on clicking the plus icon. Now, I want to create a new div in Main.js on click of a coloured div in Nav.js. This new div should have the same background colour as the one we click on. I'm confused how to do it. Please! Help me out.
This is my Nav component.
const Nav = () => {
const [showColors, setShowColors] = useState(false);
return(
<div className="NavBar">
<h1 className="NavHeading">Notes.</h1>
<div onClick={() => setShowColors(!showColors)} className="PlusImg"></div>
<div className={showColors ? "" : "ColorBar"}>
<div className="Color Color1"></div>
<div className="Color Color2"></div>
<div className="Color Color3"></div>
<div className="Color Color4"></div>
<div className="Color Color5"></div>
</div>
</div>
);
};
export default Nav;
This is my Main component.
const Main = () => {
return(
<div className="Main">
<div className="NoteItem"></div>
</div>
);
};
export default Main;
I want to create new div with class name of NoteItem as the one written in the Main component.
I'm not 100% sure I understand what you're trying to do. Apologies if I've misunderstood.
I'd approach this by keeping track of the items that have been added in component state:
const App = () => {
// starting with an empty array
const [items, setItems] = React.useState([]);
And defining a handler for adding an item:
const App = () => {
const [items, setItems] = React.useState([]);
// itemClass is just a string, e.g. "color1" or "color5"
const addItem = itemClass => setItems([...items, itemClass]);
The spread syntax used above creates a new array containing the previous array plus the new item:
const oldArr = ['color1','color2'];
const newArr = [...oldArr, 'color3']
// newArr is now ['color1', 'color2', 'color3'];
You can then pass the items to Main as a prop, which can render a div for each item:
const App = () => {
const [items, setItems] = React.useState([]);
const addItem = itemClass => setItems([...items, itemClass]);
return (
<Main items={items} /> {/* pass items array to Main */}
);
}
const Main = ({items}) => { // {items} is the equivalent of props.items
// render a div for each item in the items array
return items.map( item => (
<div className={item}>This item has a class of {item}</div>
)
}
And pass the addItem handler to Nav so it can tell App to add the clicked item:
const Nav = ({addItem}) => {
return (
<div className={showColors ? "" : "ColorBar"}>
<div onClick={() => addItem('Color1')} className="Color Color1"></div>
{/* repeat for Color2, etc. */}
</div>
);
}
With this place, when a Nav div is clicked an item will get added to App's items array, which will trigger a re-render, passing the updated array to Main, and you'll see the new div.
a bit of housekeeping:
In the Main component above you're going to get a react key warning. I omitted the key in the interest of readability, but you'll need to include a key prop that's unique for each item. The easiest way to do this is to just use the index of the iteration:
const Main = ({items}) => {
// add key={index} to make react happy
return items.map((item, index) => (
<div key={index} className={item}>This item has a class of {item}</div>
)
}
Also, the items.map call will blow up if items isn't provided. You can dodge this by setting it to an empty array by default:
const Main = ({items = []}) => {
// ...
}
First of all you need to define your state in your App component and pass it to your components as an argument because both your Nav and Main components needs to access your state. You can do it like this
class App extends React.Component{
constructor(){
super()
this.state = {
showColors: false
}
}
}
After that you can take elements color with the onClick event listener. Define it in your state as well. And create a div element inside your Main component with it.
Dont forget to pass it as an argument to your Main component too.
As shown in the minimal example (gif), the scroll-behavior when adding items depends on the current scroll position.
Here is the code for that example:
const [items, setItems] = React.useState([]);
const addItem = i => {
setItems(s => [...s, i]);
};
return (
<div className="App">
<header style={boxStyle}>HEADER</header>
{items.map(item => (
<Item text={item} key={item} />
))}
<button onClick={() => addItem(`item${items.length}`)}>Add</button>
<footer style={boxStyle}>FOOTER</footer>
</div>
);
https://codesandbox.io/s/ancient-feather-lttw5?file=/src/App.js
For the first 2 items the add button moves down. But then the button feels like staying at the same position when new items are added before (effectively the scroll position changes what makes it feel like the item is added "before" rather than "in-place").
In our real world app that UX feels super stange, because each item is a big item-from with 10+ fields and the user would see only the bottom of that form. Instead, we would like the user to see the beginning of the form after clicking the add button (like in the example when adding item0 and item1 where the beginning of the item is shown where the mouse is).
Desired scroll UX
I found out that hiding the add button for a render after adding the item fixes the issue:
const [items, setItems] = React.useState([]);
const addItem = i => {
setHide(true);
setItems(s => [...s, i]);
setTimeout(() => setHide(false), 1);
};
const [hide, setHide] = React.useState(false);
return (
<div className="App">
<header style={boxStyle}>HEADER</header>
{items.map(item => (
<Item text={item} key={item} />
))}
{!hide && (
<button onClick={() => addItem(`item${items.length}`)}>Add</button>
)}
<footer style={boxStyle}>FOOTER</footer>
</div>
);
https://codesandbox.io/s/stupefied-kirch-6s5fs?file=/src/App.js
Question
Is there a (cross-browser (and ie11+)) way to achieve the desired behavior in a more elegant way? Ideally without manipulating the scroll position manually.
I think that your best candidate to do so is the method scrollIntoView(). Maybe when you click your button you can identify your item in the dom and scroll to it whenever the document is scrollable and the first item is out of view. Like:
const handleClick = (item) => {
const firstItem = undefined; //you should find the way to get the first item of your list in the dom
firstItem.scrollIntoView({ block: "start" }); // you can add some options for smoothess and position
addItem(item);
}
UPDATE:
Now that I understand the expected result clear, I still think that scrollIntoView is the solution, but in this case you will need to do it after item adding, you may need the hook useEffect for this, like:
useEffect(()=> {
const myAddButton = document.getElementById('myAddButton'); //get the button element
myButton.scrollIntoView({
block: "end"
});
}, [items])
/* Handle click is not needed anymore
const handleClick = (item) => {
addItem(item);
}*/
You can still tweak this a little bit so you can have a gap between the bottom of the page, but this will make a good start.