How to scroll to the desired element in React? - javascript

I have a question - I have a list that is in a separate popup with a limited height. After this height, a side scroll appears. I need to scroll to a specific element automatically when that component is rendered. How to implement it? I just can't figure out how to scroll to a certain element.
Below is an example of my jsx code
<ul className={style.list}>
{itemsForRender?.length ? (
itemsForRender.map((item) => (
<li className={style.item} key={item.id}>
<button
type="button"
className={
activeItem === item.id
? `${style.button} ${style.activeClass}`
: style.button
}
onClick={() => selectItem(item.id)}
>
{item.name}
</button>
</li>
))
) : (
<p className={style.searchSubtitle}>
Just text
</p>
)}
</ul>

Use this code :
const ScrollDemo = () => {
const myRef = useRef(null)
const executeScroll = () => myRef.current.scrollIntoView()
// run this function from an event handler or an effect to execute scroll
return (
<>
<div ref={myRef}>Element to scroll to</div>
<button onClick={executeScroll}> Click to scroll </button>
</>
)
}

I had used Element.scrollIntoView() Method in React with making a reference of element i want to scroll to, here is the example:
function TestComponent() {
const testRef = useRef(null);
const scrollToElement = () => testRef.current.scrollIntoView();
// Once the scrollToElement function is run, the scroll will show the element
return (
<>
<div ref={testRef}>Element you want to view</div>
<button onClick={scrollToElement}>Trigger the scroll</button>
</>
);
}

you can use scrollIntoView() where you want scroll automatically
document.getElementById(element-id).scrollIntoView({behavior: 'smooth'})
you can use this in a useEffect() to run it when component is rendered

I hope this would be helpful. thanks
const scrollRef = useRef([]);
useEffect(() => {
// here you call the function scrollToSection and pass the id where you want to scroll
}, [])
const scrollToSection = id => {
if (scrollRef.current.length) {
scrollRef.current[id].scrollIntoView();
}
};
<ul className={style.list}>
{itemsForRender?.length ? (
itemsForRender.map((item) => (
<li className={style.item} ref={ref => (scrollRef.current[item.id] = ref)} key={item.id}>
<button
type="button"
className={
activeItem === item.id
? `${style.button} ${style.activeClass}`
: style.button
}
onClick={() => selectItem(item.id)}
>
{item.name}
</button>
</li>
))
) : (
<p className={style.searchSubtitle}>
Just text
</p>
)}
</ul>

Related

How do I only call the onClick() function to a single React component?

There is a function called Question that takes a list of objects (questionList) and changes each object into a React component that shows a question and when clicked should reveal the answer in a FAQ style. There is another function called Answer which shows the answer when a Question container is clicked. However, instead of showing the answer of just the container which was clicked, it shows every answer of all the questions - as if rendered all the questions again with their answers. When a container is clicked again, all Answer containers are hidden.
How do I only show the answer of the specific Question container?
Example:
How does this work?
What do you do?
click on 2
How does this work?
Answer to 1
What do you do?
Answer to 2
Instead of:
How does this work?
What do you do?
Answer to 2
function Question(props) {
const [show, setShow] = useState(false);
const onClick = () => {
if (show) {
setShow(false);
} else {
setShow(true);
}
};
const questionList = props.questionList;
const questions = questionList.map(question => (
<div onClick={onClick} className="container">
<div className="line"></div>
<div className="question-container">
<h3 className="question">{question.question}</h3>
<div className="drop-icon">
<i className="fa-solid fa-caret-down"></i>
</div>
</div>
{show ? <Answer question={question} /> : null}
</div>
));
return <section>{questions}</section>;
}
function Answer(props) {
const question = props.question;
const answer = (
<div className="answer">
<p>{question.answer}</p>
</div>
);
return answer;
}
It seems that the state show is just one value while each item should probably have their own show for the desired result.
Try make show an object (or array) that stores a boolean value for each item in the list:
function Question({ questionList }) {
const [show, setShow] = useState({});
const onClick = (index) =>
setShow((prev) => ({ ...prev, [index]: !Boolean(prev[index]) }));
const questions = questionList.map((question, index) => (
<div key={index} onClick={() => onClick(index)} className="container">
<div className="line"></div>
<div className="question-container">
<h3 className="question">{question.question}</h3>
<div className="drop-icon">
<i className="fa-solid fa-caret-down"></i>
</div>
</div>
{show[index] ? <Answer question={question} /> : null}
</div>
));
return <section>{questions}</section>;
}
Or consider to make each question a component QuestionItem (like Answer in the example) that has its own show state:
const QuestionItem = ({ question }) => {
const [show, setShow] = useState(false);
const onClick = () => setShow((prev) => !prev);
return (
<div onClick={onClick} className="container">
<div className="line"></div>
<div className="question-container">
<h3 className="question">{question.question}</h3>
<div className="drop-icon">
<i className="fa-solid fa-caret-down"></i>
</div>
</div>
{show ? <Answer question={question} /> : null}
</div>
);
};
And iterate the component QuestionItem in Question:
function Question({ questionList }) {
const questions = questionList.map((question, index) => (
<QuestionItem key={index} question={question} />
));
return <section>{questions}</section>;
}

onClick not working on first click in react nested component

I have a parent component. This parent component combines a lot of if else control and a lot of mini jsx. I added my case. If I click the NextBtnText in the Modal component. It doesn't fire the first click. It needs a second click.
How can I fire the first click? What is wrong?
const StepperComponent = ({closeModal}) => {
/**
* there is some ,useState, useEffect and conditional functions
*/
const test = () => setActiveStep((prevActiveStep) => prevActiveStep + 1);
const NextBtnText = () => {
return (<Button
onClick={test}
disabled={firmType}
className={styles.StepperNextButton}
>
<span>{t("createFirm.buttons.next")}</span>
<KeyboardArrowRightIcon />
</Button>
);
};
const BackBtnText = () => {
return (
<>
<span>{t("createFirm.buttons.back")}</span>
</>
);
}
const RequestAssignmentBtnText = () => {
return (
<Button
onClick={handleSubmit}
disabled={firmType}
className={styles.StepperRequestButton}
>
<span>{t("createFirm.buttons.requestAssignment")}</span>
</Button>
)
}
return (
<div className={styles.StepperContainer}>
<Stepper activeStep={activeStep} className={styles.Steps}>
{steps.map((label, index) => {
return (
<Step key={index}>
<StepLabel >{label}<span className={styles.StepCountMobile}>{`(${index + 1} / ${steps.length})`}</span></StepLabel>
</Step>
);
})}
</Stepper>
{getStepContent(activeStep)}
<div className={styles.StepperButtons}>
<Button
disabled={activeStep === 0}
onClick={handleBack}
className={styles.StepperBackButton}
>
<span>{t("createFirm.buttons.back")}</span>
</Button>
{activeStep === steps.length - 1 ? (<RequestAssignmentBtnText />) : (<NextBtnText />)}
</div>
</div>
);
}
Yo're not calling your function. Simply do:
<Button
onClick={(event) => handleSubmit(event)}
>
// Your function has to look like this
const handleSubmit = (event) => {
// your code...
}
Or you can do this:
<Button
onClick={handleSubmit()}
>
// Your function has to look like this
const handleSubmit = () => {
return (event) => {
// your code...
}
}
What is the default value you set in useState() for activeStep?
I believe the onClick handler is working but the state is not what you expect on the first click. Maybe set the default value to 1 or 0 (I am not sure what is suitable for your use case).
const [activeStep, setActiveStep] = useState(1);
If the onClick is actually not working with the first click, try using plain HTML <input /> with test onClick handler and see if that works. It might have something to do with the Button component itself.
I fixed the onClick problem. The cause is about rerendered after disabled={firmType}. My button has a disabled attribute. I need to control after the checkbox is true/false.
Before :
{activeStep === steps.length - 1 ? (<RequestAssignmentBtnText />) : (<NextBtnText />)}
Solution :
{activeStep === steps.length - 1
?
<Button
onClick={handleSubmit}
className={styles.StepperNextButton}
>
<span>{t("createFirm.buttons.requestAssignment")}</span>
</Button>
:
<Button
onClick={handleNext}
disabled={firmType}
className={styles.StepperNextButton}
>
<span>{t("createFirm.buttons.next")}</span>
<KeyboardArrowRightIcon />
</Button>
}
Actually, I want to know what is different between Before and Solution.
Maybe someone can explain the issue of solution.

Hide multiple divs with useState hooks React

I am trying to hide multiple divs with useSate.
They will be rendered random on the page, not from a list.
I have managed to do so by setting up different variables but couldn't find a more generic solution:
https://stackblitz.com/edit/react-t3shrc?file=src%2FApp.js
Also is there a way to close them when clicking outside?
Can you help please.
export default function App() {
const [isVisible, setIsVisible] = useState(false);
const [isVisible2, setIsVisible2] = useState(false);
const showInfo = (e, setIsVisible) => {
e.preventDefault();
setIsVisible(true);
};
const hideInfo = (e, setIsVisible) => {
e.preventDefault();
setIsVisible(false);
};
return (
<div>
<button
onClick={(e) => {
showInfo(e, setIsVisible);
}}
>
Show info 1
</button>
{isVisible && (
<div className="info">
Info 1
<button
onClick={(e) => {
hideInfo(e, setIsVisible);
}}
>
Close
</button>
</div>
)}
<br></br>
<br></br>
<button
onClick={(e) => {
showInfo(e, setIsVisible2);
}}
>
Show info 2
</button>
{isVisible2 && (
<div className="info">
Info 2
<button
onClick={(e) => {
hideInfo(e, setIsVisible2);
}}
>
Close
</button>
</div>
)}
</div>
);
}
I'm not 100% sure what you mean by a more 'generic' solution. Here is what comes to my mind:
First of all, we create a more complex object to basically hold all the variables / sections we encounter and use this as our state.
const initialVisibleAreas = {
area1: true,
area2: false
};
const [visibleAreas, setVisibleAreas] = useState(initialVisibleAreas);
Please note that this is propabably something you want to generate from your data using Object.keys(...) or mapping an array.
Next up, we create the functions for the buttons to use this new state accordingly:
// shows the element by given key
const showInfo = (event, key) => {
event.preventDefault();
setVisibleAreas({ ...visibleAreas, ...{ [key]: true } });
};
// hides the element by given key
const hideInfo = (event, key) => {
event.preventDefault();
setVisibleAreas({ ...visibleAreas, ...{ [key]: false } });
};
// sets every key to false to hide them all at once
const hideAllInfo = (event) => {
event.preventDefault();
const allFalse = Object.assign(
...Object.keys(visibleAreas).map((key) => ({ [key]: false }))
);
setVisibleAreas(allFalse);
};
Last but not least, we use them in jsx. This is basically one 'section':
<button
onClick={(e) => {
showInfo(e, 'area2');
}}
>
Show info 2
</button>
{
visibleAreas['area2'] && (
<div className="info">
Info 2
<button
onClick={(e) => {
hideInfo(e, 'area2');
}}
>
Close
</button>
</div>
);
}
To answer the last question; nothing is holding you to call 'hideAllInfo' inside a onClick handler of your surounding div. Sample is included in the modified stackblitz.
Have a complete look at the modified stackblitz
EDIT: In the case you want to close all areas by clicking the surrounding div, make sure to not propagate the button click event with:
event.stopPropagation();
I updated the stackblitz once again.

how to toggle between two list of arrays with one button react js

looking for advice on how to have one button that when clicked will switch the list of items to the alphabetized list of items and back when clicked again and so on. as of right now when i click the button it will show the alphabetized list but its just rendering on top of the original list already showing. not really sure on where to go from here
class MenuItems extends Component {
state = {
sortedItems: []
}
handleclick = (item) => {
this.props.deleteMenuItem(item.id);
}
menuSort = () => {
const ogList = [...this.props.menuItems]
let sortedList = ogList.sort((a, b) => a.name.localeCompare(b.name));
this.setState({sortedItems: sortedList})
};
render(){
return (
<div>
<button onClick={this.menuSort}>filter a to z</button>
{this.state.sortedItems.map((item) =>(
<li class="list" key={item.id}>
{item.name}
<br></br>
{item.body}
<br></br>
<img src={item.image}></img>
<br></br>
<button id={item.id} onClick={() => this.handleclick(item)}>delete </button>
</li>
))}
{this.props.menuItems.map((item) =>(
<li class="list" key={item.id}>
{item.name}
<br></br>
{item.body}
<br></br>
<img src={item.image}></img>
<br></br>
<button id={item.id} onClick={() => this.handleclick(item)}>delete </button>
</li>
))}
</div>
)
}
}
export default connect(null, {deleteMenuItem})(MenuItems)```
You correctly thought to keep the sorted version in the state. But you have to somehow instruct the component to render the original list or the sorted one.
So you could add a flag in the state to specify which one you want.
You could also set the sorted list in the componentDidMount lifecycle event, and updated it whenever the menuItems change through the componentDidUpdate lifecycle event.
So something like
state = {
sortedItems: [],
showSorted: false
};
toggleSort = () => {
this.setState(({ showSorted }) => ({
showSorted: !showSorted
}));
};
updateSortedItems() {
const sorted = [...this.props.menuItems].sort((a, b) =>
a.name.localeCompare(b.name)
);
this.setState({
sortedItems: sorted
});
}
componentDidMount() {
this.updateSortedItems();
}
componentDidUpdate(prevProps) {
if (this.props.menuItems !== prevProps.menuItems) {
this.updateSortedItems();
}
}
and in your render method
let itemsToShow = this.props.menuItems;
if (this.state.showSorted) {
itemsToShow = this.state.sortedItems;
}
and use the itemsToShow when you want to display them
{itemsToShow.map((item) => (
Full working example at https://codesandbox.io/s/elated-heyrovsky-m3jvv

get all elements with specific Classname in React

I want to get all elements with the classname selected from this component
function ChooseElements() {
const listItems = elementObjects.map((object) =>
<ListItem key={object.id.toString()} value={object.Element} />
);
return (
<div> <ul>
{listItems}
</ul>
<button onClick={ console.log("get all list items")}>get Elements</button>
</div>
);
}
in plain js i could use document.getElementsByClassName('selected') to get all elements
I have read somewhere that useRef should be used
to access the elements in the virtual dom, is this correct and how can I do this ?
i think you can use document.querySelectorAll('.list-item') but you need to assign a classname first. example my class is list-item so you can get the DOM elements.
You should probably see it from a different perspective. You can lift your state one level up to your <ChooseElements /> component.
This is the practice encouraged by react for similar problems. here
function ChooseElements() {
const [selectedItems, setSelectedItems] = useState([]);
const handleItemSelect = item => {
setSelectedItems([...selectedItems, item]);
}
const handleItemUnselect = item => {
setSelectedItems(selectedItems.filter(i => i !== item));
}
const listItems = elementObjects.map((object) =>
<ListItem
key={object.id.toString()}
value={object.Element}
onSelect={() => handleItemSelect(object)}
onUnselect={() => handleItemUnselect(object)}
/>
);
return (
<div>
<ul>
{listItems}
</ul>
<button onClick={() => console.log(selectedItems)}>get Elements</button>
</div>
);
}
However, if ONLY AND ONLY lifting state up is not possible to you, you can use ref like this:
function ChooseElements() {
const myRef = useRef();
const listItems = elementObjects.map((object) =>
<ListItem
key={object.id.toString()}
value={object.Element}
/>
);
return (
<div>
<ul ref={myRef}>
{listItems}
</ul>
<button onClick={() => {
console.log(ref.current.querySelector('.selected'));
}}>
get Elements
</button>
</div>
);
}

Categories

Resources