unable to remove first character from search in input react js - javascript

I have made a search field with debouncing. Everything works fine but when I try to empty the search field with backspace it continuously re-show all characters and does not remove them(the first character is always there).
you can see it in the attached gif
my parent component
class ParentComponent extends React.Component {
this.queryParam = {
keyword: ''
}
keywordSearch = value => {
const {
history: { push },
match: { url },
location: { search },
} = this.props;
queryParams = {...queryParams, keyword: value, };
push(`${url}?${queryString})
};
render() {
<SearchComponent
value={this.queryParams.keyword}
onUpdate={this.keywordSearch}
/>
}
}
my search field component
const SearchComponent = ({ value, onUpdate }) => {
const [fieldValue, setFieldValue] = useState(value);
const handleChange = ({ target: { value } }) => {
debounceFunc(() => {
onUpdate(value);
}, 300);
setFieldValue(value);
};
return (
<Input
value={fieldValue || value}
disableUnderline
onChange={handleChange}
className={classes.root}
placeholder='Search'
startAdornment={
<InputAdornment position="start">
<Search className={classes.icon} fontSize="small" />
</InputAdornment>
}
/>
}
here is my custom debounce component
export const debounceFunction = () => {
let timeOut = null;
return (callBack, wait) => {
if (timeOut) clearTimeout(timeOut);
timeOut = setTimeout(() => {
callBack();
}, wait);
};
};
export const debounceFunc = debounceFunction();
the problem is in this debounce function. can anyone help me in this regard? why it isn't removing the first character?
Thanks

First problem is <Input value={fieldValue || value}
=> use just the local state:
<Input value={fieldValue} to change the visible value immediatelly.
Second problem is this.queryParams.keyword being an instance property, not a React State
=> use this.state.... and this.setState(...) (or Hooks) to update debounced state in the parent

I have changed debounceFunction parameter and the way to use it. Can you give it a try
const SearchComponent = ({ value, onUpdate }) => {
const [fieldValue, setFieldValue] = useState(value);
const debouncedUpdate = debounceFunction(onUpdate, 300);
const handleChange = ({ target: { value } }) => {
debouncedUpdate(value);
setFieldValue(value);
};
return (
<Input
value={fieldValue || value}
disableUnderline
onChange={handleChange}
className={classes.root}
placeholder='Search'
startAdornment={
<InputAdornment position="start">
<Search className={classes.icon} fontSize="small" />
</InputAdornment>
}
/>
}
export const debounceFunction = (callBack, wait) => {
let timeOut = null;
return () => {
if (timeOut) clearTimeout(timeOut);
timeOut = setTimeout(() => {
callBack();
}, wait);
};
};
export const debounceFunc = debounceFunction();

Related

How to escape infinite loop of useState hook in React?

I am new to react and learning about hooks but I cannot escape from useState at second execution.
My caller function is calling another component called QueryPanel with parameter:
const [availables, setAvailable] = useState<string[]>([]);
useEffect(() => {
context.services.records.getAvailablesList()
.then((resp) => {
if(resp != undefined) {
setAvailables(resp);
}
});
}, []);
return <Panel1 text={availables}></Panel1>;
So Panel1 is opening with a string array or an empty array. What I want to do is that show dropdown and if the parameter is not empty then show first item as default item or if the parameter list is empty then show a string as a default value "No option available"
export const QueryPanel = (props: any) => {
const t = useTrans();
const context = useContext(AppContext);
const [form] = Form.useForm<Store>();
const [defaultValue, setDefaultValue] = useState("asd");
const { Option } = Select;
const onFinish = (values: Store) => {
const queryParams: Query = {
system: values.systemType,
startDate: values.startDate,
endDate: values.endDate,
startFrequency: values.startFrequency,
endFrequency: values.endFrequency
};
context.services.records.query(queryParams);
};
const validateOnFormChange = () => {
form.validateFields();
};
const onFinishFailed = (errorInfo: ValidateErrorEntity) => {
console.log('Failed:', errorInfo);
};
// useEffect(() => {
//
// if (props.text.length !== 0) {
// setDefaultValue(props.text[0]);
// }
// else {
// setDefaultValue("No option available");
// }
// }, []);
// if (props.text.length > 0) {
// setDefaultDbValue(props.text[0]);
// }
// else {
// let defaultDropDownOption:string = "No Db Available";
// setDefaultDbValue(defaultDropDownOption);
// }
My if-else is stuck in infinite loop because whenever I set default value to a value then it goes to same if statement again. How can I escape from it without using extra field ? useEffect is commented out here and the weird thing is when i check the parameter prop.text it is undefined at beginning and thats why the code is not going inside if-else in useEffect and then at second iteration the prop.text is coming as true.
I have tried something with useEffect hook but did not work:
useEffect(() => {
if (props.text.length > 0) {
setDefaultValue(props.text[0]);
}
}, [defaultValue]);
But this time default db is coming empty
Here is the complete code of Panel1 (QueryPanel) but the some names are different
import { Button, Col, DatePicker, Form, InputNumber, Row, Select } from 'antd';
import { Store } from 'antd/lib/form/interface';
import { ValidateErrorEntity } from 'rc-field-form/lib/interface';
import React, {useContext, useEffect, useState} from 'react';
import { AppContext } from '../../../../shared/Context';
import './QueryPanel.less';
import { Query } from '../../../../rest/BackendApi';
import { SystemType } from '../../../../shared/models/SystemType';
import { useTrans } from "../../../../shared/i18n/i18n";
export const QueryPanel = (props: any) => {
const t = useTrans();
const context = useContext(AppContext);
const [form] = Form.useForm<Store>();
const [defaultDbValue, setDefaultDbValue] = useState("Select Db");
const { Option } = Select;
const onFinish = (values: Store) => {
const queryParams: Query = {
system: values.systemType,
startDate: values.startDate,
endDate: values.endDate,
startFrequency: values.startFrequency,
endFrequency: values.endFrequency
};
context.services.records.query(queryParams);
};
const validateOnFormChange = () => {
form.validateFields();
};
const onFinishFailed = (errorInfo: ValidateErrorEntity) => {
console.log('Failed:', errorInfo);
};
// useEffect(() => {
// if (props.text.length !== 0) {
// setDefaultDbValue(props.text[0]);
// }
// else {
// setDefaultDbValue("No db available");
// }
// }, []);
useEffect(() => {
if (!props.text) return;
if (props.text.length > 0) {
setDefaultDbValue(props.text[0]);
}
else setDefaultDbValue("No Db Available");
}, [props.text?.length]);
return (
<div>
<Form form={form} onFinish={onFinish} onFinishFailed={onFinishFailed} onValuesChange={validateOnFormChange}>
<Row justify={"space-between"} >
<Col span={4}>
<Form.Item label={t.trans.queryPanel.systemType} name='systemType' className={"inline-form-item"}>
<Select defaultValue={defaultDbValue}>
{props.text.map((element: any) => (
<Option key={element} value={element}>
{element}
</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label={t.trans.queryPanel.startDate} name='startDate' className={"inline-form-item"}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || !getFieldValue('endDate') || getFieldValue('endDate') >= value) {
return Promise.resolve();
}
return Promise.reject(t.trans.queryPanel.errors.startDate);
},
}),
]}>
<DatePicker format="DD-MM-YYYY" />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label={t.trans.queryPanel.endDate} name='endDate' className={"inline-form-item"}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || !getFieldValue('startDate') || getFieldValue('startDate') <= value) {
return Promise.resolve();
}
return Promise.reject(t.trans.queryPanel.errors.endDate);
},
}),
]}>
<DatePicker format="DD-MM-YYYY" />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label={t.trans.queryPanel.startFrequency} name='startFrequency' className={"inline-form-item"}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value) return Promise.resolve();
if (value < 0 || value > 200) {
return Promise.reject(t.trans.queryPanel.errors.startFrequency);
} else if (getFieldValue('endFrequency') && getFieldValue('endFrequency') < value) {
return Promise.reject(t.trans.queryPanel.errors.startFrequencyValues);
}
return Promise.resolve();
},
}),
]}>
<InputNumber />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label={t.trans.queryPanel.endFrequency} name='endFrequency' className={"inline-form-item"}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value) return Promise.resolve();
if (value < 0 || value > 400) {
return Promise.reject(t.trans.queryPanel.errors.endFrequency);
} else if (getFieldValue('startFrequency') && getFieldValue('startFrequency') > value) {
return Promise.reject(t.trans.queryPanel.errors.endFrequencyValues);
}
return Promise.resolve();
},
}),
]}>
<InputNumber />
</Form.Item>
</Col>
<Col span={2}>
<Form.Item className={"inline-form-item"}>
<Button type="primary" htmlType="submit">
{t.trans.queryPanel.search}
</Button>
</Form.Item>
</Col>
</Row>
</Form>
</div>
);
}
The infinite loop is created because React will rerender your <Panel1 /> component whenever the state of the component changes.
In the body of your component you have this if ... else statement sitting without wrapping it in a hook:
if (props.text.length > 0) {
setDefaultDbValue(props.text[0]);
} else {
let defaultDropDownOption:string = "No Db Available";
setDefaultDbValue(defaultDropDownOption);
}
So, either way you will execute setDefaultDbValue() which will update the state and trigger a rerender.
To prevent this loop you can wrap this snipped with useEffect() and use your text.length as a dependency:
useEffect(() => {
if (!props.text) return; // wait until it is defined.
if (props.text.length > 0) setDefaultDbValue(props.text[0]);
else setDefaultDbValue("No Db Available");
}, [props.text?.length]);
There is no need to pass defaultValue as dependency to the useEffect hook. Or do I missunderstand your intention with this?
This aside: the better way to set default values is to do it in the useState(<DEFAULT_VALUE>) hook directly. Just the way you did it. Why is this not an option for you?
Edit
After your edit it is clear that the issue lies in how Ant uses the prop defaultValue. This prop is not reactive - so updating it after the Select has been rendered has no effect.
So you can either wait for the request to resolve before rendering your Panel, or:
If you want to select the first element of your text prop, once it is loaded, you have to set the value of the Select accordingly. Note that you have to update the value via onChange handler, if you bind the value to a state variable.
Here an example:
const Select = antd.Select;
const values = [
'one',
'two',
'three',
];
// This is just a dummy representing your `text` prop
const lazyValues = new Promise((res) => {
setTimeout(() => {
res(values);
}, 2000);
});
const App = () => {
const [text, setText] = React.useState([]);
const [value, setValue] = React.useState();
React.useEffect(() => {
// fake the request
lazyValues.then((values) => setText(values));
}, []);
// if `text` changes its value, set the Select to its first element
React.useEffect(() => {
if (text.length > 0 && !value) setValue(text[0]);
}, [text]);
return <div>
<Select onChange={setValue /* Need to update our value manually */} defaultValue={"Default value visible if there is no value"} value={value}>
{text.map((text, i) => <Select.Option key={i} value={text}>{text}</Select.Option>)}
</Select>
</div>
}
ReactDOM.render(<App />, document.getElementById('app'));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/antd#4.17.4/dist/antd.min.js"></script>
<link rel='stylesheet' type="text/css" href="https://unpkg.com/antd#4.17.4/dist/antd.min.css"></link>
<div id="app"/>
I believe that one way would be to do :
useEffect(() => {
if (props.text.length > 0 && props.text[0] !== defaultValue) {
setDefaultValue(props.text[0]);
}
}, [defaultValue]);
By doing so, you will update the defaultValue only when it differs from props.text[0]

Input focus is not working with specific component react

I have component PhoneConformition which uses input :
<Flexbox>
{CODE_INPUTS.map((input, index) => (
<Controller
key={input.name}
name={input.name}
control={control}
defaultValue=""
// rules={{ required: true }}
render={(props) => (
<InputCode
{...props}
ref={inputListRefs.current[index]}
className={styles.phoneConfirmation__formInput}
onValueSet={() => handleOnValueSet(index)}
/>
)} // props contains: onChange, onBlur and value
/>
))}
</Flexbox>
Where InputCode is =
type Props = {
className?: string;
name: string;
onChange: (...event: any[]) => void;
onValueSet: (value: string) => void;
};
const InputCode = forwardRef<HTMLInputElement, Props>(
({ className, name, onChange, onValueSet }, ref) => {
const classNames = [styles.inputCode, className].join(" ");
const [valueState, setValueState] = useState<string>("");
const handleChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
event.preventDefault();
};
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
const value: number = parseInt(event.key, 10);
if (!isNaN(value)) {
const valueString: string = value.toString();
setValueState(valueString);
onChange(valueString);
onValueSet(valueString);
}
};
return (
<div className={classNames}>
<input
ref={ref}
name={name}
className={styles.inputCode__input}
type="text"
onChange={handleChange}
onKeyPress={handleKeyPress}
value={valueState}
/>
</div>
);
}
);
export default InputCode;
BUT if i want to use the function on value set function where it focus on another function according to index.Everything is wokring instead of .focus
const handleOnValueSet = (index: number) => {
const formvalues = getValues();
onCodeChange(formvalues);
if (index + 1 < CODE_INPUTS.length) {
inputListRefs.current[index + 1].current?.focus();
}
}
.focus is not working with the onValueSet however with onChange or onKeyDown working correctly.
Found the solution. The issue was that the second onCodeChange call started before the first one finished.
I just added a setTimeOut of 10ms on the second call and it worked.
Thanks all

Should I use the Redux store to pass data from child to parent?

I am building a to-do/notes app in order to learn the basics of Redux, using React hooks and Typescript.
A note is composed of an ID and a value. The user can add, delete or edit a note.
The add / delete mechanics work fine. But the edit one is trickier for me, as I'm questionning how it should be implemented.
I think my reducer's code is fine. The problem lies between my component (Note.tsx) and its parent one (App.tsx).
When i'm logging the value, I can see that the new updated/edited value of the note is not sent to the reducer. As a result, my note is not edited with the new value.
I've tried "cloning" the redux store and making my changes here, but it seems tedious and unnatural to me. Should I just call the edit method from my Note.tsx component ?
Is there a clean / conventional way to do this ?
Here is my code :
App.tsx
function App() {
const notes = useSelector<NotesStates, NotesStates['notes']>(((state) => state.notes));
const dispatch = useDispatch();
const onAddNote = (note: string) => {
dispatch(addNote(note));
};
const onDeleteNote = (note: NoteType) => {
dispatch(deleteNote(note));
};
const onEditNote = (note: NoteType) => {
dispatch(updateNote(note));
};
return (
<div className="home">
<NewNoteInput addNote={onAddNote} />
<hr />
<ul className="notes">
{notes.map((note) => (
<Note
updateNote={() => onEditNote(note)}
deleteNote={() => onDeleteNote(note)}
note={note}
/>
))}
</ul>
</div>
);
}
Note.tsx
interface NoteProps {
deleteNote(): void
updateNote(noteValue: string | number): void
note: NoteType
}
const Note: React.FC<NoteProps> = ({ deleteNote, updateNote, note: { id, value } }) => {
const [isEditing, setIsEditing] = useState(false);
const [newNoteValue, setNewNoteValue] = useState(value);
const onDeleteNote = () => {
deleteNote();
};
const onUpdateNote = () => {
updateNote(newNoteValue);
setIsEditing(false);
};
const handleOnDoubleClick = () => {
setIsEditing(true);
};
const renderBody = () => {
if (!isEditing) {
return (
<>
{!value && <span className="empty-text">Note is empty</span>}
<span>{value}</span>
</>
);
}
return (
<input
value={newNoteValue}
onChange={(e) => setNewNoteValue(e.target.value)}
onBlur={onUpdateNote}
/>
);
};
return (
<li className="note" key={id}>
<span className="note__title">
Note n°
{id}
</span>
<div className="note__body" onDoubleClick={handleOnDoubleClick}>
{renderBody()}
</div>
<button type="button" onClick={onDeleteNote}>Delete</button>
</li>
);
};
export default Note;
and the notesReducer.tsx
export interface NotesStates {
notes: Note[]
}
export interface Note {
id: number
value: string
}
const initialState = {
notes: [],
};
let noteID = 0;
export const notesReducer = (state: NotesStates = initialState, action: NoteAction): NotesStates => {
switch (action.type) {
case 'ADD_NOTE': {
noteID += 1;
return {
...state,
notes: [...state.notes, {
id: noteID,
value: action.payload,
}],
};
}
case 'UPDATE_NOTE': {
return {
...state,
notes: state.notes.map((note) => {
if (note.id === action.payload.id) {
return {
...note,
value: action.payload.value,
};
}
return note;
}),
};
}
case 'DELETE_NOTE': {
return {
...state,
notes: [...state.notes
.filter((note) => note.id !== action.payload.id)],
};
}
default:
return state;
}
};
Thanks to #secan in the comments I made this work, plus some changes.
In App.tsx :
<Note
updateNote={onEditNote}
deleteNote={() => onDeleteNote(note)}
note={note}
/>
In Note.tsx :
interface NoteProps {
deleteNote(): void
updateNote(newNote: NoteType): void // updated the signature
note: NoteType
}
// Now passing entire object instead of just the value
const onUpdateNote = (newNote: NoteType) => {
updateNote(newNote);
setIsEditing(false);
};
const renderBody = () => {
if (!isEditing) {
return (
<>
{!value && <span className="empty-text">Note is empty</span>}
<span>{value}</span>
</>
);
}
return (
<input
value={newNoteValue}
onChange={(e) => setNewNoteValue(e.target.value)}
// modifying current note with updated value
onBlur={() => onUpdateNote({ id, value: newNoteValue })}
/>
);
};

update is not capturing and unable to update the input field

please find below code which contains name id and am rendering initially using map
am replacing id value to input type in UI
with the updated input type am trying to update the value onchange
update is not capturing and unable to update the input field
any suggestion?
please refer below snippet
import React, { useState } from "react";
const CstmInput = (props) => {
return (
<input
name={props.name}
type="text"
value={props.value}
onChange={(event) => props.onInputChange(event)}
/>
);
};
export default CstmInput;
import React, { useState } from "react";
import CstmInput from "./CstmInput";
const HierarcyTest = () => {
let rowData = [
{ name: "first", id: 10 },
{ name: "second", id: 20 },
];
const [data, setData] = useState(rowData);
const [name, setName] = useState({ fn: "test" });
const onInputChange = (e) => {
console.log("---event---", e.target.value);
setName({ ...name, fn: e.target.value });
};
let updateValue = () => {
let newData = data.map(
(item, index) =>
(item.id = (
<CstmInput name={item.name} value={item.id} onInputChange={(e) => onInputChange(e)} />
))
);
setData([...data, newData]);
};
return (
<div>
<div>Testing</div>
{data.map((val) => (
<h6>
{" "}
{val.name} {val.id}
</h6>
))}
<button onClick={updateValue}> Click </button>
</div>
);
};
export default HierarcyTest;
A few things why your code isn't working as intended:
1.
let updateValue = () => {
let newData = data.map((item, index) => {
if (item.id === 10) {
return [
(item.id = (
<CstmInput
value={item.id}
onInputChange={(e) => onInputChange(e)}
/>
)),
];
}
});
setData([...data, newData]);
};
In the above function inside the callback of map, you're only returning when a condition satisfies. Are you trying to filter the array instead? If not then return something when the if condition fails.
And why are you returning an array?
return [
(item.id = (
<CstmInput
value={item.id}
onInputChange={(e) => onInputChange(e)}
/>
)),
];
the above code seems logically wrong.
2.
const onInputChange = (e) => {
console.log("---event---", e.target.value);
setName({ ...name, fn: e.target.value });
};
If you want to update state which depends on the previous state then this is how you do it:
setName((prevState) => ({ ...prevState, fn: e.target.value }));
but since you're not actually relying on the properties of the previous state you can just use:
setName({fn: e.target.value });
Note that since your state only has one property and you want to update that single property you can completely overwrite the state, you don't need to spread the previous state.
update
change the updateValue function as the following:
let updateValue = () => {
setData(prevData => {
return prevData.map(el => {
return { ...el, id: <CstmInput value={el.id} onInputChange={(e) => onInputChange(e)} /> };
})
});
};
A stackblitz example I've created that implements what you're trying to do.

Trigger onChange event manually

I would like to call my onChange manually.
Here's my select with onChange (I pass onChangeSelect as a prop from parent component)
return(
<Form Control disabled={disabled}
<NativeSelect ref={ref} onChange={onChangeSelect}>
...
And I'd like to call that onChange every time my variable changes and is empty
useEffect(() => {
if (requiredSelect[0].length > 0 || id === "root1") { setDisabled(false) }
else { ref.current.value = ([[], []]); ref.current.onChange ; setDisabled(true); }
}, [requiredSelect])
And here's onChangeSelect in parent component
<Child onChangeSelect={(e) => rootChange(e)}>
and what it does
const rootChange = e => { setRootSelect(e.target.value.split(',')); }
The simplest solution here would be to change the definition of your rootChange function to accept the value instead of the event itself.
const rootChange = value => { setRootSelect(value.split(',')); }
// In parent:
<Child onChangeSelect={rootChange}>
// Select
<NativeSelect ref={ref} onChange={(e) => onChangeSelect(e.target.value)}>
You can trigger the function manually with:
onChangeSelect(whateverValueYouWant); // notice that you need the brackets when calling the function.
Answer in Typescript
//Child Component
type PropsType = {
onChange: (value: string) => void;
value: string;
};
const CustomInput: FC<PropsType> = (props: PropsType) => {
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
props.onChange(event.target.value);
};
return (<input
onChange={onChange}
type="text"
value={props.value}></input>);
};
//Parent Component
const [input, setInput] = React.useState('');
<CustomInput
onChange={(value: string) => {
setInput(value);
}}
value={input}></CustomInput>

Categories

Resources