I have 3 components. Main Component named 'XYZ' which contains 2 other components named 'RadioButton' and 'Textbox'. I need to update state of Textbox component (Setting value to 500) on change of RadioButton component. Below is my code. I am new to React development. Any help would be surely appreciated!
Main Component - XYZ.tsx
const XYZ = (props) => {
let lossCapacityValues = [
{ value: "Average", text: "Average" },
{ value: "Medium", text: "Medium" }
];
const lossCapacityChange = (event) => {
let ele = $(event.target);
};
return (
<div className="mel-box">
<RadioButton groupName="radio-loss-type" selectedValue="2" items={lossCapacityValues} onChange={lossCapacityChange} />
<TextBox value="1000" />
</div>
)
};
export default XYZ;
Child Component - 1- RadioButton.tsx
const RadioButton = (props) => {
const onChange = (event) => {
props.onChange && props.onChange(event);
}
return (
<div>
{
props.items.map((i: { "text": string, "value": string }, index: number) => {
return (
<div key={index} className="radiobutton-option-div">
<input
className="radio-custom"
type="radio"
name={props.groupName}
value={i.value}
defaultChecked={i.value === props.selectedValue}
onChange={onChange}
/>
<label className="radio-custom-label">
<span className="radio-custom-text">{i.text}</span>
</label>
</div>
)
})
}
</div>
);
};
export default RadioButton;
Child Component - 2- TextBox.tsx
const TextBox = (props) => {
const [state, setState] = React.useState({
value: props.value
});
const handleChange = (evt) => {
setState({ value: evt.target.value });
};
return (
<React.Fragment>
<div className="input-text">
<input
type="text"
value={state.value}
onChange={handleChange}
/>
</div>
</React.Fragment>
)
};
export default TextBox;
This can be done by lifting the state up, out of the TextBox component into the XZY component.
Working demo
In XYZ, put the text state handlers:
const XYZ = (props) => {
const [textState, setTextState] = React.useState({
value: 1000
});
const handleTextChange = (evt) => {
setTextState({ value: evt.target.value });
};
....
....
};
Pass them as props:
<TextBox value={textState.value} onChange={handleTextChange} />
Change your input to use them:
<input
type="text"
value={props.value}
onChange={props.onChange}
/>
In your XYZ you can check the selected radio value and set the text state accordingly.
const lossCapacityChange = (event) => {
//let ele = $(event.target);
if(event.target.value == 'Average'){
setTextState({ value: 500 });
}
};
Side note: you should avoid the use of jQuery in React unless there is a good reason for it. React uses a Virtual DOM which can be affected by jQuery use.
Related
I need to update the state on main page, the problem is that I update values 3 levels down...
This component is where I get all the cities, putting the data on the State and map through cities.
CitiesPage.tsx
export const CitiesPage = () => {
const [cities, setCities] = useState<City[]>([]);
useEffect(() => {
getCities().then(setCities);
}, []);
return (
<>
<PageTitle title="Cities" />
<StyledCitySection>
<div className="headings">
<p>Name</p>
<p>Iso Code</p>
<p>Country</p>
<span style={{ width: "50px" }}></span>
</div>
<div className="cities">
{cities.map((city) => {
return <CityCard key={city.id} city={city} />;
})}
</div>
</StyledCitySection>
</>
);
};
On the next component, I show cities and options to show modals for delete and update.
CityCard.tsx.
export const CityCard = ({ city }: CityProps) => {
const [showModal, setShowModal] = useState(false);
const handleModal = () => {
setShowModal(true);
};
const handleClose = () => {
setShowModal(false);
};
return (
<>
{showModal && (
<ModalPortal onClose={handleClose}>
<EditCityForm city={city} closeModal={setShowModal} />
</ModalPortal>
)}
<StyledCityCard>
<p className="name">{city.name}</p>
<p className="isoCode">{city.isoCode}</p>
<p className="country">{city.country?.name}</p>
<div className="options">
<span className="edit">
<FiEdit size={18} onClick={handleModal} />
</span>
<span className="delete">
<AiOutlineDelete size={20} />
</span>
</div>
</StyledCityCard>
</>
);
};
and finally, third levels down, I have this component.
EditCityForm.tsx.
export const EditCityForm = ({ city, closeModal }: Props) => {
const [updateCity, setUpdateCity] = useState<UpdateCity>({
countryId: "",
isoCode: "",
name: "",
});
const handleChange = (
evt: ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
const { target } = evt;
setUpdateCity({ ...updateCity, [target.name]: target.value });
};
const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();
const { id: cityId } = city;
updateCityHelper(cityId, updateCity);
closeModal(false);
};
useEffect(() => {
setUpdateCity({
isoCode: city.isoCode,
name: city.name,
countryId: city.country?.id,
});
}, [city]);
return (
<form onSubmit={handleSubmit}>
<Input
label="Iso Code"
type="text"
placeholder="Type a IsoCode..."
onChange={handleChange}
name="isoCode"
value={updateCity.isoCode}
/>
<Input
label="Name"
type="text"
placeholder="Paste a Name.."
onChange={handleChange}
name="name"
value={updateCity.name}
/>
<CountrySelect
label="Country"
onChange={handleChange}
value={city.country?.name || ""}
name="countryId"
/>
<Button type="submit" color="green" text="Update" />
</form>
);
};
Edit form where retrieve data passed from CityCard.tsx and update State, passing data to a function that update Info, closed modal and... this is where I don't know what to do.
How can I show the info updated on CitiesPage.tsx when I submitted on EditCityForm.tsx
Any help will be appreciated.
Thanks!
You are storing the updated value in a different state, namely the updateCity state, but what you should be doing is update the origional cities state. While these two states are not related, and at the same time your UI is depend on cities state's data, so if you wish to update UI, what you need to do is update cities' state by using it's setter function setCities.
Just like passing down state, you pass it's setters as well, and use the setter function to update state's value:
// CitiesPage
{cities.map((city) => {
return <CityCard key={city.id} city={city} setCities={setCities} />;
})}
// CityCard
export const CityCard = ({ city, setCities }: CityProps) => {
// ...
return (
// ...
<ModalPortal onClose={handleClose}>
<EditCityForm city={city} closeModal={setShowModal} setCities={setCities} />
</ModalPortal>
// ...
)
}
// EditCityForm
export const EditCityForm = ({ city, closeModal, setCities }: Props) => {
// const [updateCity, setUpdateCity] = useState<UpdateCity>({ // no need for this
// countryId: "",
// isoCode: "",
// name: "",
// });
const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();
const { id: cityId } = city;
setCities(); // your update logic
closeModal(false);
};
}
I'd advise you to use React-Redux or Context API whenever you have nested structures and want to access data throughout your app.
However in this case you can pass setCities and cities as a prop to CityCard and then pass this same prop in the EditCityForm component and you can do something like this in your handleSubmit.
const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();
let updatedCities = [...cities];
updatedCities.forEach(el => {
if(el.id == updateCity.id) {
el.countryCode = updateCity.countryCode;
el.name = updateCity.name;
el.isoCode = updateCity.isoCode;
}
})
setCities(updatedCities);
closeModal(false);
};
I have a form component where I create the template of fields that can be initiated in another component, then in the parent component's template, I define fields properties. Problem is that my form submit button is in the parent component which means I have no access to input values from here.
This is Parent component:
function SignIn() {
//Here I will be doing API call and will need to use input values
};
function Login() {
let template = {
fields: [
{
title: data.email,
type: 'email',
name: data.email,
placeholder: data.inputPlaceholder,
icon: letter,
},
{
title: data.password,
type: 'password',
name: data.password,
placeholder: data.inputPlaceholder,
icon: lock,
}
]
}
return (
<Form template={template} />
<Button btnstyle={'mazhrBtn light-green'} onClick={SignIn}>
{data.signInButton}
</Button>
);
}
This is Form component:
function Form({ template, children, onSubmit, errors }) {
const [ value , setValue ] = useState('');
let [ formErrors, setFormErrors] = useState(false);
let { fields } = template;
const handleSubmit = (event) => {
event.preventDefault();
onSubmit({
text: value
});
if(errors) {
formErrors = true;
setFormErrors(formErrors);
} else {
formErrors = false;
setFormErrors(formErrors);
event.target.reset();
document.querySelector('.editable').innerHTML = "";
}
};
const renderFields = (fields) => {
return fields.map((field, index) => {
let { title, type, name, placeholder, icon, textArea, dropdown, dropdownTemplate } = field;
const onChange = (event) => {
let eName = event.target.name;
let eValue = event.target.value;
setValue({
...value,
[eName]: eValue
});
console.log(eName + ':' + eValue);
if(icon) {
if (event.target.value !== '') {
document.getElementById('icon' + index).classList.add('active');
} else {
document.getElementById('icon' + index).classList.remove('active');
}
}
};
return (
<div key={index} className="form-field">
<label htmlFor={name}>{title}</label>
<div className="input-wrapper">
{dropdown ?
<DropdownButton dropdownTemplate={dropdownTemplate} placeholder={placeholder} customClass='input'/>
:
textArea ?
<p className="m-0 editable"
contentEditable
type={type}
name = {name}
id={name}
placeholder={placeholder}
value={index.value}
onChange={onChange}
suppressContentEditableWarning={true}
onInput={
e => setValue(
{
...value,
[e.target.getAttribute("name")]: e.currentTarget.textContent
}
)
}
></p>
:
<input
type={type}
name={name}
id={name}
placeholder={placeholder}
value={index.value}
onChange={onChange}
/>
}
{icon ? <img id={'icon' + index} className="icon" src={icon} alt="icon"/> : ''}
</div>
</div>
)
})
}
return (
<>
<form className="form" onSubmit={handleSubmit}>
{ renderFields(fields) }
{children}
</form>
</>
)
}
Sorry in advance for a messy code, I'm learning React
In general, when you need the state in a parent component, the best thing to do is to lift the state up. See https://reactjs.org/docs/lifting-state-up.html
That means moving your state hooks to the parent component and passing them down as props to the child component
I have a bunch of checkboxes with the following markup
<input type='checkbox' data-id='123' data-label='abc' ref={checkboxRef} />
<input type='checkbox' data-id='456' data-label='xyz' ref={checkboxRef} />
And a state which is initially set as an empty array
const [contacts, setContacts] = useState([])
What I want to do is update the state with an object of a checkbox's data based on whether it's checked or not. If checked, it's data is to be added to the state and if unchecked, remove it.
Expected state after a checkbox is checked
[
{ id: '123', label: 'abc' }
]
I've used a ref for now to the input and getting the data of it but can't figure out how to go about updating the state.
const handleToggle = () => {
setIsChecked(prevState => !isChecked)
const id = checkboxRef.current.getAttribute('data-id')
const label = checkboxRef.current.getAttribute('data-label')
}
I have solved it. Check it here.
https://codesandbox.io/s/affectionate-fermi-f6bct
Full code hereby is
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [contacts, setContacts] = useState([]);
const ref1 = React.createRef();
const ref2 = React.createRef();
const handleClick = (ref) => {
const id = ref.current.getAttribute("data-id");
const label = ref.current.getAttribute("data-label");
if (contacts.map((e) => e.id).includes(id)) {
setContacts(contacts.filter((e) => e.id !== id));
} else {
setContacts([...contacts, { id, label }]);
}
console.log(contacts);
};
return (
<div className="App">
<input
type="checkbox"
data-id="123"
data-label="abc"
ref={ref1}
onClick={() => {
console.log("hi");
handleClick(ref1);
}}
/>
<input
type="checkbox"
data-id="456"
data-label="xyz"
ref={ref2}
onClick={() => handleClick(ref2)}
/>
</div>
);
}
I made a PoC to see how to handle my change detection on a dynamic list of checkboxes (note, i do not know beforehand how many checkboxes i have.) I created a n ES6 map (Dictionary) that tracks the checked state of each individual checkbox. but for some reason I get the following error:
A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
Usually when my number of form input fields is known i track them via the state, but how would one handle this case. The logic works fine, but I need to get rid of the error.
My app code:
import React, { Component } from "react";
import Checkbox from "./checkbox";
class App extends Component {
constructor(props) {
super(props);
this.state = {
checkedItems: new Map()
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = e => {
const item = e.target.name;
const isChecked = e.target.checked;
this.setState(prevState => ({
checkedItems: prevState.checkedItems.set(item, isChecked)
}));
};
deleteCheckboxState = (name, checked) => {
const updateChecked = typeof checked === "undefined" ? true : false;
this.setState(prevState => prevState.checkedItems.set(name, updateChecked));
};
clearAllCheckboxes = () => {
const clearCheckedItems = new Map();
this.setState({ checkedItems: clearCheckedItems });
};
render() {
const checkboxes = [
{
name: "check-box-1",
key: "checkBox1",
label: "Check Box 1"
},
{
name: "check-box-2",
key: "checkBox2",
label: "Check Box 2"
},
{
name: "check-box-3",
key: "checkBox3",
label: "Check Box 3"
},
{
name: "check-box-4",
key: "checkBox4",
label: "Check Box 4"
}
];
const checkboxesToRender = checkboxes.map(item => {
return (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={this.state.checkedItems.get(item.name)}
onChange={this.handleChange}
type="checkbox"
/>
</label>
);
});
const checkboxesDeleteHandlers = checkboxes.map(item => {
return (
<span
key={item.name}
onClick={() =>
this.deleteCheckboxState(
item.name,
this.state.checkedItems.get(item.name)
)
}
>
{item.name}
</span>
);
});
return (
<div className="App">
{checkboxesToRender}
<br /> {checkboxesDeleteHandlers}
<p onClick={this.clearAllCheckboxes}>clear all</p>
</div>
);
}
}
export default App;
The checkbox reusable component:
import React from "react";
class Checkbox extends React.Component {
render() {
return (
<input
type={this.props.type}
name={this.props.name}
checked={this.props.checked}
onChange={this.props.onChange}
/>
);
}
}
export default Checkbox;
The problem is that the checked state is initially undefined, which is a falsy value, but interpreted as not provided.
So you can simply ensure that the falsy state will actually be false by using !!.
So change the line
checked={this.state.checkedItems.get(item.name)}
to this
checked={!!this.state.checkedItems.get(item.name)}
React gives you this warning because it likes that you chose between controlled and uncontrolled components.
In the case of a checkbox input the component is considered controlled when its checked prop is not undefined.
I've just given a default value for checked and changed the code testing for undefined a little bit.
The warning should be gone.
// import React, { Component } from "react";
class Checkbox extends React.Component {
static defaultProps = {
checked: false
}
render() {
return (
<input
type={this.props.type}
name={this.props.name}
checked={this.props.checked}
onChange={this.props.onChange}
/>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedItems: new Map()
};
this.handleChange = this.handleChange.bind(this);
}
handleChange = e => {
const item = e.target.name;
const isChecked = e.target.checked;
this.setState(prevState => ({
checkedItems: prevState.checkedItems.set(item, isChecked)
}));
};
deleteCheckboxState = (name, checked) => {
const updateChecked = checked == null ? true : false;
this.setState(prevState => prevState.checkedItems.set(name, updateChecked));
};
clearAllCheckboxes = () => {
const clearCheckedItems = new Map();
this.setState({ checkedItems: clearCheckedItems });
};
render() {
const checkboxes = [
{
name: "check-box-1",
key: "checkBox1",
label: "Check Box 1"
},
{
name: "check-box-2",
key: "checkBox2",
label: "Check Box 2"
},
{
name: "check-box-3",
key: "checkBox3",
label: "Check Box 3"
},
{
name: "check-box-4",
key: "checkBox4",
label: "Check Box 4"
}
];
const checkboxesToRender = checkboxes.map(item => {
return (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={this.state.checkedItems.get(item.name) || false}
onChange={this.handleChange}
type="checkbox"
/>
</label>
);
});
const checkboxesDeleteHandlers = checkboxes.map(item => {
return (
<span
key={item.name}
onClick={() =>
this.deleteCheckboxState(
item.name,
this.state.checkedItems.get(item.name)
)
}
>
{item.name}
</span>
);
});
return (
<div className="App">
{checkboxesToRender}
<br /> {checkboxesDeleteHandlers}
<p onClick={this.clearAllCheckboxes}>clear all</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I am trying to render a custom input field, and not use the default one, as I need some extra elements surrounding my input
This is my component, PetalkInvitationModal. The other 2 are examples of components that I'm trying to hack together for rendering the actual input field. I've initially tried to use RenderInputField, but then I started building RenderInputModalField, so to be able to control the props manually
import React from 'react'
import {GenericActionOrCancelModal} from "../GenericActionOrCancelModal";
import {reduxForm, Field} from "redux-form";
import Datetime from 'react-datetime';
import WarningAlert from "./components/alerts";
export class PetalkInvitationModal extends GenericActionOrCancelModal {
this.renderDateInputOutsideForm = this.renderDateInputOutsideForm.bind(this);
this.renderDateInputField = this.renderDateInputField.bind(this);
this.renderDateInputAsFormField = this.renderDateInputAsFormField.bind(this);
}
renderDateInputOutsideForm(options) {
return <Datetime
closeOnSelect={true}
renderInput={this.renderDateInputField(options)}/>;
}
/**
* #param {{id}, {label}, {name}, {required}} options
*/
renderDateInputField(options) {
return (props, openCalendar, closeCalendar)=> {
return (
<div className="form-group">
<div className="form-label-group">
<input
className="form-control"
{...props}/>
<label htmlFor={options.id} onClick={openCalendar}>{options.label}</label>
</div>
</div>
)
}
}
renderDateInputAsFormField(props) {
const {input, inputProps, meta: {touched, error}} = props;
// I'm trying to pass all the callbacks that I get, down to my component
// I tried calling the callbacks from props2, and from olderInput
const RenderInputModalWrapper = (
(props2) => <RenderInputModalField olderInput={input} {...props2}/>
);
return <Datetime {...input} {...inputProps} renderInput={RenderInputModalWrapper}/>
}
renderFields() {
return (
<div>
{/*this doesn't work, sadly :( */}
<Field
name="option1"
type="text"
component={this.renderDateInputAsFormField}
inputProps={{
closeOnSelect: true
}}
/>
{/*this works...but it's not controlled by redux-form */}
{this.renderDateInputOutsideForm({
label: 'Erste Option (erforderlich)',
name: 'option1',
})}
</div>
)
}
}
function validate(values) {
//...
}
export function RenderInputField(field) {
const {input, meta: {touched, error, valid}} = field;
const error_message = touched && !valid ? error : "";
return (
<div className="form-group">
<label htmlFor={field.htmlFor}>{field.label}</label>
<input {...field.input}
id={field.htmlFor}
className={field.className}
type={field.type ? field.type : "text"}
autoFocus={field.autoFocus}
placeholder={field.placeholder}
readOnly={field.readOnly ? field.readOnly : false}
/>
</div>
)
}
export function RenderInputModalField(props) {
let extras = {};
let {onChange, onBlur, onFocus, onDragStart, onDrop} = props;
if(props.olderInput){
// this is not good at all field doesn't respond to clicks now
// onChange = (evt) => (props.olderInput.onChange(evt), onChange(evt))
// onBlur = (evt) => (props.olderInput.onBlur(evt), onBlur(evt))
// onFocus = (evt) => (props.olderInput.onFocus(evt), onFocus(evt))
// onDragStart = (evt) => (props.olderInput.onDragStart(evt), onDragStart(evt))
// onDrop = (evt) => (props.olderInput.onDrop(evt), onDrop(evt))
// this is not good; does something on the second click. only changes the
// field value the first time
// onChange = (evt) => (onChange(evt), props.olderInput.onChange(evt))
// onBlur = (evt) => (onBlur(evt), props.olderInput.onBlur(evt))
// onFocus = (evt) => (onFocus(evt), props.olderInput.onFocus(evt))
// onDragStart = (evt) => (onDragStart(evt), props.olderInput.onDragStart(evt))
// onDrop = (evt) => (onDrop(evt), props.olderInput.onDrop(evt))
// this also responds to open only on the second click.
// it also updates the value only the first time
// onChange = (evt) => onChange(evt)
// onBlur = (evt) => (onBlur(evt))
// onFocus = (evt) => (onFocus(evt))
// onDragStart = (evt) => (onDragStart(evt))
// onDrop = (evt) => (onDrop(evt))
// this doesn't respond to the first click, and doesn't update anything
// onChange = (evt) => ( props.olderInput.onChange(evt))
// onBlur = (evt) => (props.olderInput.onBlur(evt))
// onFocus = (evt) => (props.olderInput.onFocus(evt))
// onDragStart = (evt) => (props.olderInput.onDragStart(evt))
// onDrop = (evt) => (props.olderInput.onDrop(evt))
}
extras = {onChange, onBlur, onFocus, onDragStart, onDrop};
return (
<div className="form-group">
<label htmlFor={props.htmlFor}>{props.label}</label>
<input {...props}
{...extras}
id={props.htmlFor}
className={props.className}
type={props.type ? props.type : "text"}
autoFocus={props.autoFocus}
placeholder={props.placeholder}
readOnly={!!props.readOnly}
/>
</div>
)
}
export default reduxForm({
validate,
form: 'PetalkInvitationModalForm'
})(PetalkInvitationModal)
So as you can see, I tried calling and passing all the callbacks I could, but obviously I'm missing something, and I spent a lot of time on this already.
Any ideas are welcome
[EDIT]
Also, I need to mention that I did find this post https://github.com/YouCanBookMe/react-datetime/issues/552#issuecomment-392226610
so I know how to render a Datetime component inside a Field.
<Field
name="arrivalDate"
component={CustomDatetimePicker}
inputProps={{
timeFormat: false,
closeOnSelect: true,
dateFormat: 'DD/MM/YYYY',
}}
/>
const CustomDatetimePicker = props => {
const {
input,
inputProps,
meta: { touched, error }
} = props;
return (
<Datetime {...input} {...inputProps} />
);
};
I have also read the documentation of react-datetime where I'm shown how to render a customized input field inside a Datetime component:
https://github.com/YouCanBookMe/react-datetime#customize-the-input-appearance
var MyDTPicker = React.createClass({
render: function(){
return <Datetime renderInput={ this.renderInput } />;
},
renderInput: function( props, openCalendar, closeCalendar ){
function clear(){
props.onChange({target: {value: ''}});
}
return (
<div>
<input {...props} />
<button onClick={openCalendar}>open calendar</button>
<button onClick={closeCalendar}>close calendar</button>
<button onClick={clear}>clear</button>
</div>
);
},
});
I'm just too noob at react to be able to put those 2 together