React hook api state is sometimes empty - reactjs

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.

Related

how to allow decimal value in TextField and keep state value also number in React

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}
/>;

Invalid custom hook call

I'm just a react beginner. I'm trying to create a custom hook, which will be triggered once an onClick event is triggered. By what I see, I need to use the useRef hook, to take into account if the component is rendered by first time, or if it's being re-rendered.
My code approach is the next:
const Clear = (value) => {
const useClearHook = () => {
const stateRef = useRef(value.value.state);
console.log(stateRef);
useEffect(() => {
console.log("useEffect: ");
stateRef.current = value.value.state;
stateRef.current.result = [""];
stateRef.current.secondNumber = [""];
stateRef.current.mathOp = "";
console.log(stateRef.current);
value.value.setState({
...stateRef.current,
result: value.value.state.result,
secondNumber: value.value.state.secondNumber,
mathOp: value.value.state.mathOp,
});
}, [stateRef.current]);
console.log(value.value.state);
};
return <button onClick={useClearHook}>Clear</button>;
};
Any suggestion? Maybe I might not call ...stateRef.current in setState. I'm not sure about my mistake.
Any help will be appreciated.
Thanks!
Your problem is useClearHook is not a component (the component always goes with the first capitalized letter like UseClearHook), so that's why when you call useRef in a non-component, it will throw that error. Similarly, for useEffect, you need to put it under a proper component.
The way you're using state is also not correct, you need to call useState instead
Here is a possible fix for you
const Clear = (value) => {
const [clearState, setClearState] = useState()
const useClearHook = () => {
setClearState((prevState) => ({
...prevState,
result: [""],
secondNumber: [""],
mathOp: "",
}));
};
return <button onClick={useClearHook}>Clear</button>;
};
If your states on the upper component (outside of Clear). You can try this way too
const Clear = ({value, setValue}) => {
const useClearHook = () => {
setValue((prevState) => ({
...prevState,
result: [""],
secondNumber: [""],
mathOp: "",
}));
};
return <button onClick={useClearHook}>Clear</button>;
};
Here is how we pass it
<Clear value={value} setValue={setValue} />
The declaration for setValue and value can be like this in the upper component
const [value, setValue] = useState()

Unable to figure out why useState is not being called on chage

I am trying to set the search a brand from searchbar and then find it from a prop passed in this component, and for that i thought of using useState which is not being called any time when searbrand is changed. And also, why when i try to setState on onChange or onClick, it never changes immediately. How does it work??
const [searchBrand, setSearchBrand] = useState('');
const [searchBrandHolder, setSearchBrandHolder] = useState('');
const [searching, setSearching] = useState(false)
const brandSearchHandler = (e) => {
}
useState (() => {
if (searchBrandHolder === "") {
setSearching(false);
}
setSearching(true);
console.log("searching state = "+searching);
console.log("brand name searching = "+searchBrand);
}, [searchBrand])
console.log("brand to find = "+searchBrand);
return (
<div>
<div className="searchByBrand">
<input type="text" onChange={e => setSearchBrandHolder(e.target.value)} placeholder="Search by brand"></input>
<SearchIcon className="searchIcon" onClick={e => {setSearchBrand(searchBrandHolder); brandSearchHandler()}} />
what you need is useEffect readmore. Here is how your code should be structured:
useEffect (() => {
if (searchBrandHolder === "") {
setSearching(false);
}
setSearching(true);
console.log("searching state = "+searching);
console.log("brand name searching = "+searchBrand);
}, [searchBrandHolder])

React Hooks - useState value cannot change

Problem
I want to get the value of the updated hooks state whenever I clicked enter.
But I always get the Initial Value, instead of the updated one.
CODE
import React, { useState } from "react";
import ReactDOM from "react-dom";
import ContentEditable from "react-contenteditable";
const ItemCol = props => {
const [value, setValue] = useState("Initial Value");
const onChange = event => {
setValue(event.target.value);
console.log("onChange: " + value);
};
const keyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
//Value should be changed, but did not change
console.log("Enter Pressed: " + value);
}
};
return (
<ContentEditable
html={value}
onKeyDown={React.useCallback(keyDown)}
onChange={React.useCallback(onChange)}
/>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<ItemCol />, rootElement);
CodeSandBox.io: https://codesandbox.io/embed/pensive-worker-31l3r
Note: keyCode 13 is Enter.
Note 2: I'm using react-contenteditable dependencies. (https://www.npmjs.com/package/react-contenteditable)
Please do help me, as I have this problem for hours. Thanks !
The ContentEditable component seems to memoize the onKeydown method and hence whenever you invoke it, it prints the value from its initial closure which is why you have the initial value always.
To solve this closure issue, you could keep the value in a ref and mutate it
const ItemCol = props => {
const [value, setValue] = useState("Initial Value");
const valRef = useRef(value);
const onChange = event => {
setValue(event.target.value);
valRef.current = event.target.value;
};
const keyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
//Value should be changed, but did not change
console.log("Enter Pressed: " + valRef.current);
}
};
return (
<ContentEditable
html={value}
onKeyDown={keyDown}
onChange={React.useCallback(onChange)}
/>
);
};
Working demo
#Michael Harley,problem was with closure or with the way react-content-editable onkeydown function setup inside react-content-editable.
Working Demo:- https://codesandbox.io/embed/elated-bohr-jeu4u?fontsize=14
Resource :- https://overreacted.io/a-complete-guide-to-useeffect/

react hooks setTimeout after setState

I recently wanted to design an input component with react hooks.
The component would check validation after entering input in 0.5 second.
my code like
const inputField = ({ name, type, hint, inputValue, setInput }) => {
// if there is default value, set default value to state
const [value, setValue] = useState(inputValue);
// all of validation are true for testing
const validCheck = () => true;
let timeout;
const handleChange = e => {
clearTimeout(timeout);
const v = e.target.value;
setValue(v);
timeout = setTimeout(() => {
// if valid
if (validCheck()) {
// do something...
}
}, 500);
};
return (
<SCinputField>
<input type={type} onChange={handleChange} />
</SCinputField>
);
};
unfortunately, it's not worked, because the timeout variable would renew every time after setValue.
I found react-hooks provide some feature like useRef to store variable.
Should I use it or shouldn't use react-hooks in this case?
Update
add useEffect
const inputField = ({ name, type, hint, inputValue, setInput }) => {
// if there is default value, set default value to state
const [value, setValue] = useState(inputValue);
// all of validation are true for testing
const validCheck = () => true;
let timeout;
const handleChange = e => {
const v = e.target.value;
setValue(v);
};
// handle timeout
useEffect(() => {
let timeout;
if (inputValue !== value) {
timeout = setTimeout(() => {
const valid = validCheck(value);
console.log('fire after a moment');
setInput({
key: name,
valid,
value
});
}, 1000);
}
return () => {
clearTimeout(timeout);
};
});
return (
<SCinputField>
<input type={type} onChange={handleChange} />
</SCinputField>
);
};
It looks worked, but I am not sure about it's a right way to use.
Here's how I would do it:
import React, {useState, useEffect, useRef} from 'react';
function InputField() {
const [value, setValue] = useState(''); // STATE FOR THE INPUT VALUE
const timeoutRef = useRef(null); // REF TO KEEP TRACK OF THE TIMEOUT
function validate() { // VALIDATE FUNCTION
console.log('Validating after 500ms...');
}
useEffect(() => { // EFFECT TO RUN AFTER CHANGE IN VALUE
if (timeoutRef.current !== null) { // IF THERE'S A RUNNING TIMEOUT
clearTimeout(timeoutRef.current); // THEN, CANCEL IT
}
timeoutRef.current = setTimeout(()=> { // SET A TIMEOUT
timeoutRef.current = null; // RESET REF TO NULL WHEN IT RUNS
value !== '' ? validate() : null; // VALIDATE ANY NON-EMPTY VALUE
},500); // AFTER 500ms
},[value]); // RUN EFFECT AFTER CHANGE IN VALUE
return( // SIMPLE TEXT INPUT
<input type='text'
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
WORKING EXAMPLE ON SNIPPET BELOW:
function InputField() {
const [value, setValue] = React.useState('');
const timeoutRef = React.useRef(null);
function validate() {
console.log('Validating after 500ms...');
}
React.useEffect(() => {
if (timeoutRef.current !== null) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(()=> {
timeoutRef.current = null;
value !== '' ? validate() : null;
},500);
},[value]);
return(
<input type='text' value={value} onChange={(e) => setValue(e.target.value)}/>
);
}
ReactDOM.render(<InputField/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
You don't need to keep the reference to the timeout between renders. You can just return a function from the useEffect to clear it:
React.useEffect(() => {
const timeout = setTimeout(()=> {
if (value !== '') {
validate();
}
}, 500);
return () => {
clearTimeout(timeout); // this guarantees to run right before the next effect
}
},[value, validate]);
Also, don't forget to pass all the dependencies to the effect, including the validate function.
Ideally, you would pass the value as a parameter to the validate function: validate(value) - this way, the function has fewer dependencies, and could even be pure and moved outside the component.
Alternatively, if you have internal dependencies (like another setState or an onError callback from props), create the validate function with a useCallback() hook :
const validate = useCallback((value) => {
// do something with the `value` state
if ( /* value is NOT valid */ ) {
onError(); // call the props for an error
} else {
onValid();
}
}, [onError, onValid]); // and any other dependencies your function may use
This will keep the same function reference between the renders if the dependencies don't change.
You can move timeout variable inside handleChange method.
const inputField = ({ name, type, hint, inputValue, setInput }) => {
// if there is default value, set default value to state
const [value, setValue] = useState(inputValue);
// all of validation are true for testing
const validCheck = () => true;
const handleChange = e => {
let timeout;
clearTimeout(timeout);
const v = e.target.value;
setValue(v);
timeout = setTimeout(() => {
// if valid
if (validCheck()) {
// do something...
}
}, 500);
};
return (
<SCinputField>
<input type={type} onChange={handleChange} />
</SCinputField>
);
};

Resources