I have a handleChange function in typescript that I call within another function to send the value of changes in a text field to a mobx tree. However, when I set const { name } = event.currentTarget and later log it in the function, the name variable is coming back as 'currentTarget' instead of the name attribute I assign in in my renderHexTextField function, and the value is undefined.
I render a number of different text fields by calling the renderHexTextField function, which takes in two params. The first is the value of the
If it was working as intented, the name variable would equal the 'hoverFontColor' string from my return statement, which would then be passed into handleChange as a key for the css object, and value would manipulate the mobx state tree.
Any help is appreciated!
edit** I forgot to mention that the TextField component is a MaterialUI component
SOLUTION EDIT** -- My handleChange was bound to a debounce. I had to update my onChange component attribute so event.persist() ran before this.handleChange. Thank you Praveen and Chris!
return (
this.renderHexTextField(css.hoverFontColor, 'hoverFontColor')
)
private renderHexTextField(input: string, name: string) {
// name parameter used to specify which state in handleChange function
if (name === 'fontType' || this._throwHexErr(input) === 'True') {
// If hex format is correct, render normal text field
return (
<TextField
required={true}
id="standard-required"
margin="normal"
name={name}
placeholder={input}
onChange={this.handleChange}
/>
)
} else {
// else render error text field
return (
<TextField
error={true}
id="standard-error"
margin="normal"
name={name}
placeholder={input}
onChange={this.handleChange}
/>
)
}
}
private handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const { name, value } = event.currentTarget
const { store } = this.props
const currentBot = store.bots.current
if (currentBot) {
const id = currentBot._id
const css: any = toJS(currentBot.theme.css)
log('css obj >> ', css)
if (css) {
css[name] = value
log('handleChange >>> ', name, value, css)
currentBot.patchCSS(id, css)
}
} else {
log('No current bot in handleChange')
}
}
private _validateHex(hexcode: string, regex: any) {
// Regex Testing Function
log('validating hex')
return regex.test(hexcode)
}
private _throwHexErr(userInput: string) {
// Return True or Error depending on result of Regex Test
const regex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
if (this._validateHex(userInput, regex)) {
return 'True'
} else {
return 'Error'
}
}
I have had the same trouble recently, I have used React.FormEvent<HtmlInputElement>. That gives me event.currentTarget.name from the interface. Does that help?
So just to elaborate, try changing React.ChangeEvent<HTMLInputElement> to React.FormEvent<HtmlInputElement>.
I think you need to change
const { name, value } = event.currentTarget
to
const { name, value } = event.target
or
const name = event.target.name;
const value = event.target.value;
This should work fine
private handleChange = (event: any): void => {
const name = event.target.name;
const value = event.target.value;
const { store } = this.props
const currentBot = store.bots.current
if (currentBot) {
const id = currentBot._id
const css: any = toJS(currentBot.theme.css)
log('css obj >> ', css)
if (css) {
css[name] = value
log('handleChange >>> ', name, value, css)
currentBot.patchCSS(id, css)
}
} else {
log('No current bot in handleChange')
}
}
also, do
<TextField
error={true}
id="standard-error"
margin="normal"
name={name}
placeholder={input}
onChange={(event) => this.handleChange(event)}
/>
See my solution edit above. My handleChange function was bound to a debounce, so I had to include event.persist() in the onChange attribute.
use e.currentTarget.getAttribute('name')
example:
const handleClick = (e) => {
console.log(e.currentTarget.getAttribute('name'))
}
Related
I'm using React with Material UI and TypeScript. I want TextField to allow decimal value and at the same time keeping my state value as a number instead of string.
export default function BasicTextFields() {
const [value, setValue] = React.useState(0);
const handleChange = (event) => {
const newValue = parseFloat(event.target.value);
setValue(newValue);
};
React.useEffect(() => {
console.log("value", typeof value);
}, [value]);
const handleKeyPress = (event) => {
const pattern = /[0-9.]/;
let inputChar = String.fromCharCode(event.charCode);
if (!pattern.test(inputChar)) {
event.preventDefault();
}
};
return (
<TextField
value={value}
onChange={handleChange}
onKeyPress={handleKeyPress}
/>
);
}
I'm restricting the non numeric value with the use of handleKeyPress. Now I want my state value to remain number so I'm adding parseFloat, but parseFloat('5.') will resolve in 5 only so I'm not able to enter '.' in text-field at all.
If I remove parseFloat it will allow decimal value but my state will be set as a string.
One possible solution is to use onBlur and setting up the state again with number value. To me this doesn't look the best way so any other way to solve this issue?
Value from input field is always string so in my opinion you should just put type="number" in input, or pass it as props to TextField component, while leaving value state as string and convert it later in functions when needed.
I believe that would be best practice.
Or you can look at this code from youtuber web-dev-simplified: https://github.com/WebDevSimplified/react-calculator/blob/main/src/App.js
He implements something similar.
I would suggest to follow the next steps:
Remove onKeyPress event listener
Update your onChange event listener to store only float numbers in the state as string
Convert your string state to number by adding + sign, if you want to use the float version of it in your code
Here's the updated version of your code:
export default function BasicTextFields() {
const [val, setValue] = React.useState('');
const handleChange = ({ target: { value } }) => {
const onlyFloat = value.replace(/[^0-9.]/g, '').replace(/(\..*?)\..*/g, '$1');
setValue(onlyFloat);
};
React.useEffect(() => {
//will be string
console.log("string version", typeof val);
//will be float
console.log("float version", typeof +val);
}, [val]);
// to use it as a float number just add the plus before val
console.log(+val);
return (
<TextField
value={val}
onChange={handleChange}
/>
);
}
export const verify = (test: string): boolean => {
var res: boolean = true;
let result: string = test.substr(0, test.length - 1);
if (isNaN(test[test.length - 1]) && test[test.length - 1] !== ".") {
res = false;
} else {
if (isNaN(result)) {
res = false;
}
}
return res;
};
then use this function before updating the useState Hook
<TextField
value={value}
onChange={(event) => {
if (verify(event.target.value)) {
handleChange(event.target.value);
}
}}
onKeyPress={handleKeyPress}
/>;
I think this is easier to explain using a codesandbox link. This follows on from a previous question of mine, which may help provide more overall context. Currently, when interacting with the child elements (i.e. inputs), the state updates to {"values":{"0":{"Text1":"test"},"1":{"bool":true}}}. The issue is that if you interact with the other inputs within a Parent component, e.g. Text2 in the Parent component with id 0, it will overwrite the value already in the state, which makes it look like this {"values":{"0":{"Text2":"test"},"1":{"bool":true}}}. I want it to look like {"values":{"0":{"Text1":"test", "Text2":"test"},"1":{"bool":true}}}.
This is my try with your problem. I would like to have childIndex instead of number like you. It would be easier to work with other components later.
Here is my codesandbox
import { useEffect, useState } from "react"
import Parent from "./Parent"
const id1 = 0
const id2 = 1
interface Boo {
childIndex: number
value: {
[name: string]: string | boolean
}
}
const GrandParent: React.FC = () => {
const [values, setValues] = useState<Boo[]>([])
const valuesChange = (e: React.ChangeEvent<HTMLInputElement>, id: number) => {
console.log("change event")
const name = e.target.name
let value: any
if (name === "bool") {
value = e.target.checked
} else {
value = e.target.value
}
setValues((prev) => {
// Update new value to values state if input already there
let updateBoo = prev.find((boo) => boo.childIndex === id)
if (updateBoo) {
// Update Values
const valKeys = Object.keys(updateBoo.value)
const valIndex = valKeys.find((val) => val === name)
if (valIndex) {
updateBoo.value[valIndex] = value
} else {
updateBoo.value = { ...updateBoo.value, [name]: value }
}
} else {
// Create new if not added
updateBoo = {
childIndex: id,
value: { [name]: value }
}
}
return [...prev.filter((boo) => boo.childIndex !== id), updateBoo]
})
}
useEffect(() => {
console.log("Render", { values })
})
return (
<>
<div>{JSON.stringify({ values }, undefined, 4)}</div>
<br />
<br />
<Parent valueChange={(e) => valuesChange(e, id1)} key={id1} />
<Parent valueChange={(e) => valuesChange(e, id2)} key={id2} />
</>
)
}
export default GrandParent
The trick is you should return the previous state of the property too:
setValues((prev) => {
return {
...prev,
[id]: {
...(prev && (prev[id] as {})), // <= return the previous property state
[name]: value
}
}
})
I'm not very good at typescript but I tried my best to solve some types' errors
you can see an example below
Hi there i'm new to react i'm setting state and city using pincode when it has length of 6 digits when i put whole function inside useEffect it gives error to include setPersonalDetState & i also want to use same funtion to validate i cannot include it inside useEffect
const intialState = {
city: '',
state: '',
pincode: ''
};
const PersonalDetails = () => {
const [personalDetState, setPersonalDetState] = useState(intialState);
const { city, state, pincode } = personalDetState;
const fetchPincode = async (pincode) => {
if (pincode.length != 6) {
return;
}
let cityState = await axios.get(
`https://api.postalpincode.in/pincode/${pincode}`
);
const { Status, PostOffice } = cityState.data[0];
const { District, State } = PostOffice[0];
personalDetState['city'] = District;
personalDetState['state'] = State;
return setPersonalDetState({ ...personalDetState });
};
useEffect(() => {
fetchPincode(pincode);
}, [pincode]);
const handleChange = (event) => {
let { value, name } = event.target;
if (name === 'pincode') value = value.replace(/\D/g, '');
if (name === 'pincode' && value.length === 7) return;
setPersonalDetState({ ...personalDetState, [name]: value });
};
return (
<Fragment>
<input
type='text'
name='pincode'
value={pincode}
onChange={handleChange}
/>
<input type='text' name='city' value={city} disabled />
<input type='text' name='state' value={state} disabled />
</Fragment>
);
};
You can useCallback for fetchPincode in order to correctly specify it as a dependency to the effect. It will run whenever pincode changes, but since fetchPincode callback doesn't have dependencies, it won't trigger the effect.
const intialState = {
city: '',
state: '',
pincode: ''
};
const PersonalDetails = () => {
const [personalDetState, setPersonalDetState] = useState(intialState);
const { city, state, pincode } = personalDetState;
const fetchPincode = useCallback(() => {
if (pincode.length != 6) {
return;
}
axios.get(
`https://api.postalpincode.in/pincode/${pincode}`
).then(cityState => {
const { Status, PostOffice } = cityState.data[0];
const { District, State } = PostOffice[0];
setPersonalDetState(prev => ({
city: District,
state: State
}));
});
}, [pincode]);
useEffect(fetchPincode, [pincode]);
const handleChange = (event) => {
let { value, name } = event.target;
if (name === 'pincode') value = value.replace(/\D/g, '');
if (name === 'pincode' && value.length === 7) return;
setCredentials({ ...userCredentials, [name]: value });
};
return (
<Fragment>
<input
type='text'
name='pincode'
value={pincode}
onChange={handleChange}
/>
<input type='text' name='city' value={city} disabled />
<input type='text' name='state' value={state} disabled />
</Fragment>
);
};
I've rewritten the fetchPincode to use the old-fashion Promise syntax, because it must not return anything in order to be used as useEffect callback. I answered a similar question here.
Why useCallback? In order to keep the same function reference between re-renders. Reference to the function will change if its dependencies change (dependencies are specified as array right after the function, as second parameter to useCallback).
Why the same function reference? Because if included as a dependency to another hook (say useEffect), changing reference may cause the effect to re-run (in case of effects), which is probably not desired if that function reference changes too often.
In your particular case, pincode will change whenever you type (not 100% sure, but I assume setCredentials is doing that, I didn't see its declaration). This will cause the function reference to fetchPincode to change. If specified as a dependency to an effect, the effect would run if the function ref changes. But you also have pincode specified as dependency so effect would run on each key input anyway. But you might think about making the request after specified period of inactivity (user not typing), also known as debounce.
To learn about hooks:
Introducing hooks
Hooks FAQ
I am using hook api for managing state, the problem is that state is sometimes empty in handler fucntion.
I am using component for manage contenteditable (using from npm) lib. You can write to component and on enter you can send event to parent.
See my example:
import React, { useState } from "react"
import css from './text-area.scss'
import ContentEditable from 'react-contenteditable'
import { safeSanitaze } from './text-area-utils'
type Props = {
onSubmit: (value: string) => void,
}
const TextArea = ({ onSubmit }: Props) => {
const [isFocused, setFocused] = useState(false);
const [value, setValue] = useState('')
const handleChange = (event: React.FormEvent<HTMLDivElement>) => {
const newValue = event?.currentTarget?.textContent || '';
setValue(safeSanitaze(newValue))
}
const handleKeyPress = (event: React.KeyboardEvent<HTMLDivElement>) => {
// enter
const code = event.which || event.keyCode
if (code === 13) {
console.log(value) // THERE IS A PROBLEM, VALUE IS SOMETIMES EMPTY, BUT I AM SURE THAT TEXT IS THERE!!!
onSubmit(safeSanitaze(event.currentTarget.innerHTML))
setValue('')
}
}
const showPlaceHolder = !isFocused && value.length === 0
const cls = [css.textArea]
if (!isFocused) cls.push(css.notFocused)
console.log(value) // value is not empty
return (
<ContentEditable
html={showPlaceHolder ? 'Join the discussion…' : value}
onChange={handleChange}
className={cls.join(' ')}
onClick={() => setFocused(true)}
onBlur={() => setFocused(false)}
onKeyPress={handleKeyPress}
/>
)
}
export default React.memo(TextArea)
Main problem is that inside handleKeyPress (after enter keypress) is value (from state) empty string, why? - in block console.log(value) // THERE IS A PROBLEM, VALUE IS SOMETIMES EMPTY, BUT I AM SURE THAT TEXT IS THERE!!! I don't understand what is wrong??
The value is empty, because onChange doesn't actually change it, which means
const newValue = event?.currentTarget?.textContent || '';
this line doesn't do what it's supposed to. I think you should read the target prop in react's synthetic events instead of currentTarget. So, try this instead
const newValue = event.target?.value || '';
Hope this helps.
Can anyone help me im stuck at React Select Onchange by Index
<Select
name="taskTitle"
value={resultTitle[idx]}
onChange={this.handleChange(idx)}
options={optionResultTask}
/>
handleChange = idx => e => {
const { name, value } = e.target;
console.log(idx);
const task_results = [...this.state.task_results];
task_results[idx] = {
[name]: value
};
this.setState({
task_results
});
};
Codesandbox
My OnChange Function got error
Thanks.
Here's how I did it.
My select (rendered inside of a UI that can have N number of children, built by mapping over it):
{data.items.map((item, index) =>
<Select
options={dogFoodOptions}
isMulti={false}
name="dogfood"
id="dogfood"
value={item.value}
onChange={handleDogFoodChange(index)}
/>
}
The function:
const handleDogFoodChange = index => (option) => {
console.log(`index: ${index}, value: ${JSON.stringify(option)}`)
}
React Select onChange handler is called with the selected option as parameter (not an event), so change your code to something like this :
handleChange = idx => selected => {
console.log(idx, selected);
const { label, value } = selected;
// rest of your code
};