Toggle button onclick in react - reactjs

I am making a table in react, where I want to change the edit button to save when I click on it. I have tried the below code, I know this isn't the correct way. Can anyone tell me what is the correct approach?
This is the edit button in render. It calls edit function when clicked.
dataProp contains the json data I am importing from a file.
{this.state.dataProp.map((data, index) => {
return (
<div className="show-grid row category-row">
<div className="col-md-8 text-left category" key={data.brandId}>
<b>{data.categoryName}</b></div>
<div className="col-md-4 text-right" >
<button className="edit" ref="newText" onClick={() =>
this.edit(index)}>{this.state.text}</button>
</div>
</div>
</div>
)
})}
class DisplayTable extends Component {
constructor(props) {
super(props);
this.state = {
text: "EDIT",
newArray: [],
dataProp: this.props.dataProp,
productsEditList:[ ---->This is for toggling individual button
{
id: 0,
isEdit: false
},
{
id: 1,
isEdit: false
},
{
id: 2,
isEdit: false
}
]
}
this.edit = this.edit.bind(this);
this.save = this.save.bind(this);
}
edit(key) {
this.state.productsEditList.map(keyy => {
if (key == keyy.id) {
keyy.isEdit:true
}
this.save(key);
})
}
save(key) {
if (!this.state.editing.key) {
this.setState({
text: 'SAVE',
editing: false
})
}
}
This code causes all the buttons change to save when I click on any one of them.
I do not understand how should I toggle individual buttons.
all buttons changes to save when I click on anyone of them

I took the text of the button in a state and toggled it whenever my button is clicked.
Here is the code for button -
<button className={this.state.text === "EDIT" ? "edit" : "save"} onClick={() =>this.edit}>{this.state.text}
</button>
Here is code for toggling the button
edit() {
if (this.state.editing === false) {
this.setState({
text: 'SAVE',
editing: true
});
}
else {
this.setState({
text: 'EDIT',
editing: false
});
if (this.state.changedSkus.length > 0) {
this.props.edit_menu_items_api({ "changedSkus": this.state.changedSkus });
}
}
this.render();
}

Related

change text of a specific button when clicked in React

I want to change the text of a specific button when I click on that button in React. But the issue is when I click the button the title will change for all buttons!
class Results extends Component {
constructor() {
super();
this.state = {
title: "Add to watchlist"
}
}
changeTitle = () => {
this.setState({ title: "Added" });
};
render() {
return (
<div className='results'>
{
this.props.movies.map((movie, index) => {
return (
<div className='card wrapper' key={index}>
<button className='watchListButton' onClick={this.changeTitle}>{this.state.title}</button>
</div>
)
})
}
</div>
)
}
}
You would need to come up with a mechanism to track added/removed titles per movie. For that, you would have to set your state properly. Example:
this.state = {
movies: [
{id: 1, title: 'Casino', added: false},
{id: 2, title: 'Goodfellas', added: false}
]
This way you can track what's added and what's not by passing the movie id to the function that marks movies as Added/Removed. I have put together this basic Sandbox for you to get you going in the right direction:
https://codesandbox.io/s/keen-moon-9dct9?file=/src/App.js
And here is the code for future reference:
import React, { Component } from "react";
import "./styles.css";
class App extends Component {
constructor() {
super();
this.state = {
movies: [
{ id: 1, title: "Casino", added: false },
{ id: 2, title: "Goodfellas", added: false }
]
};
}
changeTitle = (id) => {
this.setState(
this.state.movies.map((item) => {
if (item.id === id) item.added = !item.added;
return item;
})
);
};
render() {
const { movies } = this.state;
return (
<div className="results">
{movies.map((movie, index) => {
return (
<div className="card wrapper" key={index}>
{movie.title}
<button
className="watchListButton"
onClick={() => this.changeTitle(movie.id)}
>
{movie.added ? "Remove" : "Add"}
</button>
</div>
);
})}
</div>
);
}
}
export default App;

Adding new options to form on click in ReactJs

I am doing a React search where user can add multiple filters. The idea is that at first there is only one filter (select and input field) and if user wishes to add more, he can add one more row of (select and input) and it will also take that into account.
I cannot figure out the part on how to add more rows of (select, input) and furthermore, how to read their data as the list size and everything can change.
So I have multiple options in the select array:
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
Now if user selects the first value from the Select box and then types a text in the input box I will get their values and I could do a search based on that.
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends React.Component {
state = {
selectedOption: null,
textValue: null
};
handleOptionChange = selectedOption => {
this.setState({ selectedOption: selectedOption.value });
};
handleTextChange = event => {
this.setState({ textValue: event.target.value });
};
handleSubmit = () => {
console.log(
"SelectedOption: " +
this.state.selectedOption +
", textValue: " +
this.state.textValue
);
};
addNewRow = () => {
console.log("adding new row of filters");
};
render() {
const { selectedOption } = this.state;
return (
<div>
<div style={{ display: "flex" }}>
<Select
value={selectedOption}
onChange={this.handleOptionChange}
options={options}
/>
<input
type="text"
value={this.state.textValue}
onChange={this.handleTextChange}
/>
</div>
<button onClick={this.addNewRow}>AddNewRow</button>
<button onClick={this.handleSubmit}>Submit</button>
</div>
);
}
}
export default App;
I have also created a CodeSandBox for this.
If user clicks on the addNewRow a new row should appear and the previous (search, input) should be selectable without the row that was previously selected.
I don't even really know how I should approach this.
To add new row of inputs on click of button you need to add new input item into the list of inputs, like I have mention below::
import React, { Component } from 'react'
import Select from "react-select";
const options = [
{ label: "foo", value: 1 },
{ label: "bar", value: 2 },
{ label: "bin", value: 3 }
];
class App extends Component {
constructor(props) {
super(props);
this.state = { inputGroups: ['input-0'] };
}
handleSubmit = () => {
console.log("form submitted");
};
AddNewRow() {
var newInput = `input-${this.state.inputGroups.length}`;
this.setState(prevState => ({ inputGroups: prevState.inputGroups.concat([newInput]) }));
}
render() {
return (
<div>
<div>
<div>
{this.state.inputGroups.map(input =>
<div key={input} style={{ display: "flex" }}>
<Select
options={options}
/>
<input
type="text"
// value={this.state.textValue}
// onChange={this.handleTextChange}
/>
</div>
)}
</div>
</div>
<button onClick={() => this.AddNewRow()}>AddNewRow</button>
<button onClick={this.handleSubmit()}>Submit</button>
</div>
);
}
}
export default App;
After click on "AddNewRow" button it will add new input group for you. Now you need to wrap this inputGroup inside "Form" to get data of each inputGroup on click of submit.
I hope it will resolve your issue.

Stop propagation in React onclick handler

I have a problem with propagation in react.
I hace this method that render div-tags with p-tags within:
private renderTags(tag: Tags, index: number) {
return <div>
<div onClick={(e) => { e.stopPropagation(); this.collectTags(tag); }}>
<p className={styles.tag}># {tag.title} <i className="ms-Icon ms-Icon--CirclePlus"></i></p>
</div>
</div>
}
that method is called from the render like this:
<div className={styles.tagsContainer}>
{this.state.items.slice(0, 12).map((w, index) => this.renderTags(w, index))}
</div>
As you see the renderTags method calls for each items in the array.
the idea with the first method is that when a user click on one of the elements that element is sent to an array, the problem is that when I click in one of these elements all the elements sends to the array. I tested by adding a class name to the clicked element just for checking reason, and i can see that the same behavior arises, when I click in one of the elements all the elements get the class name.
how can I stop this propagation?. by the way this is the method that hear for the click and put the clicked element in the array:
private collectTags(newTag: Tags): any {
//this.setState({ savingSettings: true, tagActive: true });
let selectedTags: Tags[] = this.state.selectedTags;
selectedTags.push(newTag);
this.setState({
selectedTags: selectedTags,
hideSaveButton: false
});
return selectedTags;
}
UPDATE
Better I post the entire code:
import * as React from 'react';
import { CacheManager } from "../../common/CacheManager";
import { ITagsDataProvider } from "../../interfaces/ITagsDataProvider";
import Tags from "./Tags";
import styles from './TagsContainer.module.scss';
import { Dialog, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
export interface ITagsContainerProps {
provider: ITagsDataProvider;
}
export interface ITagsContainerState {
items: Tags[];
allTags: Tags[];
selectedTags: Tags[];
savingSettings: boolean;
currentTagsIndex: number;
activeTile: number;
hideDialog: boolean;
hideSaveButton: boolean;
}
export default class TagsContainer extends React.Component<ITagsContainerProps, ITagsContainerState> {
private readonly cacheKey = "TagsLinks";
constructor(props: ITagsContainerProps) {
super(props);
this.state = {
items: [],
allTags: [],
selectedTags: [],
savingSettings: false,
currentTagsIndex: -1,
activeTile: -1,
hideDialog: true,
hideSaveButton: true
}
}
public componentDidMount(): void {
var cacheManager = new CacheManager();
var cachedValue = cacheManager.get(this.cacheKey);
//If there are cached values update the state
if (cachedValue) {
this.setState({
items: cachedValue,
allTags: [],
savingSettings: false,
currentTagsIndex: -1
});
return;
}
this.props.provider.getAllTags().then((tags) => {
if (tags != null) {
cacheManager.set(this.cacheKey, tags);
}
this.setState({
items: tags,
allTags: [],
});
});
}
private renderTags(tag: Tags, index: number) {
return <div>
<div onClick={(e) => this.onTagClick(tag, e)}>
<p className={styles.tag}># {tag.title} <i className="ms-Icon ms-Icon--CirclePlus"></i></p>
</div>
</div>
}
private onTagClick(tag: Tags, e: React.MouseEvent<HTMLDivElement>) {
e.stopPropagation();
this.collectTags(tag);
}
private collectTags(newTag: Tags): any {
this.setState({
selectedTags: {
...this.state.selectedTags,
newTag
},
hideSaveButton: false
});
}
private saveSettings(): void {
let sTags = this.state.selectedTags;
this.setState({
items: sTags
});
console.log('SELECTED TAG ' + this.state.items);
var cacheManager = new CacheManager();
cacheManager.set(this.cacheKey, sTags);
this.props.provider.saveSettingsData(sTags).then(() => {
this.setState({
savingSettings: false
});
});
}
// Render the tags in the dialog box
private onRenderDialog = (tag: Tags, index: number): JSX.Element => {
return (
<div className={styles.tag} onClick={(e) => { e.stopPropagation(); this.collectTags(tag); }}>
<span># {tag.title} <i className="ms-Icon ms-Icon--CirclePlus"></i></span>
</div>
)
}
public render(): JSX.Element {
return <div className={styles.tagCloud}>
<div>
<h1>What are you interested in?</h1>
<p>We'll show you more stories from the topics you pick below</p>
</div>
<div>
<div className={styles.tagsContainer}>
{this.state.items.slice(0, 12).map((t, index) => this.renderTags(t, index))}
</div>
<div>
<a className={styles.allItemsLink} href="#" onClick={this._showDialog}>View all topcis</a>
</div>
<div>
{ this.state.hideSaveButton === false ? <DefaultButton
text="Done"
style={{ backgroundColor: '#ff0033', color: '#ffffff' }}
onClick={(e) =>{e.stopPropagation(); this.saveSettings()}}
/> : null}
</div>
</div>
<Dialog
hidden={this.state.hideDialog}
onDismiss={this._closeDialog}
containerClassName={'ms-dialogMainOverride ' + styles.textDialog}
modalProps={{
isBlocking: true,
}}>
<div className={styles.tagsDialogContainer}>
{this.state.allTags.map((t, index) => this.onRenderDialog(t, index))}
</div>
<DialogFooter>
<DefaultButton
style={{ backgroundColor: '#ff0033', color: '#ffffff' }}
onClick={this._closeDialog}
text="Done"
/>
</DialogFooter>
</Dialog>
</div>
}
private _showDialog = (): void => {
this.setState({ hideDialog: false });
this.props.provider.getAllTags().then((items) => {
this.setState({ allTags: items });
})
};
private _closeDialog = (): void => {
this.setState({ hideDialog: true });
}
}
Best regards
Americo
First of all you need to create separate method for event handling, for example onTagClick.
private renderTags(tag: Tags, index: number) {
return <div>
<div onClick={e => this.onTagClick(e, tag)}>
<p className={styles.tag}># {tag.title}
<i className="ms-Icon ms-Icon--CirclePlus"></i>
</p>
</div>
</div>
}
private onTagClick(tag: Tags, e: React.MouseEvent<HTMLElement>) {
e.stopPropagation();
this.collectTags(tag);
}
Another issue - you are mutating state directly, which not allowed in React.
// Here you creating the link to array named `selectedTags`.
let selectedTags: Tags[] = this.state.selectedTags;
// and here you mutating your state directly
selectedTags.push(newTag);
Just copy your array before adding new item or use spread operator.
private collectTags(newTag: Tags): any {
this.setState({
selectedTags: {
...this.state.selectedTags,
newTag
},
hideSaveButton: false
});
}
Also, don't forget to bind context to collectTags method in constructor.
constructor(props) {
super(props);
...some code if you have...
this.collectTags = this.collectTags.bind(this);
}
Hope it helped.

Hide an element in multiple elements by react

I want to hide an element by react in multiple elements. I try to use isHidden: true, but when i click the close button instead of hiding selected element, gives me a full blank page ? why?
class App extends React.Component {
constructor(props){
super(props);
this.state = {
data: [
{ _id: "5bb85a2be138230670c3687b", firstName: "foo", lastName: "foo", email: "foo#foo.com"},
{ _id: "5bb9b3cae13823261e886990", firstName: "bar", lastName: "bar", email: "bar#bar.com" },
],
editVisibles: {},
isHidden: true,
};
}
showEditDiv = (_id) => {
this.setState( prevState => ({
editVisibles: { ...prevState.editVisibles, [_id]: !prevState.editVisibles[_id] }
})
)
};
toggleHidden = ()=> this.setState((prevState)=>({isHidden: !prevState.isHidden}))
renderFlight() {
return this.state.data.map(item => {
return (
<div>
{this.state.isHidden &&
<li key={item._id}>
<div class="close" onClick={() => this.toggleHidden(item._id)}>X</div>
<p>{item.email}</p>
<button onClick={() => this.showEditDiv(item._id)}>Edit</button>
<div key={item._id} className={`edit-form ${!this.state.editVisibles[item._id] ? "unvisible" : "visible"}`}>
</div>
</li>
}
</div>
)
})
}
render() {
return (
<div>{this.renderFlight()}</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
First of all, the code is really messy and hard to read, so you should edit it for better visibility.
Also, notice that isHidden is true when the element should not be visible, but your code states that "if this.state.isHidden is true, then render the content of the div".
What you want is !this.state.isHidden && ...
Also, you should use reduce/filter to filter out the elements that have a truthy value for isHidden, instead of map, because right now you're pushing empty div elements which is unnecessary
Could see an argument in your method call - this.toggleHidden(item._id), but item._id is not used in the function definition. Any particular reason for that?
toggleHidden = ()=>
this.setState((prevState)=>({isHidden: !prevState.isHidden
}))
Basically we have to select the item by keyProp and apply the toggle method. Go through
How to find element by Key in React?
<li keyProp={'listItem_'+item._id}>
would give the necessary attribute and we can apply show/hide on the li element by selecting using props.keyProp

How to update state in map function in reactjs

I am having 4 buttons each button have name id and selected boolean flag.
What I am trying to achieve is, on click of button, boolean button flag should be changed of that particular button. For this, I need to setState in map function for that particular button Id.
My issue is I am unable to setState in map function for that particular clicked button, its btnSelected should be changed
My aim is to create a multi-select deselect button.Its kind of interest selection for the user and based on that reflect the UI as well my array. Here is my code.
Thanks in anticipation.
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
export default class Test extends Component {
constructor(props, context) {
super(props, context);
this.handleChange = this.handleChange.bind(this);
this.state = {
value: "",
numbers: [1, 2, 3, 4, 5],
posts: [
{
id: 1,
topic: "Animal",
btnSelected: false
},
{
id: 2,
topic: "Food",
btnSelected: false
},
{
id: 3,
topic: "Planet",
btnSelected: false
},
{ id: 4, topic: "Nature", btnSelected: false }
],
allInterest: []
};
}
handleChange(e) {
//console.log(e.target.value);
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value });
}
getInterest(id) {
this.state.posts.map(post => {
if (id === post.id) {
//How to setState of post only btnSelected should change
}
});
console.log(this.state.allInterest);
if (this.state.allInterest.length > 0) {
console.log("Yes we exits");
} else {
console.log(id);
this.setState(
{
allInterest: this.state.allInterest.concat(id)
},
function() {
console.log(this.state);
}
);
}
}
render() {
return (
<div>
{this.state.posts.map((posts, index) => (
<li
key={"tab" + index}
class="btn btn-default"
onClick={() => this.getInterest(posts.id)}
>
{posts.topic}
<Glyphicon
glyph={posts.btnSelected === true ? "ok-sign" : "remove-circle"}
/>
</li>
))}
</div>
);
}
}
Here's how you do something like this:
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (e) => {
const { posts } = this.state;
const { id } = e.target;
posts[id].selected = !this.state.posts[id].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="radio" id={i} key={i} checked={p.selected} onClick={this.handleClick} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here.
You can do this by passing the index from the map into each button's handleClick function, which would then return another function that can be triggered by an onClick event.
In contrast to Colin Ricardo's answer, this approach avoids adding an id prop onto each child of the map function that is only used for determining the index in the handleClick. I've modified Colin's example here to show the comparison. Notice the event parameter is no longer necessary.
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (index) => () => {
const { posts } = this.state;
posts[index].selected = !this.state.posts[index].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="checkbox" key={i} checked={p.selected} onClick={this.handleClick(i)} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here

Resources