I am making a simple react app where there are two different div's..
One with select input and selected list,
<div id="container">
<div className="_2iA8p44d0WZ">
<span className="chip _7ahQImy">Item One</span>
<span className="chip _7ahQImy">Item Two</span>
<span className="chip _7ahQImy">Item Three</span>
<span className="chip _7ahQImy">Item Four</span>
<span className="chip _7ahQImy">Item Five</span>
<input
type="text"
className="searchBox"
id="search_input"
placeholder="Select"
autoComplete="off"
value=""
/>
</div>
</div>
Another will list down the selected option as fieldset,
<div>
{selectedElements.map((item, i) => (
<div key={i} className="selected-element" ref={scrollDiv}>
<fieldset>
<legend>{item}</legend>
</fieldset>
</div>
))}
</div>
Based on this solution, I have added createRef to the selected element like,
<div key={i} className="selected-element" ref={scrollDiv}>
</div>
Then I took Javascript query methods to get DOM elements like,
const chipsArray = document.querySelectorAll("#container > div > .chip");
Added click event listener to all the elements like,
chipsArray.forEach((elem, index) => {
elem.addEventListener("click", scrollSmoothHandler);
});
Then scrollSmoothHandler like,
const scrollDiv = createRef();
const scrollSmoothHandler = () => {
console.log(scrollDiv.current);
if (scrollDiv.current) {
scrollDiv.current.scrollIntoView({ behavior: "smooth" });
}
};
But this doesn't work the way as expected.
Requirement:
On click over any item in first div, then its related fieldset needs to get smooth scrolled in another div..
Eg:
If user click on the element Item Four under
<div id="container"> ... <span className="chip _7ahQImy">Item Four</span> ... </div>
then the related fieldset needs to get scrolled into. Here the fieldset with legend as Item Four ..
I think also making the js dom query methods on react and it seems not a react way of implementation. Can anyone please kindly help me to achieve the result of scrolling to a related fieldset on click over the selected item..
Issue
React.createRef is really only valid in class-based components. If used in a functional component body then the ref would be recreated each render cycle.
Don't use a DOM query selector to attach onClick listeners to DOM elements. These live outside react and you'd need to remember to clean them up (i.e. remove them) so you don't have a memory leak. Use React's onClick prop.
When the selectedElements are mapped you attach the same ref to each element, so the last one set is the one your UI gets.
Solution
Use React.useRef in the functional component body to store an array of react refs to attach to each element you want to scroll into view.
Attach the scrollSmoothHandler directly to each span's onClick prop.
Attach each ref from the ref array created in 1. to each mapped field set you want to scroll to.
Code
import React, { createRef, useRef } from "react";
import { render } from "react-dom";
const App = () => {
const selectedElements = [
"Item One",
"Item Two",
"Item Three",
"Item Four",
"Item Five"
];
// React ref to store array of refs
const scrollRefs = useRef([]);
// Populate scrollable refs, only create them once
// if the selectedElements array length is expected to change there is a workaround
scrollRefs.current = [...Array(selectedElements.length).keys()].map(
(_, i) => scrollRefs.current[i] ?? createRef()
);
// Curried handler to take index and return click handler
const scrollSmoothHandler = (index) => () => {
scrollRefs.current[index].current.scrollIntoView({ behavior: "smooth" });
};
return (
<div>
<div id="container">
<div className="_2iA8p44d0WZ">
{selectedElements.map((el, i) => (
<span
className="chip _7ahQImy"
onClick={scrollSmoothHandler(i)} // <-- pass index to curried handler
>
{el}
</span>
))}
<input
type="text"
className="searchBox"
id="search_input"
placeholder="Select"
autoComplete="off"
value=""
/>
</div>
</div>
<div>
{selectedElements.map((item, i) => (
<div
key={i}
className="selected-element"
ref={scrollRefs.current[i]} // <-- pass scroll ref # index i
>
<fieldset>
<legend>{item}</legend>
</fieldset>
</div>
))}
</div>
</div>
);
};
Solution #2
Since you can't update any elements in the div with id="container" and all the onClick handlers need to be attached via querying the DOM, you can still use a curried scrollSmoothHandler callback and enclose an index in scope. You'll need an useEffect hook to query the DOM after the initial render so the spans have been mounted, and an useState hook to store a "loaded" state. The state is necessary to trigger a rerender and re-enclose over the scrollRefs in the scrollSmoothHandler callback.
const App = () => {
const selectedElements = [
"Item One",
"Item Two",
"Item Three",
"Item Four",
"Item Five"
];
const [loaded, setLoaded] = useState(false);
const scrollRefs = useRef([]);
const scrollSmoothHandler = (index) => () => {
scrollRefs.current[index].current.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
const chipsArray = document.querySelectorAll("#container > div > .chip");
if (!loaded) {
scrollRefs.current = [...Array(chipsArray.length).keys()].map(
(_, i) => scrollRefs.current[i] ?? createRef()
);
chipsArray.forEach((elem, index) => {
elem.addEventListener("click", scrollSmoothHandler(index));
});
setLoaded(true);
}
}, [loaded]);
return (
<div>
<div id="container">
<div className="_2iA8p44d0WZ">
<span className="chip _7ahQImy">Item One</span>
<span className="chip _7ahQImy">Item Two</span>
<span className="chip _7ahQImy">Item Three</span>
<span className="chip _7ahQImy">Item Four</span>
<span className="chip _7ahQImy">Item Five</span>
<input
type="text"
className="searchBox"
id="search_input"
placeholder="Select"
autoComplete="off"
value=""
/>
</div>
</div>
<div>
{selectedElements.map((item, i) => (
<div key={i} className="selected-element" ref={scrollRefs.current[i]}>
<fieldset>
<legend>{item}</legend>
</fieldset>
</div>
))}
</div>
</div>
);
};
Related
I want add active class to click element but it is toogle. How can i fixed it?
const PodcastTab = () => {
const [tabOpened, setTabOpened] = useState(false);
return <div>
<div>
<span className={classNames('my-podcast',{'active':tabOpened})} onClick={()=>{setTabOpened(!tabOpened)}}>My Podcast</span>
<span className={classNames('podcasts-stats',{'active':!tabOpened})} onClick={()=>{setTabOpened(!tabOpened)}}>Podcast's Stats</span>
<div className="my-podcast-tab">
<Podcast />
</div>
<div className="podcasts-stats-tab">
<Podcast imgURL="https://i.picsum.photos/id/144/75/75.jpg?hmac=9kweqWXv0sL19dFj1CaMKxbH3kQMIuFFbHy2hWhKJ4w" status="x" title="y"/>
</div>
</div>
</div>;
}
You can use the following className:
className={classNames('podcasts-stats',{tabOpened ? 'active' : ''})}
Here's what I've have so far - Full working view https://codesandbox.io/s/hungry-elbakyan-v3h96
Accordion component:
const Accordion = ({ data }) => {
return (
<div className={"wrapper"}>
<ul className={"accordionList"}>
{data.map((item) => {
return (
<li className={"accordionListItem"} key={item.title}>
<AccordionItem {...item} />
</li>
);
})}
</ul>
</div>
);
};
const AccordionItem = ({ content, title }) => {
const [state, setState] = useState({
isOpened: false
});
return (
<div
className={cn("accordionItem", state.isOpened && "opened")}
onClick={() => setState({ isOpened: !state.isOpened })}
>
<div className={"lineItem"}>
<h3 className={"title"}>{title}</h3>
<span className={"icon"} />
</div>
<div className={"inner"}>
<div className={"content"}>
<p className={"paragraph"}>{content}</p>
</div>
</div>
</div>
);
};
When I click on the accordion item nothing happens. I can see the content appears in inspect and the state changes as expected but it doesn't slide down. Did I miss something in my css or component?
Here is what I was able to achieve. You may not like it completely(animations). But things seems sorted
https://codesandbox.io/s/relaxed-babbage-2zt4f?file=/src/styles.css
props name was not right for accordion body
and styles need to be changes once the accordion is in open state.
You need to add or remove the className inner when state.isOpen so you can see your content
import React, {useEffect, useContext, useState} from 'react';
import {FormContext} from "../../../core/context";
export default function Todo(props) {
const { todos, setTodos, deleteTodo, onEditSubmit, editContent, setEditContent} = useContext(FormContext);
const [isEditing, setIsEditing] = useState(false);
useEffect(() => {
}, [isEditing])
return (
<div className={todoCard(props.todo)} id="todo-card">
<div className="card-body d-flex align-items-center">
<input type="checkbox" className="mx-2"
onChange={() => props.toggleCompleted(props.index)}
checked={props.todo.isCompleted}/><span className={props.todo.isCompleted ? 'line-through mr-auto': 'mr-auto'}>{props.todo.content}</span>
<i className={editIcon()} id="edit" onClick={(e) => editTodo(e)}></i>
<i onClick={() => deleteTodo(props.todo.id)} className="fa fa-trash-o fa-lg" id="trash-o"></i>
</div>
<div style={{display: 'none'}} className='card-footer m-0 p-0 justify-content-center'>
<form className="p-2 d-flex" onSubmit={(e) => onEditSubmit(e, props.todo.id)}>
<input type="text" className="form-control mx-2" onChange={(e) => setEditContent(e.target.value)} />
<input type="submit" value="Submit" className="btn btn-warning"/>
</form>
</div>
</div>
);
function editIcon() {
let baseline = 'fa fa-pencil-square-o mx-3';
if (isEditing) baseline += 'editing';
return baseline;
}
function editTodo(e) {
const cardFooter = e.target.parentElement.parentElement.childNodes[1];
cardFooter.style.display === 'none' ? cardFooter.style.display = 'flex': cardFooter.style.display = 'none';
}
function todoCard(item) {
let className = 'card';
if (item.isFound) className += 'text-white bg-info';
return className;
}
}
On the first line of the editTodo() function I'm selecting the card footer of the exact instance of the todo that I'm trying to edit. I would like to know if there are any alternatives to doing this(The first line of the function). Because if my elements were much deeply nested then it would be a pain to select a sibling node.
Well, there are two options.
By native javascript and react, you can pass variable when you call your function i.e. (editTodo(myIdVar)). You need to generate a specific ID that is unique (before the return), pass it to your editTodo then use it to search for that by ID (document.getElementById('myUniqueId')). This is the best solution because it will directly point a specific item, however, for this, you need a unique ID.
You pull in jQuery, and it has a parent function that goes up for a specific tag (i.e. $(element).parent('div.myclass') will look for a div tag that has a myclass class on it) after then you can go down by selecting a class or telling a child item. You can get the element by wrapping the e.target like $(e.target) or any other Javascript DOM object. This solution doesn't need unique id, it's more dynamic than your solution but it costs little more time to execute.
import Link from 'next/link';
function myClick(e){
console.log(e);
}
const Index = () => (
<section className="min-vh-100">
<div className="input_bar border rounded-maximum p-1 mx-1 bg-white d-flex">
<input className="myButton submit_bar text-black" placeholder="Insert your input…"/>
<Link href="#"><a onClick={myClick} className="input_icon"></a></Link>
</div>
</section>
);
I'm using nextjs and I'm trying to change a div's class through a click function in another div, I couldn't find examples of how to do that so I can understand how it works. Thank you.
Here is an example using refs:
import Link from 'next/link'
const Index = () => {
let myDiv = React.createRef()
function myClick() {
myDiv.current.classList.add('add-this-class')
}
return (
<section className="min-vh-100">
<div
ref={myDiv}
className="input_bar border rounded-maximum p-1 mx-1 bg-white d-flex"
>
<input
className="myButton submit_bar text-black"
placeholder="Insert your input…"
/>
<Link href="#">
<a onClick={myClick} className="input_icon" />
</Link>
</div>
</section>
)
}
To understand what I'm doing here. I'm creating a reference with this line:
let myDiv = React.createRef()
Then I assign it to the element I want to access, in the example I assign it to the div:
<div ref={myDiv} className="..." >
And on the onClick function I access the div and add a class:
myDiv.current.classList.add('add-this-class')
Let me know if this works for you. (If it did, thank Abderrahman)
I used hooks.
const Index = () => {
const [className, setClassName] = useState("")
const myClick = () => {
setClassName("some-class")
}
return (
<div>
<button onClick={myClick}>Click me</button>
<div className={className}>Classname gets changes</div>
</div>
)
}
I am learning React. I have tried to keep components in separate files. So, I have:
SaveDocument (class)
PersonList (const)
Person (const)
PersonList represents a dropdown of persons. I am trying to figure out how to get the value of the select dropdown in the SaveDocument class (i.e. when they click 'Save Changes').
How can i get the value of the select dropdown when the user clicks click Save?
Code below:
PersonList.js
import React from "react";
import Person from "./../model/Person";
const PersonList = props => {
return (
<div key="PersonList">
<select className="col-6">
{props.persons.map(person => <Person key={person.id} {...person} />)}
</select>
</div>
);
};
export default PersonList;
Person.js
import React from "react";
import moment from "moment";
import "react-datepicker/dist/react-datepicker.css";
const Person = person => {
console.log(JSON.stringify(person));
return (
<option id="{person.id}">{person.firstName + " " + person.lastName}</option>
);
};
Document.defaultProps = {
firstName: "",
lastName: ""
};
export default Person;
SaveDocument.js
import React, { Component } from "react";
import postDocument from "./../rest/PostDocument";
import fetchPersons from "./../rest/FetchPersons";
import PersonList from "./../components/PersonList";
import ShowDatePicker from "./../components/ShowDatePicker";
class SaveDocument extends Component {
state = {
persons: [],
personFromSelect: ''
};
cachePersons = personInfo => {
console.log(">> persons" + personInfo);
this.setState(prevState => ({
persons: personInfo
}));
};
resetFields () {
console.log("reset");
console.log(this.keys.PersonList.value);
}
componentWillMount() {
console.log("mounted");
fetchPersons.callApi(this.cachePersons);
}
render() {
return (
<div
className="modal fade"
id="basicExampleModal"
tabIndex="-1"
role="dialog"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title" id="exampleModalLabel">
Save document
</h5>
<button
type="button"
className="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<div className="row">
<div className="col-4 text-left">Document Date:</div>
<div className="col-6">
<ShowDatePicker />
</div>
</div>
<br />
<div className="row">
<div className="col-4 text-left">Person From:</div>
<PersonList persons={this.state.persons} />
</div>
<br />
<div className="row">
<div className="col-4 text-left">Comments:</div>
<div className="col-md-6">
<div className="form-group">
<input
type="text"
className="form-control"
id="commentsBox"
placeholder="Comments"
onKeyPress={event => {
if (event.key === "Enter") {
}
}}
/>
</div>
</div>
</div>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-secondary"
data-dismiss="modal"
onClick={() => this.resetFields()}
>
Close
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => postDocument.callApi(this.props)}
>
Save changes
</button>
</div>
</div>
</div>
</div>
);
}
}
export default SaveDocument;
In general uncontrolled components (where the input state is handled directly by the DOM element) are generally not advisable and make it harder to manage and reason about your app state. I'd recommend you change to a controlled component, where the state of your input is managed by React and the DOM simply renders that state.
PersonList.js:
Note that the <select> element receives its selected value from props, as well as a callback handler for when the user makes a change.
const PersonList = props => {
return (
<div key="PersonList">
<select className="col-6" value={this.props.value} onChange={this.props.onChangeCallback} >
{props.persons.map(person => <Person key={person.id} {...person} />)}
</select>
</div>
);
};
Person.js:
Note that it now has a value prop so that onchange events know what the new value will be, and <select> knows which option to display based on value.
const Person = person => {
console.log(JSON.stringify(person));
return (
<option value={person.id} id="{person.id}">{person.firstName + " " + person.lastName}</option>
);
};
SaveDocument.js:
Note that you're now keeping the dropdown select state in React state and passing it down to the child component PersonList, along with the callback handler for updating state.
...
onChangeCallback = (e) => {
this.setState({personValue: e.target.value});
}
cachePersons = personInfo => {
console.log(">> persons" + personInfo);
this.setState(prevState => ({
persons: personInfo,
personValue: personInfo[0].id
}));
};
render() {
...
<PersonList
persons={this.state.persons}
value={this.state.personValue}
onChangeCallback={this.onChangeCallback}
/>
...
}
Now you are actually keeping the select state of your dropdown menu in your parent component, SaveDocument, and passing it down into the list. The list simply renders the dropdown menu with the appropriately selected value (from state) and provides a callback for when it changes. Now the state of your dropdown lives inside React state and is easily accessible from inside SaveDocument when the user clicks the "save" button, instead of ambiguously living in the DOM element.
Add a prop to PersonList:
<PersonList onChangePerson={this.props.onChangePerson} />
Add an event handler for onChangePerson to SaveDocument.js and don’t forget to bind this in your constructor.
onChangePerson(event) {
var value = event.target.value
}
this.onChangePerson = this.onChangePerson.bind(this);
On the select add the onChange event
<select onChange={this.props.onChangePerson}></select>
You would then setState in the onChangePerson event to save your currently selected person and then when the user clicked Save, you would reference this.state.selectedPerson for example.