I tried this solution, checking whether state variable is missing.
selectedOrganization?.barionId !== undefined
<TextFieldItem
primary="Barion ID"
required="**"
value={selectedOrganization?.barionId ?? ""}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
selectedOrganization.barionId = event.target.value;
setSelectedOrganization((preState) => ({
...preState,
barionId: event.target.value,
}));
}}
disabled={
selectedOrganization?.barionId !== undefined
}
/>
and has this state:
const [selectedOrganization, setSelectedOrganization] = useState<
OrganizationOut | undefined
>(undefined);
The challenge is that after the first character is filled into the textfield, the textfield gets disabled.
It's a little bit complicated. You have a field that maybe filled (or not). If you want to disable any field that have a previous value, I prefer this trick that use when I have same problem.
Define a variable for default value of the field
let defaultValueFromSomewhereElse : String = "";
If the defaultValueFromSomewhereElse has a value (ex. from an api), assign it. then
Define useState with default value
const [selectedOrganization, setSelectedOrganization] = useState<OrganizationOut>
(defaultValueFromSomewhereelse);
Disable field base on defaultValueFromSomewhereelse length.
<TextFieldItem
primary="Barion ID"
required="**"
value={defaultValueFromSomewhereElse}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
selectedOrganization.barionId = event.target.value;
setSelectedOrganization((preState) => ({
...preState,
barionId: event.target.value,
}));
}}
disabled={
defaultValueFromSomewhereElse.length > 0
}
/>
Related
I have a several ReactSelect components, and one piece of global state which is responsible for holding all my selected values in an array.
Select
<Select
styles={inputStyles}
className="basic-single"
classNamePrefix="select"
isClearable={true}
isSearchable={false}
placeholder={'Select Your Most Convenient Time Slot'}
options={newHoursArr}
isMulti={false}
onChange={(values) => handleChange(values)}
defaultValue={clientServiceReferral.selectedTimeSlots.map((referral) => (
referral.timeSlot === timeWithDate ? (
{ ['label']: referral.value, ['value']: referral.value }
) : null
))}
/>
handleChange function
const handleChange = (value) => {
const found = clientServiceReferral.selectedTimeSlots.find(element => element.timeSlot === timeWithDate);
if (found) {
clientServiceReferral.selectedTimeSlots.splice(found, 1)
}
const newValue = {
timeSlot: timeWithDate,
value: value.value
}
setClientServiceReferral({
...clientServiceReferral,
selectedTimeSlots: [...clientServiceReferral.selectedTimeSlots, newValue]
})
}
ReactSelect has an isClearable prop. Which allows users to clear the input with a button click. This returns a value of null when values is logged in the onChange function, but is there a way to return the actual value inside the select that is getting cleared when the clear button is clicked?
There's an optional second parameter passed to the onChange event. It's of this type:
export interface ActionMetaBase<Option> {
option?: Option | undefined;
removedValue?: Option;
removedValues?: Options<Option>;
name?: string;
}
Now, I've never used this library, but it looks like removedValue or removedValues could be helpful? idk.
Anyway, I got that from their docs. Hope it works out for you:
For anyone interested, Via Joshua Wood's answer, the value of any cleared item(s) can be found as so:
onChange={(values, removedValue) => handleChange(values, removedValue)}
const handleChange = (value, removedValue) => {
if (removedValue.action === 'clear') {
console.log('removed', removedValue.removedValues[0])
}
// removedValues returns an array
I am using react-colorful & using it's <HexColorInput /> component as:
<HexColorInput
className="w-12 text-blue-gray-500"
color={rgb2hex(selectedColor.rgba)}
onChange={(hex: string) => {
updateBackground({
selectedColor: {
...selectedColor,
rgba: hex2rgb(hex),
},
})
}}
/>
The color prop acts similar to value prop on input. And onChange handler changes as you type in an input.
The problem right now is when I make the hex input to be 3-digits, it automatically converts it to 6-digits as I'm directly using rgb2hex function on the input.
How do I fix this?
Codesandbox → https://codesandbox.io/s/react-colorful-sketch-picker-ouz5t
The common way to declare a hex color is using 6 digits. When using the 3 digits format, you are shortening the 6 digits format. This only works if the digits in the positions R (so 1,2), G (3,4) and B(5,6) are equals.
Eg: You can write #00CCFF in 3 digits format as #0CF, but you cannot do the same on #425416
Maybe this thread can make my point more clear.
I just had to check if hex.length is equal to 4 & if it is, then I return:
<HexColorInput
className="w-12 text-blue-gray-500"
color={rgb2hex(selectedColor.rgba)}
onChange={(hex: string) => {
if (hex.length === 4) return
updateBackground({
selectedColor: {
...selectedColor,
rgba: hex2rgb(hex),
},
})
}}
/>
The source of HexColorInput specifically adds # to it so for any 3-digit hex, it makes it a 4-digit.
Edit:
The above answer doesn't return to previous value if I blur when HexColorInput contains 3 digit value like #21b so I cloned HexColorInput & removed a validation from it that also supported 3 digit hex.
My new HexInput.tsx looks like:
import React from 'react'
import { useEventCallback } from '../hooks'
const hex6 = /^#?[0-9A-F]{6}$/i
const validHex = (color: string): boolean => hex6.test(color)
// Escapes all non-hexadecimal characters including "#"
const escapeNonHex = (hex: string) => hex.replace(/([^0-9A-F]+)/gi, '').substr(0, 6)
type ComponentProps = {
hex: string
onChange: (newColor: string) => void
}
type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'>
export const HexInput = ({
hex = '',
onChange,
onBlur,
...rest
}: Partial<InputProps & ComponentProps>) => {
const [value, setValue] = React.useState(() => escapeNonHex(hex))
const onChangeCallback = useEventCallback<string>(onChange)
const onBlurCallback = useEventCallback<React.FocusEvent<HTMLInputElement>>(onBlur)
// Trigger `onChange` handler only if the input value is a valid HEX-color
const handleChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = escapeNonHex(e.target.value)
setValue(inputValue)
if (validHex(inputValue)) onChangeCallback('#' + inputValue)
},
[onChangeCallback]
)
// Take the color from props if the last typed color (in local state) is not valid
const handleBlur = React.useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
if (!validHex(e.target.value)) setValue(escapeNonHex(hex))
onBlurCallback(e)
},
[hex, onBlurCallback]
)
// Update the local state when `color` property value is changed
React.useEffect(() => {
setValue(escapeNonHex(hex))
}, [hex])
return (
<input
{...rest}
value={value}
spellCheck="false" // the element should not be checked for spelling errors
onChange={handleChange}
onBlur={handleBlur}
/>
)
}
Here's the complete example with a demo → https://codesandbox.io/s/react-colorful-sketch-picker-ouz5t?file=/src/HexRgbaBox/HexInput.tsx
I'm trying to use the state hook to make the onChange work properly with material-ui to handle the error texts. But getting one issue. I have attached the screen of the error coming in the console.
Here's my code:
import React, { Component, useState } from 'react';
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';
export const CreateForm = (props) => {
const { containerClass } = props;
let [dataAction, setDataAction] = useState({ errorNameText: 'Enter product name!' });
let errorNameText = 'Enter the name!';
const handleNameChange = (event) => {
const name = event.target.value;
if(name && name !== '') {
setDataAction(prevState => {
prevState.errorNameText = '';
})
}
}
return(
<div className={containerClass}>
<div>
<TextField
className="product_nameField"
floatingLabelText="Product Name"
errorText={dataAction.errorNameText}
onChange={handleNameChange}
/>
</div>
<br/>
<div>
<TextField
className="product_priceField"
floatingLabelText="Product Price"
/>
</div>
<br/>
<div>
<TextField
className="product_infoField"
floatingLabelText="Product Info"
/>
</div>
<br/>
<div>
<RaisedButton label="Submit" primary={true} />
</div>
</div>
);
}
I guess I'm missing something vital over here on making use of the state hook in the proper way.
Any help would be appreciated.
You are setting your state in a wrong way. Two issues here:
setDataAction(prevState => {
prevState.errorNameText = '';
});
For an arrow function if you want to return an object implicitly, you need to wrap it with ()
You are mutating your state. Do not set it like prevState.errorNameText = ''
Here is the right way:
setDataAction(prevState => ({
...prevState,
errorNameText: '';
}));
So, you should spread the current state and make the necessary changes. In your case, you don't have any other properties but this is the right way. Because, if you add another property to your state in the future and do not use spread syntax here, you'll lose all other properties in your state.
change
const handleNameChange = (event) => {
const name = event.target.value;
if(name && name !== '') {
setDataAction(prevState => {
prevState.errorNameText = '';
})
}
}
to
const handleNameChange = (event) => {
const name = event.target.value;
if(name && name !== '') {
setDataAction({ errorNameText: ''}) <------ CHANGED
}
}
Please let me know if it solved it then I will elaborate.
The setDataAction will set the value of the state to the value passed or the returned value if you pass a function
You function just mutates the previous state and then returns nothing. So the next render the dataAction will be undefined and you will try to do dataAction.errorNameText and crash.
Use
// no point to use this syntax since the new state
// does not depend on the previous state
setDataAction(prevState => ({
errorNameText: '';
}))
or
// use this syntax for your use case
setDataAction({
errorNameText: '';
})
this is a continuation of:
How to use a handler function for two different purposes. SPFX (React/TypeScript)
which I've made a little progress from, so this isn't a duplicate, it's part 2 as it were.
My current code handler is:
const {value} = (evt.target as any);
const countKey = `${evt.target.name}Count`;
const obj = {
[evt.target.name]: value,
[countKey]: value.toString().length,
};
this.setState({
...this.state,
obj
}, () => { console.log(obj), console.log(countKey+'countKey'), console.log(value+'value');});
}
The render with the fields in question:
<td><TextField //THIS IS THE USER INPUT TEXTFIELD WHICH I WANT COUNTED BY THE TEXTFIELD BELOW!
name="EssCrit1"
value={this.state.EssCrit1}
onChange={this.handleChange}
multiline rows={2}
/>
</td>
<td ><TextField //THIS IS THE CHARACTER COUNTER FIELD!
name="EssCritChars1"
value={this.state.EssCrit1Count}
disabled={true}
/>
</td>
As you can see I'm logging obj and this is showing the correct textfield value typed in also it is counting how many characters. Problem is it doesn't allow text to be typed into the textfield. The value (EssCrit1) and the (EssCrit1Count) field seem to be registering the key press showing:
[object Object]: {EssCrit1: "h", EssCrit1Count: 1}
EssCrit1: "h"
EssCrit1Count: 1
__proto__: Object
in the console. But as mentioned it doesn't allow any text to be typed into the field. It seems as if the state is being overwritten as soon as something is typed in. Or something else which isn't apparent.
If it helps here is my state for the EssCrit1 textfield and it's accompanying character counter:
EssCrit1:null,
EssCrit1Count: null,
Went with:
public handleChange = (evt: any) => {
const {value} = (evt.target as any);
const countKey = `${evt.target.name}Count`;
const obj = {
[evt.target.name]: value,
[countKey]: value.toString().length,
};
this.setState(prevState => ({prevState, ...obj}));
}
and for the render:
<td><TextField
name="EssCrit1"
value={this.state.EssCrit1}
onChange={this.handleChange}
multiline rows={2}
/>
</td>
<td ><TextField
name="EssCrit1Count"
value={this.state.EssCrit1Count}
disabled={true}
/>
</td>
Matched up the text 'Count' at the end of the const CountKey variable assignment from the handler with the states that required it. The main issue was the updating of the states. I changed this to
this.setState(prevState => ({prevState, ...obj}));
from
this.setState(prevState => ({...prevState, newState });
which encompasses the changes detailed in the handler. Hope this helps someone. I'm going to reverse engineer what I have learnt from the magnificent help I've received. Thank you to everyone as always.
I use Material-UI in order to create a DateTime picker. Here is my demo code.
I added console.log to the function handleChange in order to see a current selected value. However, the value does not change when I use DatTime picker:
handleChange = name => event => {
const target = event.target;
const name = target.name;
console.log("plannedDep", this.state.plannedDep)
this.setState({
[name]: event.target.value
});
};
I added the state value as default value to plannedDep. value={this.state.plannedDep}
I changed the onChange this way: onChange={(event) => this.handleChange("plannedDep", event)}. Going by your code, onChange={this.handleChange("plannedDep")} the onchange you have will be fired as soon as the component is mounted and for every state/prop change resulting in unnecessary renders.
<TextField
id="datetime-local"
label="Next appointment"
type="datetime-local"
onChange={(event) => this.handleChange("plannedDep", event)}
value={this.state.plannedDep}
className={classes.textField}
InputLabelProps={{
shrink: true,
}}
/>
We have to check the value after setting the state, not before setting it. Im doing the check in the setState's callback, and it shows the updated value to me.
handleChange = (name, event) => {
const target = event.target; // Do we need this?(unused in the function scope)!
this.setState({
[name]: event.target.value
}, () => {
console.log(this.state.plannedDep)
// Prints the new value.
});
};
I hope this solves your problem :)
You override the name argument passed to the method handleChange inside that method, doing const name = target.name;, so when you set back the state at the end:
this.setState({
[name]: event.target.value
})
You are not setting the expected plannedDep.
You can fix it in two ways:
1) Set the state directly through:
this.setState({
plannedDep: event.target.value
})
2) Add a name attribute to your TextField so that target.name will be the value of the name attribute of your TextField which fired the event:
<TextField
id="datetime-local"
name="plannedDep" // see here!
label="Next appointment"
type="datetime-local"
onChange={this.handleChange("plannedDep")}
defaultValue="2017-05-24T10:30"
className={classes.textField}
InputLabelProps={{
shrink: true
}}
/>
Here the working demo
I believe the issue is your onChange handler. If you are passing a value into the onChange you must initialize it in a callback.
Try changing your onChange to onChange={() => this.handleChange("plannedDep")}