React How to set scroll point onScroll event - reactjs

I need to store scroll position on state. I am doing that in the following way. But don't know how it's not scrolling the div.
const [scroll, setScroll] = (0)
const scrollRef = useRef(null);
const onScroll = (e) => {
e.preventDefault();
setScroll(myRef.current.scrollLeft);
};
<div ref={myRef} onScroll={onScroll}
>
<h5>item 1 </h5>
<h5>item 2 </h5>
<h5>item 3 </h5>
</div>

You are calling e.preventDefault. This means that the default behaviour is prevented. In this case, the scroll.
Remove that line and it should work.

This can be used
componentDidMount() {
let left_scroll = document.querySelector('.className')
left_scroll.addEventListener('scroll', this.handleScroll);
}
handleScroll = (e) => {
let left_scroll = document.querySelector('.className')
if (left_scroll.scrollTop == 0){
document.getElementById('button_className').classList.remove("hidden")
}else if(left_scroll.scrollHeight > 800 && left_scroll.scrollTop >= 700){
document.getElementById('button_className').classList.add("hidden")
}
}
what I have done here is, get that div and I have tried to show and hide a button as per page scroll heigh

Related

React - Resetting children state when parent changes its state in functional components

I'm working with a list of notes in React Native, and I was using a bad-performant method to select/deselect the notes when I'm "edit mode". Everytime I selected a note, the application had to re-render the entire list everytime. If I do a test with 100 notes, I get input lags when I select/deselect a note, obviously.
So I decided to move the "select state" to the single Child component. By doing this, I'm having the re-render only on that component, so it's a huge improvement of performance. Until here, everything's normal.
The problem is when I'm disabling edit mode. If I select, for example, 3 notes, and I disable the "edit mode", those notes will remain selected (indeed also the style will persist). I'd like to reset the state of all the selected note, or finding a valid alternative.
I recreated the scene using React (not React Native) on CodeSandbox with a Parent and a Child: https://codesandbox.io/s/loving-field-bh0k9k
The behavior is exactly the same. I hope you can help me out. Thanks.
tl;dr:
Use-case:
Go in Edit Mode by selecting a note for .5s
Select 2/3 elements by clicking on them
Disable Edit Mode by selecting a note for .5s
Expectation: all elements get deselected (state of children resetted)
Reality: elements don't get deselected (state of children remains the same)
this is easy enough to do with a useEffect hook.
It allows you to "watch" variable changes over time.
When editMode changes the contents of the Effect hook runs, so when editMode goes from true to false, it will set the item's selected state.
Add this to your <Child /> component:
useEffect(() => {
if (!editMode) {
setSelected(false);
}
}, [editMode]);
If you use React.memo you can cache the Child components and prevent their re-renders.
const Parent = () => {
const [editMode, setEditMode] = useState(false);
const [childrenList, setChildrenList] = useState(INITIAL_LIST);
const [selected, setSelected] = useState([]);
const toggleEditMode = useCallback(() => {
if (editMode) {
setSelected([]);
}
setEditMode(!editMode);
}, [editMode]);
const deleteSelectedChildren = () => {
setChildrenList(childrenList.filter((x) => !selected.includes(x.id)));
setEditMode(false);
};
const onSelect = useCallback((id) => {
setSelected((prev) => {
if (prev.includes(id)) {
return prev.filter((x) => x !== id);
}
return [...prev, id];
});
}, []);
// Check when <Parent /> is re-rendered
console.log("Parent");
return (
<>
<h1>Long press an element to enable "Edit Mode"</h1>
<ul className="childrenWrapper">
{childrenList.map((content, index) => (
<Child
key={content.id}
index={index}
content={content}
editMode={editMode}
toggleEditMode={toggleEditMode}
onSelect={onSelect}
selected={selected.includes(content.id)}
/>
))}
</ul>
{editMode && (
<button onClick={deleteSelectedChildren}>DELETE SELECTED</button>
)}
</>
);
};
You have to wrap the functions you pass as props inside useCallback, otherwise they will be different on every Parent render, invalidating the memoization.
import { useRef, memo } from "react";
const Child = memo(
({ content, editMode, toggleEditMode, onSelect, selected }) => {
// Prevent re-rendering when setting timer thread
const timerRef = useRef();
// Toggle selection of the <Child /> and update selectedChildrenIndexes
const toggleCheckbox = () => {
if (!editMode) return;
onSelect(content.id);
};
// Toggle Edit mode after .5s of holding press on a Child component
const longPressStartHandler = () => {
timerRef.current = setTimeout(toggleEditMode, 500);
};
// Release setTimeout thread in case it's pressed less than .5s
const longPressReleaseHandler = () => {
clearTimeout(timerRef.current);
};
// Check when <Child /> is re-rendered
console.log("Child - " + content.id);
return (
<li
className={`childContent ${editMode && "editMode"} ${
selected && "selected"
}`}
onMouseDown={longPressStartHandler}
onMouseUp={longPressReleaseHandler}
onClick={toggleCheckbox}
>
<pre>
<code>{JSON.stringify(content)}</code>
</pre>
{editMode && (
<input type="checkbox" onChange={toggleCheckbox} checked={selected} />
)}
</li>
);
}
);
You can see a working example here.

Applying state change to specific index of an array in React

Yo there! Back at it again with a noob question!
So I'm fetching data from an API to render a quizz app and I'm struggling with a simple(I think) function :
I have an array containing 4 answers. This array renders 4 divs (so my answers can each have an individual div). I'd like to change the color of the clicked div so I can verify if the clicked div is the good answer later on.
Problem is when I click, the whole array of answers (the 4 divs) are all changing color.
How can I achieve that?
I've done something like that to the divs I'm rendering :
const [on, setOn] = React.useState(false);
function toggle() {
setOn((prevOn) => !prevOn);
}
const styles = {
backgroundColor: on ? "#D6DBF5" : "transparent",
};
I'll provide the whole code of the component and the API link I'm using at the end of the post so if needed you can see how I render the whole thing.
Maybe it's cause the API lacks an "on" value for its objects? I've tried to assign a boolean value to each of the items but I couldn't seem to make it work.
Thanks in advance for your help!
The whole component :
import React from "react";
import { useRef } from "react";
export default function Quizz(props) {
const [on, setOn] = React.useState(false);
function toggle() {
setOn((prevOn) => !prevOn);
}
const styles = {
backgroundColor: on ? "#D6DBF5" : "transparent",
};
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
let answers = props.incorrect_answers;
const ref = useRef(false);
if (!ref.current) {
answers.push(props.correct_answer);
shuffleArray(answers);
ref.current = true;
}
const cards = answers.map((answer, key) => (
<div key={key} className="individuals" onClick={toggle} style={styles}>
{answer}
</div>
));
console.log(answers);
console.log(props.correct_answer);
return (
<div className="questions">
<div>
<h2>{props.question}</h2>
</div>
<div className="individuals__container">{cards}</div>
<hr />
</div>
);
}
The API link I'm using : "https://opentdb.com/api.php?amount=5&category=27&type=multiple"
Since your answers are unique in every quizz, you can use them as id, and instead of keeping a boolean value in the state, you can keep the selected answer in the state, and when you want render your JSX you can check the state is the same as current answer or not, if yes then you can change it's background like this:
function Quizz(props) {
const [activeAnswer, setActiveAnswer] = React.useState('');
function toggle(answer) {
setActiveAnswer(answer);
}
...
const cards = answers.map((answer, key) => (
<div key={key}
className="individuals"
onClick={()=> toggle(answer)}
style={{background: answer == activeAnswer ? "#D6DBF5" : "transparent" }}>
{answer}
</div>
));
...
}

React: How to make collapsible elements that are fast?

I have an element at the top of my page that I want to be collapsible. The trouble is that if there are enough elements below it on the page (about 2000 or more), the act of collapsing/expanding causes the mouse to freeze for a few seconds. How can I add a collapsible element like this and still have a responsive UI?
My methods for collapsing that I have tried are rendering the collapsed element as "null" and rendering with height = 0. Both are slow.
The number of elements following the collapsible element in the example is not that big ~5000 - basically a table with a few hundred rows.
Code sandbox example here: https://codesandbox.io/s/2zi2s
I don't know if this can help. But on my work we implemented a component that can be collapsible with useLayoutEffect.
const InnerCardWrapper: React.FC<IInnerCardWrapper> = ({ isOpen, wrapperCssClasses = '', innerCssClasses = '', children }) => {
const innerCardHeightMeasures = useRef(0);
const [innerCardHeight, setInnerCardHeight] = useState(0);
const elementId = uuid();
useLayoutEffect(() => {
const cardInnerContainer = document.getElementById(elementId);
if (cardInnerContainer) {
innerCardHeightMeasures.current = cardInnerContainer.clientHeight;
}
if (innerCardHeightMeasures.current > 0) {
setInnerCardHeight(innerCardHeightMeasures.current);
}
}, [isOpen]);
useEffect(() => {
setInnerCardHeight(innerCardHeight === 0 ? innerCardHeightMeasures.current : 0);
}, [isOpen]);
return (
<div
style={{ height: innerCardHeight }}
className={`overflow-hidden transition-all ${isOpen ? 'border-b border-gray-light mt-6' : ''} ${wrapperCssClasses}`}
>
<div id={elementId} className={`py-3 ${innerCssClasses}`}>
{children}
</div>
</div>
);
};
export default InnerCardWrapper;
We use TailwindCSS you can check the CSS equivalent here.
Hope this works, please let me know.

How to add this setInterval function to this React code?

I have an image slider now, but the images change only at the click of a button, and I want to add this function so that it works automatically:
var timer;
timer = setInterval(function() {
plusSlides(1);
}, 5000);
This is the React image slider code (full demo at https://codepen.io/anon/pen/dBmvje):
function Slider({ items }) {
const [ active, setActive ] = React.useState(0);
const { length, [active]: slide } = items;
const next = e => setActive((active + +e.target.dataset.step + length) % length);
const goTo = e => setActive(+e.target.dataset.index);
return (
<div>
<div className="slideshow-container">
<div className="mySlides fade">
<div className="numbertext">{active + 1} / {length}</div>
<img src={slide.img} />
<div className="text">{slide.title}</div>
</div>
<a className="prev" onClick={next} data-step={-1}>❮</a>
<a className="next" onClick={next} data-step={+1}>❯</a>
</div>
<div className="dots">
{items.map((n, i) => (
<span
key={n.id}
className={`dot ${i === active ? 'active' : ''}`}
onClick={goTo}
data-index={i}
></span>
))}
</div>
</div>
);
}
And here is the whole Javascript code from where I "borrowed" this function from, so that it is clear where the "plusSlides (1);" function came from :
var timer;
var slideIndex = 1;
showSlides(slideIndex);
function plusSlides(n) {
showSlides((slideIndex += n));
}
function showSlides(n) {
var i;
var slides = document.getElementsByClassName("mySlides");
if (n > slides.length) {
slideIndex = 1;
}
if (n < 1) {
slideIndex = slides.length;
}
for (i = 0; i < slides.length; i++) {
slides[i].style.display = "none";
}
slides[slideIndex - 1].style.display = "block";
}
timer = setInterval(function() {
plusSlides(1);
}, 5000);
In functional React components, you can set up side effects with the effect hook (React.useEffect). Add this code before the return statement:
React.useEffect(() => {
const timeout = setTimeout(() => setActive((active + 1 + length) % length), 5000);
return () => clearTimeout(timeout);
}, [active, length]);
The first argument to useEffect is the function to run, and the second is the list of dependencies (the function will re-run if a dependency changes). The return value from the function tells how to clean up the effect when the component is unmounted or one of the dependencies changes.
Due to the requirement of listing the effect's dependencies, it seemed better to use setTimeout rather than setInterval in this case. When the timeout runs, the value of active will change, causing the effect function to re-run and start another timeout. A side benefit of this approach is it works better with user interaction: if the user manually advances the slide, the timeout will be reset and the slide will wait 5s to change again (whereas with the interval, the slide might change again right away if the user happened to click right before the interval's next scheduled run).

React dim element that is rendered from a map

I am working on showing images in a card using React. The images come from a API and are resolved when rendering the images page. Now I want to use the Dimmer component from the React Semantic-UI design library and dim an image on a mouseover. I tried the following example from the document page:
const dimmedID = this.state.dimmedID
const collections = this.state.collections
const collectionsList = collections.map((project) =>
<Dimmer.Dimmable
key = { project.id }
as = {Image}
dimmed = {dimmedID === project.id ? true : false}
dimmer = {{ active, content }}
onMouseEnter = {() => { this.handleShow(project.id) }}
onMouseLeave = {() => { this.handleShow('') }}
src = { project.thumbnail }
/>
)
When triggering the onMouseEnter, the dimmedID state object is set to the id of the image. However, all images that are rendered are being dimmed. Not the image which the mouse is on. I tried with a shorthand if-else on the dimmed parameter but that does not seem to work. When hovering with the mouse over one image, all images get dimmed.
Does someone know how to fix this?
Ok so apparently the fix is easy... so much for reading...
const dimmedID = this.state.dimmedID
const collections = this.state.collections
const collectionsList = collections.map((project) =>
<Dimmer.Dimmable
key = { project.id }
as = {Image}
dimmed = {dimmedID === project.id ? true : false}
dimmer = {{ active: dimmedID === project.id, content }}
onMouseEnter = {() => { this.handleShow(project.id) }}
onMouseLeave = {() => { this.handleShow('') }}
src = { project.thumbnail }
/>
)

Resources