So basically what I am doing is iterating through an array of data and making some kind of list. What I want to achieve here is on clicking on a particular list item a css class should get attached.
Iteration to make a list
var sports = allSports.sportList.map((sport) => {
return (
<SportItem icon= {sport.colorIcon} text = {sport.name} onClick={this.handleClick()} key= {sport.id}/>
)
})
A single list item
<div className="display-type icon-pad ">
<div className="icons link">
<img className="sport-icon" src={icon}/>
</div>
<p className="text-center">{text}</p>
</div>
I am not able to figure out what to do with handleClick so that If I click on a particular list it gets highlighted.
If you want to highlight the particular list item it's way better to call the handleClick function on the list item itself, and you can add CSS classes more accurately with this approach,
here is my sample code to implement the single list component
var SingleListItem = React.createClass({
getInitialState: function() {
return {
isClicked: false
};
},
handleClick: function() {
this.setState({
isClicked: true
})
},
render: function() {
var isClicked = this.state.isClicked;
var style = {
'background-color': ''
};
if (isClicked) {
style = {
'background-color': '#D3D3D3'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.text}</li>
);
}
});
Keep a separate state variable for every item that can be selected and use classnames library to conditionally manipulate classes as facebook recommends.
Edit: ok, you've mentioned that only 1 element can be selected at a time,it means that we only need to store which one of them was selected (I'm going to use the selected item's id). And also I've noticed a typo in your code, you need to link the function when you declare a component, not call it
<SportItem onClick={this.handleClick} ...
(notice how handleClick no longer contains ()).
And now we're going to pass the element's id along with the event to the handleClick handler using partial application - bind method:
<SportItem onClick={this.handleClick.bind(this,sport.id} ...
And as I said we want to store the selected item's id in the state, so the handleClick could look like:
handleClick(id,event){
this.setState({selectedItemId: id})
...
}
Now we need to pass the selectedItemId to SportItem instances so they're aware of the current selection: <SportItem selectedItemId={selectedItemId} ....Also, don't forget to attach the onClick={this.handleClick} callback to where it needs to be, invoking which is going to trigger the change of the state in the parent:
<div onClick={this.props.onClick} className={classNames('foo', { myClass: this.props.selectedItemId == this.props.key}); // => the div will always have 'foo' class but 'myClass' will be added only if this is the element that's currently selected}>
</div>
Related
I am building a simple react app for learning purpose, I just started learning react-js, I was trying to add paragraph dynamically on user action and it worked perfectly But I want to add an onClick event in insertAdjacentHTML (basically innerHTML).
But onclick event is not working in innerHTML
app.js
const addParagraph = () => {
var paragraphSpace = document.getElementById('container')
paragraphSpace.insertAdjacentHTML('beforeend', `<p>I am dynamically created paragraph for showing purpose<p> <span id="delete-para" onClick={deleteParagraph(this)}>Delete</span>`
}
const deleteParagraph = (e) => {
document.querySelector(e).parent('div').remove();
}
class App extends React.Component {
render() {
return (
<div>
<div onClick={addParagraph}>
Click here to Add Paragraph
</div>
<div id="container"></div>
</div>
)
}
}
What I am trying to do ?
User will be able to add multiple paragraphs and I am trying to add a delete button on every paragraph so user can delete particular paragraph
I have also tried with eventListener like :-
const deleteParagraph = () => {
document.querySelector('#delete').addEventListener("click", "#delete",
function(e) {
e.preventDefault();
document.querySelector(this).parent('div').remove();
})
}
But It said
deleteParagraph is not defined
I also tried to wrap deleteParagraph in componentDidMount() But it removes everything from the window.
Any help would be much Appreciated. Thank You.
Do not manipulate the DOM directly, let React handle DOM changes instead. Here's one way to implement it properly.
class App extends React.Component {
state = { paragraphs: [] };
addParagraph = () => {
// do not mutate the state directly, make a clone
const newParagraphs = this.state.paragraphs.slice(0);
// and mutate the clone, add a new paragraph
newParagraphs.push('I am dynamically created paragraph for showing purpose');
// then update the paragraphs in the state
this.setState({ paragraphs: newParagraphs });
};
deleteParagraph = (index) => () => {
// do not mutate the state directly, make a clone
const newParagraphs = this.state.paragraphs.slice(0);
// and mutate the clone, delete the current paragraph
newParagraphs.splice(index, 1);
// then update the paragraphs in the state
this.setState({ paragraphs: newParagraphs });
};
render() {
return (
<div>
<div onClick={this.addParagraph}>Click here to Add Paragraph</div>
<div id="container">
{this.state.paragraphs.map((paragraph, index) => (
<>
<p>{paragraph}</p>
<span onClick={this.deleteParagraph(index)}>Delete</span>
</>
))}
</div>
</div>
);
}
}
insertAdjecentHTML should not be used in javascripts frameworks because they work on entirely different paradigm. React components are rerendered every time you change a component state.
So you want to manipulate look of your component by changing its state
Solution:
In constructor initialize your component's state which you will change later on button click. Initial state is array of empty paragraphs.
constructor() {
super()
this.state = {
paragraphs:[]
}
}
And alter that state on button click - like this:
<div onClick={addParagraph}>
Add Paragraph function
const addParagraph = () =>{
this.state = this.state.push('New paragraph')
}
Rendering paragraphs
<div id="container">
this.state.paragraphs.map(paragraph =>{
<p>{paragraph}</p>
})
</div>
Additional tip for ReactJS in 2022 - use Functional components instead of Class components
This is something quite simple but somehow resulted in a crazy rabbit hole.
This link shows what I want:
https://www.w3schools.com/howto/howto_js_active_element.asp
Nothing special, now the thing becomes hairy for me when the elements in the navbar are rendered from an array of objects (from the specs). The approach I am following is basically rendering a list of buttons, this list of buttons is the state, since supposedly when you update a state it triggers a re-render, then when a button is clicked it "sets" the active class to false on the entire array-state then activates it only for the clicked one. So far it works.
The problem is that the active class is rendered two steps behind. One for the moment when the class in the array-state's elements are set to false, the other when the clicked element gets updated.
As far as I understand useState and setState are queues, hence those are applied asynchronously on each render, in order to avoid that and get the renders to show the current state, useEffect is utilized.
Now the thing is that I am not sure how to apply useEffect in order to achieve the immediate render of the "active" class.
This is the code I have:
import { options } from 'somewhere...'
export default function SideMenu(props){
let auxArr = []
let targetName
const [stateOptions, setStateOptions] = useState([...options])
const [currentOption, SetCurrentOption] = useState({})
function activeOption(e){
// this helps with event bubbling
if (e.target.tagName == "P" || e.target.tagName == "SPAN"){
targetName = e.target.parentElement.id
} else if (e.target.tagName == "IMG"){
targetName = e.target.parentElement.parentElement.id
} else {
targetName = e.target.id
}
// since the main state is an array of objects I am updating it
// in three steps, first the current object is "activated"
// then the main array-state gets "inactivated" to erase all
// the previous "active" classes, finally the activated object
// replaces the corresponding inactive object in the main state.
let targetElement = stateOptions.filter(e => e.id==targetName)[0]
SetCurrentOption({
id: targetElement.id,
activity:true,
img: targetElement.img,
name: targetElement.name
})
// first the "classes" are set to false, then the
// "activated" object replaces the corresponding one
// in the main object, from here comes the two
// steps delay.
auxArr = [...stateOptions]
auxArr.forEach(e => e.activity=false)
setStateOptions(auxArr)
const newOptions = stateOptions.map(e =>
e.id==currentOption.id ? currentOption : e
)
setStateOptions(newOptions)
}
return(
<aside className={styles.sideDiv}>
<nav>
{stateOptions.map(({id, img, name, activity, link}) => {
return(
<button key={id} id={id} onClick={activeOption} className={activity?styles.active:""}>
<Image src={img}/>
<p className={timeColor.theme}> {name} </p>
</button>
)
})}
</nav>
</aside>
)
}
Thanks in advance for any help you can provide.
I'm having issues in updating the state values, I'm rendering a external component using Map, and hence not able to access this. So on click of the component I'm not able to call the handleClick function to update the state values..
Here is the state :
this.state = {
attributes : {
hours : {
},
cost : 0,
amenities : defaultAmenities
},
primary_category : "General"
}
Where defaultAmenities is a external file with large javascript object.
The render function :
render() {
let basicAmenities, extendedAmenities
let basicAmenitiesList = [], extendedAmenitiesList = []
//Wrong way of storing this
let _this = this;
}
... More Logics / Switch Cases ...
let amenitiesList = basicAmenitiesList.map(function(item, index){
return <Attribute key={index} name={item.amenity_id} type={item.title} icon={item.icon} selected={item.isSelected} value="" onClick={_this.handleClick.bind(_this)}/>
})
And the attribute component
<div className="attribute-grid" onClick={this.props.onClick}>
...
</div>
Handle click is a function to setState on click of Attribute.
handleClick(e) {
console.log(e.target);
}
On click of the attribute, I need to update the state. The result of console log is attached below. I need to target the input values, but since it return the entire div, how do i get the values of name/value/placeholder?
<div class="attribute-grid-block" data-reactid=".0.2.0.3.0.1.$0.0"><div class="attribute-grid-img" data-reactid=".0.2.0.3.0.1.$0.0.0"><img src="petsIcon" data-reactid=".0.2.0.3.0.1.$0.0.0.0"></div><div class="attribute-grid-info" data-reactid=".0.2.0.3.0.1.$0.0.1"><h6 data-reactid=".0.2.0.3.0.1.$0.0.1.0">Pets</h6><input type="text" name="pets" placeholder="NO INFO FOUND" value="" disabled="" data-reactid=".0.2.0.3.0.1.$0.0.1.1"></div></div>
you can get what you need from the target. but you need to set the onClick on the element that you want it to be the target and then you will have it:
handleClick(e) {
const name = e.target.name;
const value = e.target.value;
const placeholder = e.target.placeholder;
console.log(placeholder);
}
if you want to set the onClick elsewhere you will need to send the values you want, so inside Attribute component you will have a function that will be invoke on click and call the this.props.onClick({ name: '', value: ''});
if you need to use this inside this function, and you are using react with classes. you can write this:
handleClick = (e) => {
console.log(this);
}
I have a list of elements created by using an array in react. On user click how can I make the clicked element active (by adding a CSS class) while making the other elements inactive (by removing the active class)?
My rendering of elements looks like this.
{this.props.people.map(function(person, i){
<div className='media' key={i} onClick={state.handleClick.bind(state,i,state.props)}>
<item className="media-body">{person.name}</item>
</div>
}
When the user clicks on one of these elements an active class will be added to the clicked 'media' element making the clicked element 'media active' while removing the 'active' class from the previously clicked element??
constructor(props) {
super(props);
this.state = { activeIndex: 0 };
}
handleClick(index, props) {
// do something with props
// ...
// now update activeIndex
this.setState({ activeIndex: index });
}
render() {
return (
<div>
{
this.props.people.map(function(person, index) {
const className = this.state.activeIndex === index ? 'media active' : 'media';
return (
<div className={className} key={index} onClick={handleClick.bind(this, index, this.props)}>
<item className="media-body">{person.name}</item>
</div>
);
}, this)
}
</div>
);
}
For the sake of clean code I personally would suggest you creating subcomponents to add functionality to mapped elements.
You could create a small subcomponent which simply returns the element which you want to add functionality to just like this :
...
this.state = {
active: false
}
...
return(
<div className=`media ${this.state.active ? 'active' : ''` onClick={()=>{this.setState({active: true})}}>
<item className="media-body">{this.props.name}</item>
</div>
)
...
And in your map function you simply pass the contents as properties:
{this.props.people.map(function(person, i){
<SubComponent key={i} {...person} />
}
That way you stay with clean code in the "root" component and can add complexity to your subcomponent.
In your handleClick method you could store in the component's state the clicked person (looks like the collection is of people). Then set the className conditionally based on, say, the person's id.
You can set the className using something like:
className={this.state.clickedPersonId === i ? 'media media--clicked' : 'media'}
(NB This is using i, the index of the item in the people array; you may want to use something a little more explicit, like the person's real Id.)
How do you change the position of a Component item in React?
Unless I've misunderstood it, React orders list items by key, which is represented in the DOM by data-reactid, but I don't know how to modify the key of components on the page.
i.e. How do you grab the component, change it's key, and then fire a render so that the reordered list renders in the order you've set?
e.g. in the following code example, when the Click me link is clicked, the first list item would be swapped with the last list item.
Ideally, this functionality would allow you to dynamically reorder/relocate any component on the page without changing the order of components in the render method.
Here is a link to the repo where the full project is located: https://github.com/bengrunfeld/gae-react-flux-todos
var TodoBox = React.createClass({
render: function(){
return (
<div className="todo-container">
<h4>GAE React Flux Todos</h4>
<TodoList data={this.state.data} />
</div>
)
}
});
var TodoList = React.createClass({
changePosition: function(e){
// Change position of list item (e.g. to top/certain position/end of list)
},
render:function(){
var todoNodes = this.props.data.map(function(todo) {
return (
<Todo key={todo.id} id={todo.id}>
{todo.todoText}
</Todo>
);
});
return (
<form className="todoList">
{todoNodes}
<a onClick={this.changePosition}>Click me</a>
</form>
)
}
});
var Todo = React.createClass({
render:function(){
return (
<div className="todoItem">
<input type="text" className={this.props.id} onChange={this.checkInput} defaultValue={this.props.children} ref="todoItem"/>
</div>
)
}
});
The key prop is not used to order the element, but to reconciliate it between different render calls. Elements with the same key will not be re-rendered but rather diffed against each other in order to update the DOM optimally. See Reconciliation
If you want to reorder elements, you need to change their position in your JSX or in the element array you pass as children in your render method (todoNodes).
In your case, you could make a copy of this.props.data in the TodoList component state, then update that copy in your changePosition method with something like this.setState({data: reorderedData}). A good place to make that copy would be in getInitialState.
The render method of your TodoList would then be called again, and you would map over your newly reordered this.state.data to create an array of Todo elements ordered to your liking.
However, be aware that props in getInitialState is an anti-pattern. Since your data lives in the state of your TodoBox component, a way to avoid this would be to have your TodoList component call this.props.onReorder(reorderedData) in its changePosition method. Your TodoBox component could then pass an event handler to its TodoList child, and update its state with the new data whenever this handler is called.
var TodoBox = React.createClass({
handleReorder: function(reorderedData) {
this.setState({data: reorderedData});
},
render: function(){
return (
<div className="todo-container">
<h4>GAE React Flux Todos</h4>
<TodoList data={this.state.data} onReorder={this.handleReorder} />
</div>
)
}
});
var TodoList = React.createClass({
changePosition: function(e){
// Change position of list item (e.g. to top/certain position/end of list)
// Create a copy of this.props.data and reorder it, then call
// this.props.onReorder to signal to the parent component that
// the data has been reordered
this.props.onReorder(reorderedData);
},
render:function() {
var todoNodes = this.props.data.map(function(todo) {
return (
<Todo key={todo.id} id={todo.id}>
{todo.todoText}
</Todo>
);
});
return (
<form className="todoList">
{todoNodes}
<a onClick={this.changePosition}>Click me</a>
</form>
)
}
});
Keys are used for something else, not for sorting. React uses keys to optimize its internal Virtual DOM operations. It means you tell React that "no matter the order of these siblings, the individual sibling is still identified by this key". That's how React knows whether it should prepend, insert, delete, or append new siblings by reusing the old, without throwing stuff away unnecessarily.
As for your sorting question: To change the order of the siblings, just sort the JavaScript array this.props.data.