Add & Remove class on button in React - reactjs

I am new in React so this question could sound a little bit but I couldnt figure it out. So I have two buttons and I want to add class on them when one of them are clicked. So buttons must be default className="button" and one of them clicked it should be added to the clicked button "selected-button" class. Besides, when the Button1 clicked, "selected-button" should be removed from Button2. I am really new in React sometimes, easy things could be confusing, thank you for your helps.
import React, { Component } from "react";
import { Form } from "react-bootstrap";
export class InstantQuote extends Component {
constructor(props) {
super(props);
this.state = {
active: false,
};
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
const handleSubmit = (e) => {
e.preventDefault();
};
return (
<Form className="instantquote shadow p-3 mb-5" onSubmit={handleSubmit}>
<Form.Group controlId="formGroupFrom">
<div className="selectable-buttons">
<button type="submit"
className={((this.state.active) ? "button button-selected": "button")}
onClick={ () => this.setState({active: !this.state.active}) }>
Button1
</button>
<button type="submit"
className={((this.state.active) ? "button button-selected": "button")}
onClick={ () => this.setState({active: !this.state.active}) }>
Button2
</button>
</div>
</Form.Group>
</Form>
);
}
}
export default InstantQuote;

You have no method to identify which button is active, I would suggest having a unique identifier for each of the buttons and keep track of the active button in the component's state. Here is how the button can be declared.
this.state = {
active: ""
}
...
<button
id="button1"
className={`button ${this.state.active === "button1" ? "button-selected" : ""}`}
onClick={ () => this.setState({active: "button1"}) }>
Button1
</button>
If your component needs a variable number of buttons, declare the information beforehand.
const buttons = [{label: "Button1", id: "button1"}, ...];
...
buttons.map(button =>
<button
id={button.id}
className={`button ${this.state.active === button.id ? "button-selected" : ""}`}
onClick={ () => this.setState({active: button.id}) }>
{button.label}
</button>
)

Change the active state variable to activeButton that holds an identifier (the name for example) of the active button. If you click on an already active button, you set the activeButton to empty string.
this.state = {
activeButton: ""
}
const onButtonClick = (buttonName) =>
setState({...this.state,
activeButton: this.state.activeButtonn === buttonName
? ""
: buttonName);
render(){
...
<button
type="submit"
className={'button'+ this.state.activeButton === "Button1" && ' button-selected'}
onClick={ () => onButtonClick("Button1") }>
Button1
</button>
<button type="submit"
className={'button'+ this.state.activeButton === "Button2" && ' button-selected'}
onClick={ () => onButtonClick("Button2") }>
Button2
</button>
...
}

Related

Toggle in react for multiple buttons

Onclick function execute the myChangeHandler, which changes the state to opposite on every click. This will toggle the content inside h1 element. Here the function execute the change for both button. Any possibility to change that behaviour for individual button?
class File extends React.Component {
constructor(props) {
super(props);
this.state = {
user: false,
admin:false
};
this.myChangeHandler = this.myChangeHandler.bind(this);
}
myChangeHandler() {
this.setState(state => ({
user:!state.user
admin:!state.admin
}));
}
render() {
return(
<div> <button onClick={this.myChangeHandler}>Toggle admin </button>
{this.state.display && <h1>admin online!</h1>} </div>
<div> <button onClick={this.myChangeHandler}>Toggle user </button>
{this.state.display && <h1>user online!</h1>} </div>
)
}
}
You can give the buttons a name and access those in the handler:
<button name='admin' onClick={this.myChangeHandler}>Toggle admin </button>
myChangeHandler(e) {
const id = e.target.name
this.setState((state) => ({
[id]: !state[id]
}));
}
Note that you have to save the id before the setState, because setState is async and the event will be removed after the function. So if you try to access the event during the delayed setState, the name would be null.
Sandbox
You can pass a reference to the function to tell which button you clicked on:
myChangeHandler(name) {
this.setState((prev) => ({ [name]: !prev[name] }));
}
render() {
return(
<div>
<button onClick={() => this.myChangeHandler('admin')}>Toggle admin </button>
{this.state.display && <h1>admin online!</h1>}
</div>
<div>
<button onClick={() => this.myChangeHandler('user')}>Toggle user</button>
{this.state.display && <h1>user online!</h1>}
</div>
)
}

I change the renderer with one click and then directly query an item (ref). SetTimeout a good solution?

I change the renderer with one click and then directly query an item (ref). setTimeout a good solution?
(I don't know if I change the renderer in a single click, then in the event I can do anything in the fresh renderer. setTimeout good solution? Someone else has a different solution because I feel like I didn't do it well.)
import React from 'react';
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
con1: false,
con2: false
};
}
handleClick = (e) => {
parseFloat(e.currentTarget.getAttribute('data-id')) === 1 ?
(this.setState({
con1: true,
con2: false
}))
:
(this.setState({
con2: true,
con1: false
}));
/* Good, but this is valid??? */
setTimeout(()=>{
console.log(this.buttonContainer.childNodes[0])
},0)
/* Not good
console.log(this.buttonContainer.childNodes[0]);
*/
}
render() {
const { con1, con2 } = this.state;
return (
<div className="app-container">
<button
data-id="1"
onClick = {(e) => this.handleClick(e)}
>
Button
</button>
<button
data-id="2"
onClick = {(e) => this.handleClick(e)}
>
Button
</button>
<div
className="button-conteiner"
ref={(ref) => this.buttonContainer = ref}
>
{ con1 ?
(<div className="container1">container1</div>)
:
(null)
}
{ con2 ?
(<div className="container2">container2</div>)
:
(null)
}
</div>
</div>
)}
}
export default Test;
I would suggest to use useEffect hook with function components in this case. the logic will be more readable
import React, { useState, useEffect, useRef } from "react";
const Test = props => {
const [con1, setCon1] = useState(false);
const [con2, setCon2] = useState(false);
const buttonContainer = useRef(null);
const handleClick = e => {
const b = parseFloat(e.currentTarget.getAttribute("data-id")) === 1;
setCon1(b);
setCon2(!b);
};
useEffect(() => {
if(buttonContainer.current) {
console.log(buttonContainer.current.childNodes[0])
}
}, [buttonContainer.current, con1, con2])
return (
<div className="app-container">
<button data-id="1" onClick={e => this.handleClick(e)}>
Button
</button>
<button data-id="2" onClick={e => this.handleClick(e)}>
Button
</button>
<div
className="button-conteiner"
ref={buttonContainer}
>
{con1 ? <div className="container1">container1</div> : null}
{con2 ? <div className="container2">container2</div> : null}
</div>
</div>
);
};
export default Test;

How to handle dialog state outside of dialog component?

I have the following dialog component:
class LoginDialog extends React.Component {
state = {
open: false,
};
openDialog = () => {
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
render() {
return (
<div>
<Dialog
open={this.state.open}
onClose={this.handleClose}
>
<DialogActions>
<Button onClick={this.handleClose} color="primary">
Cancel
</Button>
<Button onClick={this.handleClose} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
How can I open that dialog from parent component AND ensure the close dialog also works? This is my attempt
class MainAppBar extends React.Component {
state = {
openLoginDialog: false,
openRegisterDialog: false
};
render() {
return (
<div>
<Button color="inherit" onClick={this.state.openLoginDialog}>Login</Button>
)}
<LoginDialog /*not sure how to pass here openLoginDialog*//>
</div>
);
}
}
So I am not sure whether I really have to keep dialog states in both child/parent and how to properly open it from parent.
You have to maintain the state whether the login dialog is open or not in the parent. Pass the open/close status to the child, and the callback to close the dialog to the child via props.
class MainAppBar extends React.Component {
state = {
openLoginDialog: false,
openRegisterDialog: false
};
openLoginDialog = () => {
this.setState({
openLoginDialog: true
});
};
closeLoginDialog = () => {
this.setState({
openLoginDialog: false
});
};
render() {
return (
<div>
<Button color="inherit" onClick={() => this.openLoginDialog()}>
Login
</Button>
)}
<LoginDialog
closeLoginDialog={this.closeLoginDialog}
isLoginDialogOpen={this.state.openLoginDialog}
/>
</div>
);
}
}
This component doesn't need any state management since we're managing it in the parent. We can make is pure this way:
const LoginDialog = props => (
<div>
<Dialog open={props.isLoginDialogOpen} onClose={props.closeLoginDialog}>
<DialogActions>
<Button onClick={props.closeLoginDialog} color="primary">
Cancel
</Button>
<Button onClick={props.closeLoginDialog} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
Hope this is helpful!
If you let the parent component manage the dialog's status, you can allow it full control over it, while passing the control function to the dialog element itself:
class MainAppBar extends React.Component {
constructor(props) {
this.state = {
openLoginDialog: false,
openRegisterDialog: false
};
}
closeDialog() { // This method will be passed to the dialog component
this.setState({
openLoginDialog: false
});
}
render() {
return (
<div>
<Button color="inherit" onClick={this.state.openLoginDialog}>Login</Button>
)}
<LoginDialog isOpen={this.state.openLoginDialog} closeDialog={this.closeDialog}>
</div>
);
}
}
class LoginDialog extends React.Component {
render() {
return (
<div>
<Dialog
open={this.props.isOpen}
onClose={this.props.closeDialog}
>
<DialogActions>
<Button onClick={this.props.closeDialog} color="primary">
Cancel
</Button>
<Button onClick={this.props.closeDialog} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
You could define handleClose() or an equivalent an event-handler inside MainAppBar component and pass that down to the child. It can manage the state-variables (true/false) on the Parent and pass that boolean value into LoginDialog bar to determine if they should be open. That way the state of the child will be managed by the parent.
class MainAppBar extends React.Component {
state = {
openLoginDialog: false,
openRegisterDialog: false
};
toggleDialog = () => {
this.setState((prevState) => {
return{
openLoginDialog: !prevState.openLoginDialog
}
})
}
render() {
return (
<div>
<Button color="inherit" onClick={this.state.openLoginDialog}>Login</Button>
)}
<LoginDialog open={this.state.openLoginDialog} toggle={this.toggleDialog}/>
</div>
);
}
}
Then:
class LoginDialog extends React.Component {
render() {
return (
<div>
<Dialog
open={this.props.open}
onClose={() => this.props.toggle} //not sure what this listener does, but im assuming you want to close it
>
<DialogActions>
<Button onClick={() => this.props.toggle} color="primary">
Cancel
</Button>
<Button onClick={() => this.props.toggle} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
}
I will take a different approach than the other answers and only include LoginDialog when it's needed.
We can now make LoginDialog a functional component and lift the state up to the Parent component. now our LoginDialog is much simpler and easier to test and doesn't depend on anything
class Parent extends React.Component {
state = {
isOpen: false,
};
// No need to use open and close handler because if the modal
// is open another execute of the function will close it
// this way we can still toggle it from the button that's opening the Dialog
toggleDialog = () => {
this.setState(prevState => ({
open: !prevState.open,
}));
};
// if you want make the handler more flexible you can write it like this
// make it a toggle by default with an optional nextState to
// make it more flexible
dialogStateHandler = (nextState) => () => {
this.setState(prevState => ({
open: nextState || !prevState.open,
}));
};
// to use this handler you will need to invoke it and passing
// in the nextState or without to make it toggle
// onClick={this.dialogStateHandler(true / false || without args to toggle)}
render() {
const { isOpen } = this.state;
return (
<div>
<button onClick={this.toggleDialog}>Toggle</button>
{/* include the Dialog component only when its open */}
{isOpen && <LoginDialog closeDialog={this.toggleDialog} />}
</div>
);
}
}
Receive closeDialog as props from Parent and pass it down to Child components
const LoginDialog = ({ closeDialog }) => (
<div>
<Dialog
closeDialog={closeDialog}
>
<DialogActions>
<Button onClick={closeDialog} color="primary">
Cancel
</Button>
<Button onClick={closeDialog} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
)}
</div>
);

Multiple buttons do the same

I am building an app in React, that is connected to an API I have written before. Buttons are renderizing but all of them change at the same time. I need advice about how can I write my code in order to separate the functionality.
My app renderize with a .map the same number of Buttons as appointments which is an array. All of them change when this.state.shown change but I need to separate all the buttons in order to only show the one that I clicked. Right now, when I clicked in one of them, this.state.shown change its value so all the buttons change because all depends of the same variable. I am looking for advices about how I can separate this.
class AppointmentsList extends Component {
constructor(props) {
super(props);
this.state = {
appointments: [],
isLoading: false,
shown: false, //Variable to know if a button need to change and render the component
customerUp: false
}
this.toggleCustomer = this.toggleCustomer.bind(this);
//this.showCustomer = this.showCustomer.bind(this);
}
toggleCustomer() {
this.setState({
shown: !this.state.shown
})
} //This function change the value of shown when a Button is clicked.
render() {
const {appointments, isLoading} = this.state;
if(isLoading) {
return <p>Loading...</p>;
}
return(
<div>
<h2>Lista de citas</h2>
{appointments.map((app) =>
<div key={app.id}>
<p>Fecha: {app.appointment}</p>
<p>Cliente: {app.customer.name}</p>
<p>Id: {app.customer.id}</p>
{ this.state.shown ? <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ocultar cliente</Button> : <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ver cliente</Button> }
{ this.state.shown ? <CustomerView id={app.customer.id} /> : null }
</div>
)}
</div>
)
}
How can I reorganize my code in order to render the Buttons separately?
Thanks in advance.
Method 1: You can make shown state a object like:
state = {
shown:{}
}
toggleCustomer(id) {
const updatedShownState = {...this.state.shown};
updatedShownState[id] = updatedShownState[id] ? false : true;
this.setState({
shown: updatedShownState,
})
} //This function change the value of shown when a Button is clicked.
render() {
const {appointments, isLoading} = this.state;
if(isLoading) {
return <p>Loading...</p>;
}
return(
<div>
<h2>Lista de citas</h2>
{appointments.map((app) =>
<div key={app.id}>
<p>Fecha: {app.appointment}</p>
<p>Cliente: {app.customer.name}</p>
<p>Id: {app.customer.id}</p>
{ this.state.shown[app.customer.id] ? <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer(app.customer.id) }>Ocultar cliente</Button> : <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ver cliente</Button> }
{ this.state.shown[app.customer.id] ? <CustomerView id={app.customer.id} /> : null }
</div>
)}
</div>
)
}
Method 2: Make a separate component for Button and Customer
return(
<div>
<h2>Lista de citas</h2>
{appointments.map((app) =>
<Appointment key = {app.id} app = {app} />
)}
</div>
)
}
class Appointment extends Component {
state = {
shown: false,
}
toggleCustomer() {
this.setState({
shown: !this.state.shown
})
}
render() {
const { app } = this.props;
return (
<div key={app.id}>
<p>Fecha: {app.appointment}</p>
<p>Cliente: {app.customer.name}</p>
<p>Id: {app.customer.id}</p>
{ this.state.shown ? <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ocultar cliente</Button> : <Button key={app.customer.id} color="danger" onClick={() => this.toggleCustomer() }>Ver cliente</Button> }
{ this.state.shown ? <CustomerView id={app.customer.id} /> : null }
</div>
)
}
}
Let me know if it works and the method you prefer.
You can create a separate component for your button (buttonComponent) inside your AppointmentsList component and pass the shown has props and the in componentDidMount of buttonComponent copy the props to the state of buttonComponent.
This way each button will have its own state, which manages shown.
Button component:
import react from 'react';
interface buttonComponentProps{
shown: boolean;
}
interface buttonComponentState{
shown: boolean;
}
class buttonComponent extends react.Component<buttonComponentProps,{}>{
constructor(props:buttonComponentProps){
super();
this.state{
shown:props.shown
}
}
....
}
export default buttonComponent;

React, onChange and onClick events fires simultaneously

I have a problem on my application: when a user is typing in the (and onChange is fired I suppose), even one single letter, the onClick event below is fired. Where is my mistake?
I have simplified the code over and there (where you see the comments), there no relevant code in there!
Thanks to everyone!
class Project extends React.Component {
constructor() {
super();
this.state = {
section_title: '',
sections: []
}
this.handleChange = this.handleChange.bind(this);
this.createSection = this.createSection.bind(this);
this.getSections = this.getSections.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
createSection(project_id) {
if(this.state.section_title != '') {
//Do Something here
}
}
getSections(project_id) {
//Fetch data here
}
componentDidMount() {
let project_data = this.props.project[0];
this.getSections(project_data.uid);
}
render() {
let project_data = this.props.project[0];
return (
<div>
<h2 className="ui header">
<i className="folder outline icon"></i>
<div className="content">
{project_data.title}
<div className="sub header">{he.decode(project_data.description)}</div>
</div>
</h2>
<div className="ui divider"></div>
<Modal trigger={<Button color="teal">Add New Section</Button>} closeIcon>
<Modal.Header>Add new section</Modal.Header>
<Modal.Content image>
<Modal.Description>
<Form>
<Form.Field>
<label>Section Name</label>
<input name="section_title" placeholder='Es: Slider ecc...' value={this.state.section_title} onChange={this.handleChange} />
</Form.Field>
<Button color="green" type='submit' onClick={this.createSection(project_data.uid)}>Crea Sezione</Button>
</Form>
</Modal.Description>
</Modal.Content>
</Modal>
</div>
);
}
}
in your Button you are initializing function this.createSection(project_data.uid) instead of calling it when needed. Easiest way is to call via arrow function
onClick={() => this.createSection(project_data.uid)}
What you did is basically using the return data of your createSection function for your onClick
So, on your onClick, try
onClick={() => this.createSection(project_data.uid)}
The onChange part is already correct.
This problem is similar to an existing answered question: React onClick function fires on render

Resources