Toggle in react for multiple buttons - reactjs

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>
)
}

Related

Add & Remove class on button in React

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>
...
}

React return component onclick

I'm trying to return a component when I click on a button.
(a delete button with a confirmation popup) I've tried some examples I found online but none really worked for me.
Here is my code:
class DeleteTask extends React.Component {
handleClick() {
return <TaskDeleteModal />
}
render() {
return (
<div>
<button type="button" className={styles.deletetask} onClick={() => this.handleClick()}>Verwijderen</button>;
</div>
)
}
}
Thanks for reading, I hope you can assist me.
It did not work because the return <TaskDeleteModal /> of handleClick does not add <TaskDeleteModal /> to your render function.
You need to add <TaskDeleteModal /> to render function and control it visibility by a state:
Try the following code:
class DeleteTask extends React.Component {
this.state = {
showModal: false
}
handleClick() {
this.setState({showModal: true})
}
render() {
return (
<div>
<button type="button" className={styles.deletetask} onClick={() => this.handleClick()}>Verwijderen</button>;
{/* In TaskDeleteModal you might have a button, when you click on that button call this.setState({showModal: false}) to hide the modal */}
{this.state.showModal && <TaskDeleteModal />}
</div>
)
}
}
class DeleteTask extends React.Component {
this.state = {
showModal: false
}
handleClick() {
this.setState({showModal: true})
}
render() {
return (
<div>
<button type="button" className={styles.deletetask} onClick={() => this.handleClick()}>Verwijderen</button>;
{/* In TaskDeleteModal you might have a button, when you click on that button call this.setState({showModal: false}) to hide the modal */}
{this.state.showModal && <TaskDeleteModal />}
</div>
)
}
}

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 - Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render

I have view component, which have to import multiple component. Those component are going to be rendered according to some specific condition. How it should work is that I got 3 buttons on page, for example after I click on first button (file upload) I need to render <FileImport> component down below. What component should be rendered is in renderImportAttributes function. But it throws me a warning
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
What exactly is wrong with this?
export default class ImportView extends React.PureComponent<ModelYearListProps, IState> {
constructor(props) {
super(props);
this.state = {
selectedImportOption: null
}
}
private selectImportOption(option: string): any {
this.setState(prevState => ({
selectedImportOption: option,
}));
}
private renderImportAttributes(): JSX.Element {
if (this.state.selectedImportOption === FILE_IMPORT) {
return <FileImport history={this.props.history} importerId={this.props.match.params.importerId} />;
}
else if (this.state.selectedImportOption === SPIDER_IMPORT) {
//another component
}
else if (this.state.selectedImportOption === URL_IMPORT) {
//another component
}
return null;
}
render() {
return (
<div className="overView">
<Button onClick={() => this.selectImportOption(FILE_IMPORT)}>
File
</Button>
<Button onClick={() => this.selectImportOption(SPIDER_IMPORT)}>
Spider
</Button>
<Button onClick={() => this.selectImportOption(URL_IMPORT)}>
URL
</Button>
{this.renderImportAttributes}
</div>
);
}
};
You need to call the function:
{this.renderImportAttributes()}
{this.renderImportAttributes}
Here you are just referring to the function declaration, you have not invoked the function.
You can execute it before the return, like:
render() {
let elems = this.renderImportAttributes();
return (
<div className="overView">
<Button onClick={() => this.selectImportOption(FILE_IMPORT)}>
File
</Button>
<Button onClick={() => this.selectImportOption(SPIDER_IMPORT)}>
Spider
</Button>
<Button onClick={() => this.selectImportOption(URL_IMPORT)}>
URL
</Button>
{elems}
</div>
);
}

Resources