Programatically add/remove class generated by material ui - javascript

How can I programmatically add/ remove the material-UI classes that I defined in makeStyles()
Here's my code:
import React from 'react';
import { DropboxIcon, GoogleDriveIcon, BoxIcon } from '../components/icons';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(theme => ({
input: {
display: 'none'
},
label: {
display: 'inline-flex',
marginRight: theme.spacing(2),
marginLeft: theme.spacing(2),
justifyCOntent: 'space-between',
padding: theme.spacing(1, 1.5)
},
iconName: {
marginLeft: theme.spacing(1)
},
iconsFormWrapper: {
marginTop: theme.spacing(2)
},
selectedService: {
backgroundColor: theme.palette.primary.dark,
borderRadius: theme.spacing(1),
color: '#ffffff',
'& svg, img': {
fill: '#ffffff!important',
filter: 'brightness(0) invert(1)'
}
}
}));
const selectServiceHandle = (e) => {
const optionName = document.getElementsByName("signup-service");
optionName.classList.remove("selectedService")
document.querySelectorAll(`input[value=${e.target.value}]`).classList.add("selectedService")
}
const SignupRadioIcons = () => {
const classes = useStyles();
return (
<div className={classes.iconsFormWrapper}>
<form>
<label className={`${classes.label} ${classes.selectedService}`}>
<DropboxIcon/> <span className={classes.iconName}>Dropbox</span>
<input type="radio" onChange={(e)=>selectServiceHandle(e)} name="signup-service" value="dropbox" className={classes.input} />
</label>
<label className={classes.label}>
<GoogleDriveIcon/> <span className={classes.iconName}>Google Drive</span>
<input type="radio" onChange={(e)=>selectServiceHandle(e)} name="signup-service" value="drive" className={classes.input} />
</label>
<label className={classes.label}>
<BoxIcon/> <span className={classes.iconName}>Box</span>
<input type="radio" onChange={(e)=>selectServiceHandle(e)} name="signup-service" value="box" className={classes.input} />
</label>
</form>
</div>
)
}
export default SignupRadioIcons;
In my code, I defined a class called selectedService in my makeStyles()
But I want to add/remove this class on every change event, Whenever a user click any input I want to remove this class from all other inputs and add it to the one where the user clicked. But this code is not working because the actual classes that is generated by material UI look like makeStyles-selectedService-224 and the postfix number keeps on changing on every refresh.

You can set state to the currently selected radio's value, then use a conditional statement to check if the selected value equals the currently selected state in order to apply css.
I would recommend converting your radio's into JSON objects, then looping through them - this makes things a lot easier.
I have provided an example of how to handle this without using a loop, as well as with using a loop in the SandBox below.
Example Code Using JSON & Looping:
import React, { useState } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Dropbox from "./Dropbox.js";
import Box from "./Box.js";
import Drive from "./Drive.js";
const base = {
fontSize: "20px"
};
const useStyles = makeStyles(theme => ({
base: {
...base
},
selected: {
...base,
fontSize: "40px",
backgroundColor: "yellow",
textDecorationLine: "underline"
}
}));
const radioObjects = [
{
label: Dropbox,
value: "dropbox"
},
{
label: Drive,
value: "drive"
},
{
label: Box,
value: "box"
}
];
export default function WithLoop() {
const classes = useStyles();
const [currentlyChecked, setCurrentlyChecked] = useState();
function handleChange(event) {
setCurrentlyChecked(event.target.value);
// do work
}
return (
<div>
<form>
{radioObjects.map(RadioItem => {
return (
<div key={RadioItem.value}>
<label
className={
currentlyChecked === RadioItem.value
? classes.selected
: classes.base
}
>
<RadioItem.label />
<input
value={RadioItem.value}
onChange={handleChange}
type="radio"
name="signup-service"
/>
</label>
</div>
);
})}
</form>
</div>
);
}

Given you are using React, I'd suggest you use local state to determine when to apply the class
import React, { useState } from 'react';
const storageProviders = {
Dropbox: 1,
GoogleDrive: 2,
Box: 3
};
...
const SignupRadioIcons = () => {
const classes = useStyles();
const [focusedInput, setFocusedInput] = useState(storageProviders.Dropbox);
return (
<div className={classes.iconsFormWrapper}>
<form>
<label className={`${classes.label} ${focusedInput === storageProviders.Dropbox ? classes.selectedService : ''}`}>
<DropboxIcon/> <span className={classes.iconName}>Dropbox</span>
<input type="radio" onChange={() => setFocusedInput(storageProviders.Dropbox)} name="signup-service" value="dropbox" className={classes.input} />
</label>
<label className={`${classes.label} ${focusedInput === storageProviders.GoogleDrive ? classes.selectedService : ''}`}>
<GoogleDriveIcon/> <span className={classes.iconName}>Google Drive</span>
<input type="radio" onChange={() => setFocusedInput(storageProviders.GoogleDrive)} name="signup-service" value="drive" className={classes.input} />
</label>
<label className={`${classes.label} ${focusedInput === storageProviders.Box ? classes.selectedService : ''}`}>
<BoxIcon/> <span className={classes.iconName}>Box</span>
<input type="radio" onChange={() => setFocusedInput(storageProviders.Box)} name="signup-service" value="box" className={classes.input} />
</label>
</form>
</div>
)
}
There's a common pattern there that you could probably move into a reusable component but you get the jist of it.

This code will work if react version is more than 16
import React, { useState } from 'react';
const SignupRadioIcons = () => {
const classes = useStyles();
const [selectedService, setSelectedService]= useState('dropbox');
return (
<div className={classes.iconsFormWrapper}>
<form>
<label className={`${classes.label} ${selectedService === 'dropbox' ? classes.selectedService : undefined}`}>
<DropboxIcon/> <span className={classes.iconName}>Dropbox</span>
<input type="radio" onChange={(e)=> setSelectedService('dropbox')} name="signup-service" value="dropbox" className={classes.input} />
</label>
<label className={`${classes.label} ${selectedService === 'googleDrive' ? classes.selectedService : undefined}`}>
<GoogleDriveIcon/> <span className={classes.iconName}>Google Drive</span>
<input type="radio" onChange={(e)=> setSelectedService('googleDrive')} name="signup-service" value="drive" className={classes.input} />
</label>
<label className={`${classes.label} ${selectedService === 'box' ? classes.selectedService : undefined}`}>
<BoxIcon/> <span className={classes.iconName}>Box</span>
<input type="radio" onChange={(e)=> setSelectedService('box')} name="signup-service" value="box" className={classes.input} />
</label>
</form>
</div>
)
}

Related

React Hook Form set checkbox to checked state

I am trying out React-Hook-form
The simple code for the checkbox is as below:
import React from 'react'
import { useForm } from 'react-hook-form'
export default function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm()
const onSubmit = (data: any) => console.log(data)
console.log(errors)
return (
<div className='mx-auto justify-center p-32 flex'>
<form onSubmit={handleSubmit(onSubmit)}>
<div className='p-2'>
<label htmlFor=''>January</label>
<input
type='checkbox'
placeholder='January'
{...register('January', {})}
className='mx-3'
checked
/>
</div>
<div className='p-2'>
<label htmlFor=''>February</label>
<input
type='checkbox'
placeholder='February'
{...register('February', {})}
className='mx-3'
/>
</div>
<input type='submit' />
</form>
</div>
)
}
I can submit the form correctly but I have like the January checkbox to start off as a checked box but when I put 'checked' as shown in the code, I somehow could not 'uncheck' it.
I seem to be missing something and any help would be greatly appreciated.
The issue with passing checked is that it takes control away from useForm to manage the checkbox.
Imagine the function register() returns { checked: true/false, onChange: changeHandler }. So if we where to look at the attributes this produces the following.
<input
type='checkbox'
placeholder='January'
{...register('January', {})}
className='mx-3'
checked
/>
<input
type='checkbox'
placeholder='January'
{...{
checked: true/false,
onChange: changeHandler,
}}
className='mx-3'
checked
/>
<input
type='checkbox'
placeholder='January'
checked={true/false}
onChange={changeHandler}
className='mx-3'
checked
/>
Since checked is present twice, the latter will override the former. In this case your checked is last so it overrides the value that is managed by useForm.
Passing it before the register() call won't help you either, since your default value will be overwritten by a value managed by useForm and is therefore never used.
Now that I've cleared up why this issue happens let's move on to the solution.
useForm allows you to pass a default values when you initially call the hook.
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ defaultValues: { January: true } });
// ...
<input
type='checkbox'
{...register("January")}
className='mx-3'
/>
Alternatively, instead of giving each checkbox its own name, you could also use "months". If there are multiple checkboxes using the same name, the result will not be true/false, but rather an array containing the values.
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
defaultValues: { months: ["January"] }
// only January ^ is checked by default
});
//...
<input
type='checkbox'
value='January'
{...register("months")}
className='mx-3'
/>
<input
type='checkbox'
value='February'
{...register("months")}
className='mx-3'
/>
The complete working code based on #3limin4tOr
import React, { useState } from 'react'
import { useForm } from 'react-hook-form'
export default function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
defaultValues: { months: ['January'] },
})
const onSubmit = (data: any) => console.log(data)
console.log(errors)
return (
<div className='mx-auto justify-center p-32 flex'>
<form onSubmit={handleSubmit(onSubmit)}>
<div className='p-2'>
<label htmlFor=''>January</label>
<input
type='checkbox'
value='January'
placeholder='January'
{...register('months')}
className='mx-3'
/>
</div>
<div className='p-2'>
<label htmlFor=''>February</label>
<input
type='checkbox'
value='February'
placeholder='February'
{...register('months')}
className='mx-3'
/>
</div>
<input type='submit' />
</form>
</div>
)
}
You can find here input, checkbox, and radio in one field with React Hook form
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import "./App.css";
import { FormProvider, useForm, useFormContext } from "react-hook-form";
const ChooseCarType = () => {
const { register } = useFormContext();
return (
<>
<div>
<label htmlFor="field-rain">
<input
{...register("weather")}
type="radio"
value="rain"
id="field-rain"
/>
Rain
</label>
<label htmlFor="field-wind">
<input
{...register("weather")}
type="radio"
value="wind"
id="field-wind"
/>
Lots of wind
</label>
<label htmlFor="field-sun">
<input
{...register("weather")}
type="radio"
value="sun"
id="field-sun"
/>
Sunny
</label>
</div>
</>
);
};
const ChooseService = () => {
const { register } = useFormContext();
return (
<>
<div>
<label>
<input type="checkbox" {...register("jan")} />
Jan
</label>
<label>
<input type="checkbox" {...register("feb")} />
Feb
</label>
<label>
<input type="checkbox" {...register("mar")} />
Mar
</label>
</div>
</>
);
};
const UserData = () => {
const { register } = useFormContext();
return (
<>
<div>
<label>
Username
<input {...register("username")} />
</label>
<label>
Password
<input {...register("password")} />
</label>
</div>
</>
);
};
function App() {
const methods = useForm();
const onSubmit = (data: any) => alert(JSON.stringify(data, null, 2));
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<ChooseCarType />
<ChooseService />
<UserData />
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}
export default App;

add styling in a dynamic html tags in react

hi i would to add the styling in dynamically generated html tags like, h1, img etc. I have generated the html tags through mapping but now i want to add some styling on it but i am unable to add styling on already generated tag.
const [textFieldStyles, setTextFieldStyles] = useState({
width: "5rem",
height: "2rem",
color: "blue",
fontSize: "20px",
});
const handleTextField = (e) => {
setTextFieldStyles({ ...textFieldStyles, [e.target.name]: e.target.value });
};
function handleAdd() {
const values = [...fields];
values.push({
top: 20,
left: 80,
tag: (
<h6
className="inputField"
style={{
width: textFieldStyles.width,
height: textFieldStyles.height,
color: textFieldStyles.color,
fontSize: textFieldStyles.fontSize,
}}
contentEditable="true"
>
hello
</h6>
),
});
setFields(values);
}
return (
<div>
fields.map((key, index)=>{
return {key.tag}
}
<label>width</label>
<input name="width" onChange={handleTextField} type="text" /><label>height</label>
<input name="height" onChange={handleTextField} type="text" /><label>color</label>
<input name="color" onChange={handleTextField} type="text" /><label>font Size</label>
<input name="fontSize" onChange={handleTextField} type="text" />
</div>
)```
Check this :
(Edited)
import React, { useState, useEffect } from "react";
export default function App() {
const [fields, setFields] = useState([]);
const [textFieldStyles, setTextFieldStyles] = useState({
width: "5rem",
height: "2rem",
color: "blue",
fontSize: "20px"
});
let values = [...fields];
const handleTextField = e => {
setTextFieldStyles({ ...textFieldStyles, [e.target.name]: e.target.value });
};
const handleAdd = () => {
values.push({
top: 20,
left: 80,
tag: (
<h6 className="inputField" style={textFieldStyles}>
hello
</h6>
)
});
setFields(values);
};
const submitForm = e => {
e.preventDefault();
handleAdd();
document.getElementById("form").reset();
};
useEffect(() => {
handleAdd();
}, []);
return (
<div>
{fields.map((key, index) => key.tag)}
<form id="form" onSubmit={submitForm}>
<label>width</label>
<input name="width" onChange={handleTextField} type="text" />
<label>height</label>
<input name="height" onChange={handleTextField} type="text" />
<label>color</label>
<input name="color" onChange={handleTextField} type="text" />
<label>font Size</label>
<input name="fontSize" onChange={handleTextField} type="text" />
<button type="submit">Click</button>
</form>
</div>
);
}
Demo: stackblitz

ReactJS TypeError: Cannot read property 'setState' of undefined even after binding in the constructor and also using the arrow function

I'm getting the error at line 116
checked={this.setState({selectedOption: "Male"})}
If I remove that line, then I get the same error at the next line.
import React, { Component } from "react";
import { Document, Page } from "react-pdf";
import { pdfjs } from 'react-pdf';
import SplitPane, { Pane } from 'react-split-pane';
import { Button } from 'react-bootstrap';
import axios from 'axios';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
export class TestPage extends React.Component{
constructor(props){
super(props);
this.state = {
numPages: null,
pageNumber: 1,
items: [],
responses: [],
color: '',
name: "React",
selectedOption: "Male",
};
this.onValueChange = this.onValueChange.bind(this);
this.formSubmit = this.formSubmit.bind(this);
this.goToPrevPage = this.goToPrevPage.bind(this);
this.goToNextPage = this.goToNextPage.bind(this);
this.onDocumentLoadSuccess = this.onDocumentLoadSuccess.bind(this);
}
componentDidMount(){
axios.get("http://localhost:5000/getquestiondata")
.then(
(result) => {
this.setState({
items: result.data.number,
});
}).catch(error => {
console.log("Error: ", error)
})
}
onDocumentLoadSuccess = ({ numPages }) => {
this.setState({ numPages: numPages });
};
formSubmit = event => {
event.preventDefault();
console.log(this.state.selectedOption);
}
onValueChange = event =>{
this.setState({
selectedOption: event.target.value
});
}
goToPrevPage = () =>
this.setState(state => ({ pageNumber: state.pageNumber - 1 }));
goToNextPage = () =>
this.setState(state => ({ pageNumber: state.pageNumber + 1 }));
render() {
const { pageNumber, numPages, items } = this.state;
var quesNos = [];
// var resp = [];
for(var i = 0; i < items; i++){
quesNos.push(i);
// resp.push(i);
}
console.log("QuesNos: ",quesNos);
return (
<div>
<h1 style={{textAlign: "center"}}>Online Test #1</h1>
<hr />
<SplitPane
split="vertical"
sizes={[50,50]}
defaultSize={parseInt(localStorage.getItem('splitPos'), 10)}
onChange={size => localStorage.setItem('splitPos', size)}
style={{overflow: 'scroll'}}
>
<div style={{overflowY: 'scroll', height:'100%', overflowX: 'scroll'}}>
<nav>
<button onClick={this.goToPrevPage}>Prev</button>
<button onClick={this.goToNextPage}>Next</button>
</nav>
<div>
<Document
file={require('./sample.pdf')}
onLoadSuccess={this.onDocumentLoadSuccess}
>
<Page pageNumber={pageNumber}/>
</Document>
</div>
<p>
Page {pageNumber} of {numPages}
</p>
</div>
<div style={{overflowY: 'scroll', height:'100%', overflowX: 'scroll'}}>
<form onSubmit={this.answerSubmit}>
{quesNos.map(function(qno){
return(
<div>
<div className="radio">
<label>
<input
type="radio"
value="Male"
checked={this.setState({selectedOption: "Male"})}
onChange={this.onValueChange}
/>
Male
</label>
</div>
<div className="radio">
<label>
<input
type="radio"
value="Female"
checked={this.state.selectedOption === "Female"}
onChange={this.onValueChange}
/>
Female
</label>
</div>
<div className="radio">
<label>
<input
type="radio"
value="Other"
checked={this.state.selectedOption === "Other"}
onChange={this.onValueChange}
/>
Other
</label>
</div>
<div>
Selected option is : {this.state.selectedOption}
</div>
<Button variant="btn btn-primary" type="submit">Submit</Button>
</div>
);
})}
<Button variant='primary' type="submit">Submit</Button>{' '}
</form>
</div>
</SplitPane>
</div>
);
}
}
Any idea why this error happens and how to fix it?
Thanks!
Two errors in your code
Your map function must be an arrow function to leverage this of the class/ or you use .bind for the map function
You must not setState directly in render. I assume you meant to compare state with checked attribute like checked={this.state.selectedOption === "Male"}
Updated code below
{quesNos.map((qno) => { // arrow function here
return(
<div>
<div className="radio">
<label>
<input
type="radio"
value="Male"
checked={this.state.selectedOption === "Male"} // Comparison here
onChange={this.onValueChange}
/>
Male
</label>
</div>
<div className="radio">
<label>
<input
type="radio"
value="Female"
checked={this.state.selectedOption === "Female"}
onChange={this.onValueChange}
/>
Female
</label>
</div>
<div className="radio">
<label>
<input
type="radio"
value="Other"
checked={this.state.selectedOption === "Other"}
onChange={this.onValueChange}
/>
Other
</label>
</div>
<div>
Selected option is : {this.state.selectedOption}
</div>
<Button variant="btn btn-primary" type="submit">Submit</Button>
</div>
);
})}

How can I Remove a className from other nested siblings after Radio input changed in React?

I am trying to change the label color of a single radio input. The color is changing. However, when I click on other radio buttons the class persist. I only want one radio button to have the changed class. Here is my code. So far I have tired running a loop over it and used classList Remove to take the class off of the other label elements. Everything I have tried is in the if statement, I was trying to compare the label Element to the others.
Here is a codeSandbox so its easier to answer https://codesandbox.io/s/naughty-curie-b8fd2?fontsize=14
function checkInput(e) {
let labelElement = e.target.parentNode;
let bgElement = labelElement.parentNode.parentNode;
bgElement.classList.add('checked');
if( labelElement !== labelElement ){
var oldChecked = document.getElementsByClassName('checked');
for (let i = 0; i < oldChecked.length; i++){
oldChecked[i].classList.remove('checked');
}
}
}
class ServiceLevel extends Component {
render(){
return (
<div>
<Segment>
<Menu attached="top" borderless={true}>
<Menu.Item>
<Header>Scale:</Header>
<Label className="filter_check" size="large">
<Form.Radio
label="Year"
control="input"
type="radio"
name="period"
onChange={checkInput}
/>
</Label>
<Label className="filter_check" size="large">
<Form.Radio
label="Quarter"
control="input"
type="radio"
name="period"
value="quarter"
onChange={checkInput}
/>
</Label>
<Label className="filter_check" size="large">
<Form.Radio
label="Month"
control="input"
type="radio"
name="period"
value="month"
onChange={checkInput}
/>
</Label>
<Label className="filter_check" size="large">
<Form.Field
label="Date"
control="input"
type="radio"
name="period"
value="date"
onChange={checkInput}
/>
</Label>
<Label className="filter_check" size="large">
<Form.Field
label="Interval"
control="input"
type="radio"
name="period"
value="interval"
onChange={checkInput}
/>
</Label>
</Menu.Item>
</Menu>
</Segment>
</div>
)
}
}
export default ServiceLevel;
Css is even tho you probably only need the JSX
.filter_check input {
display: none;
}
div.ui.large.label.filter_check.checked {
background-color: #00b5ad;
}
It's a bad practice to manipulate classes through DOM API. So you need to add state to your component in which you will store selected value. Here the working code:
https://codesandbox.io/embed/stackoverflow-one-inputchecked-bgcolored-m1w07
const periodFilters = [
{label: "Year",value: "year"},
{label: "Quarter", value: "quarter"},
{label: "Month", value: "month"},
{label: "Date", value: "date"},
{label: "Interval", value: "interval"}
];
class ServiceLevel extends Component {
constructor() {
super();
this.state = {
value: null
};
}
checkInput(value) {
this.setState({
value
});
}
render() {
return (
<div>
<Segment>
<Menu attached="top" borderless={true}>
<Menu.Item>
<Header>Scale:</Header>
{periodFilters.map(item => {
const isChecked = item.value === this.state.value;
return (
<Label
className={`filter_check ${isChecked ? "checked" : ""}`}
size="large"
>
<Form.Radio
label={item.label}
control="input"
type="radio"
name="period"
value={item.value}
onChange={() => this.checkInput(item.value)}
/>
</Label>
);
})}
</Menu.Item>
</Menu>
</Segment>
</div>
);
}
}

Deleting Dynamic Element Reactjs

--FieldSection.js--
import React, { Component } from 'react';
import Field from './Field.js';
class FieldSection extends Component{
constructor(props){
super(props);
this.state ={
numberOfFields: 1
}
}
addField = () => {
const { numberOfFields } = this.state;
this.setState({ numberOfFields: numberOfFields + 1 });
}
listFields = (numberOfFields) => {
var fields = [];
for(var i=0; i<numberOfFields; i++){
fields.push(
(<Field number={i} />)
)
}
return fields;
}
render () {
const {listFields, addField} = this;
const {numberOfFields} = this.state;
return (
<div>
<label><u>Fields</u></label>
{listFields(numberOfFields)}
<div id="fieldButtons">
<button id="addField" type="button" onClick={addField}> Add Field </button>
<button id="removeField" type="button"> Remove Field </button>
</div>
</div>
)
}
}
export default FieldSection;
-----------------Field.js-------------------
import React from 'react';
class Field extends React.Component {
constructor(props){
super(props);
this.state = {
value: 'empty',
specVisible: 'hidden',
display: 'none'
};
}
SelectChange = (event) => {
this.setState({value: event.target.value});
if(event.target.value === "string" )
{
this.setState({specVisible: 'visible'});
this.setState({display: 'block'});
}
else {
this.setState({specVisible: 'hidden'})
this.setState({display: 'none'})
}
}
render (){
const {SelectChange} = this;
const {value, specVisible, display} = this.state;
return (
<div>
<div>
<label><strong>New Field </strong></label>
<div id="remove-" className="remove" style={{display: "inline", visibility: "hidden"}}>
<label> --Remove </label> <input type="checkbox" id="removeBox" className="rmvCheckbox" />
<br />
</div>
<label> Name: </label>
<input id="name-" className="name" type="text" name="name" /> <br />
<label> Description: </label>
<input id="description-" className="description" name="description" /> <br />
<label> Datatype: </label>
<select value={value} onChange={SelectChange} id={`selectData-${this.props.number}`} className="selectData" name="selectData" /*onClick={AddListener}*/>
<option value="empty"> </option>
<option value="string"> String </option>
<option value="character"> Character </option>
<option value="timestamp"> Timestamp </option>
<option value="integer"> Integer </option>
<option value="long"> Long </option>
<option value="double"> Double </option>
<option value="boolean"> Boolean </option>
</select> <br />
</div>
<div id={`specifySection-${this.props.number}`} className="specifySection" style={{visibility: specVisible, display: display}} >
<label> Specify Length: </label>
<input className="specifyLength" type="text" name="length"/> <br />
</div>
</div>
)}
}
export default Field
I am trying to implement a feature where you click "Remove Field" button and a list of checkboxes next to all the new fields appears. Whenever you confirm the deletion, it would delete all the elements that are selected.
Obviously I could subtract one to the numberOfFields state, however I want to delete specific elements, rather than just the last field.
What would that look like?
You can do it as follows. The basic idea is to get all the ids of the fields that need to be deleted and iterate over them and delete all the components corresponding to these ids.
Sandbox for code
When addField is called fields state of FieldsSelection
component is updated by adding a key with unique id, with the Field
component as its value along with all the props.
The remove state tracks if remove Field has been clicked and
toggles the remove checkbox in each Field component by passing it
as a prop.
fieldsToRemove state is updated via the markFields prop in
Field component each time a remove field checkbox is clicked.
When deleteFields is called, it iterates over the fieldsToRemove
state and removes the corresponding components from the fields
state object.
I used uuid package for unique ids for each Field as opposed to deleting
via index, which is not a great way and also conflicts with the key prop of
react.
FieldSection.js
import React, { Component } from "react";
import Field from "./Field.js";
import { v4 } from "uuid";
class FieldSection extends Component {
constructor(props) {
super(props);
this.state = {
fields: {},
remove: false,
fieldsToRemove: []
};
}
addField = () => {
const fields = this.state.fields;
const id = v4();
fields[id] = <Field key={id} id={id} mark={this.markFields} />;
this.setState({ fields });
};
listFields = () => {
var ids = Object.keys(this.state.fields);
return ids.map(id => {
return React.cloneElement(this.state.fields[id], {
remove: this.state.remove
});
});
};
markFields = (checked, i) => {
if (checked) {
const arr = [...this.state.fieldsToRemove];
arr.push(i);
this.setState({ fieldsToRemove: arr });
} else {
const arr = this.state.fieldsToRemove.filter(x => i !== x);
this.setState({ fieldsToRemove: arr });
}
};
removeFields = () => {
this.setState({ remove: !this.state.remove });
};
deleteFields = () => {
const fields = { ...this.state.fields };
this.state.fieldsToRemove.forEach(id => {
delete fields[id];
});
this.setState({ fields, fieldsToRemove: [], remove: false });
};
render() {
const { listFields, addField, removeFields, deleteFields } = this;
const { numberOfFields, remove } = this.state;
return (
<div>
<label>
<u>Fields</u>
</label>
{listFields()}
<div id="fieldButtons">
<button id="addField" type="button" onClick={addField}>
{" "}
Add Field{" "}
</button>
<button id="removeField" type="button" onClick={removeFields}>
{" "}
Remove Field{" "}
</button>
<br />
<button type="button" onClick={deleteFields}>
{" "}
Delete Fields{" "}
</button>
</div>
</div>
);
}
}
export default FieldSection;
Field.js
import React from "react";
class Field extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "empty",
specVisible: "hidden",
display: "none"
};
}
SelectChange = event => {
this.setState({ value: event.target.value });
if (event.target.value === "string") {
this.setState({ specVisible: "visible" });
this.setState({ display: "block" });
} else {
this.setState({ specVisible: "hidden" });
this.setState({ display: "none" });
}
};
render() {
const { SelectChange } = this;
const { value, specVisible, display } = this.state;
const styles = this.props.remove
? { display: "inline", visibility: "visible" }
: { display: "inline", visibility: "hidden" };
return (
<div>
<div>
<label>
<strong>New Field </strong>
</label>
<div id="remove-" className="remove" style={styles}>
<label> --Remove </label>{" "}
<input
type="checkbox"
id="removeBox"
className="rmvCheckbox"
onChange={e => {
this.props.mark(e.target.checked, this.props.id);
}}
/>
<br />
</div>
<label> Name: </label>
<input id="name-" className="name" type="text" name="name" /> <br />
<label> Description: </label>
<input
id="description-"
className="description"
name="description"
/>{" "}
<br />
<label> Datatype: </label>
<select
value={value}
onChange={SelectChange}
id={`selectData-${this.props.number}`}
className="selectData"
name="selectData" /*onClick={AddListener}*/
>
<option value="empty"> </option>
<option value="string"> String </option>
<option value="character"> Character </option>
<option value="timestamp"> Timestamp </option>
<option value="integer"> Integer </option>
<option value="long"> Long </option>
<option value="double"> Double </option>
<option value="boolean"> Boolean </option>
</select>{" "}
<br />
</div>
<div
id={`specifySection-${this.props.number}`}
className="specifySection"
style={{ visibility: specVisible, display: display }}
>
<label> Specify Length: </label>
<input className="specifyLength" type="text" name="length" /> <br />
</div>
</div>
);
}
}
export default Field;

Categories

Resources