When I press a key for inputting the value, the text field loses the focus and I again need to click on the input field.
import React, { useState } from "react";
import styled from "styled-components";
import Button from "../../UI/Button/Button";
import "./CourseInput.css";
const CourseInput = (props) => {
const [enteredValue, setEnteredValue] = useState("");
const [isValid, setIsValid] = useState(true);
Here is the code to handle input field changes
const goalInputChangeHandler = (event) => {
if (event.target.value.trim().length > 0) {
setIsValid(true);
}
setEnteredValue(event.target.value);
};
Here is the code to handle the form submission.
const formSubmitHandler = (event) => {
event.preventDefault();
if (enteredValue.trim().length === 0) {
setIsValid(false);
return;
}
props.onAddGoal(enteredValue);
setEnteredValue("");
};
const FormControl = styled.form`
// ....some CSS
`;
return (
<form onSubmit={formSubmitHandler}>
<FormControl className={!isValid && " invalid"}>
<label>Course Goal</label>
<input
value={enteredValue}
type="text"
onChange={goalInputChangeHandler}
/>
</FormControl>
<Button type="submit">Add Goal</Button>
</form>
);
};
export default CourseInput;
I've tried to recreate your issue locally and wasn't able to.
However, I believe the input loses focus as you are setting isValid to true, triggering a re-render and therefor input focus gets lost.
Instead of setting isValid in goalInputChangeHandler, leverage useEffect hook, which will allow you to perform side effects (e.g. when changing the value of the input, a side effect is to update the isValid value) like below:
useEffect(() => {
if (enteredValue.length > 0) {
setIsValid(true);
} else {
setIsValid(false);
}
}, [enteredValue]);
const goalInputChangeHandler = (event) => {
setEnteredValue(event.target.value);
};
This also will allow you to update your submit function to be as follows:
const formSubmitHandler = (event) => {
event.preventDefault();
props.onAddGoal(enteredValue);
};
As the useEffect will ensure isValid is correctly updated.
Related
I created an input field which am trying to validate
const [name, setName] = useState('');
const [formErrors, setFormErrors] = useState({});
<p>Name</p>
<input
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Error> {formErrors.name}</Error>
<Button
onClick={handleSubmit}
>
Submit
</Button>
OnClick of the submit button it checks if the name field is empty in the handleSubmit function
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = 'Name is required!';
}
return errors;
};
const handleSubmit = async (e) => {
const val = {name};
setFormErrors(validate(val));
if (Object.keys(formErrors).length === 0) {
console.log('No empty');
}else{
console.log('Empty');
}
};
The issue am having is that it lags behind in response. For example if the name field is empty it console's Not empty, on first click of the buttton, if I then click the button again it then console's the correct data which is 'Empty'.
This is becasue the state is not set until the component is being re-render therefore the formErrors state is {} until the handle function ends. Create a new constant to hold the errors and use these to console the outcome instead of the state itself if you still need to do something during the event, however use the state inside the JSX to render correctly since state will have been changed by then.
const handleSubmit = async (e)=>{
const val = {name};
const errors = validate(val)
setFormErrors(errors);
if (Object.keys(errors).length === 0) {
console.log('No empty');
}else{
console.log('Empty');
}
}
I am building a form where the hotel owners will add a hotel and select a few amenities of the same hotel. The problem is If I use state in the onChange function the checkbox tick is not displayed. I don't know where I made a mistake?
import React from "react";
import { nanoid } from "nanoid";
const ListAmenities = ({
amenities,
className,
setHotelAmenities,
hotelAmenities,
}) => {
const handleChange = (e) => {
const inputValue = e.target.dataset.amenitieName;
if (hotelAmenities.includes(inputValue) === true) {
const updatedAmenities = hotelAmenities.filter(
(amenitie) => amenitie !== inputValue
);
setHotelAmenities(updatedAmenities);
} else {
//If I remove this second setState then everything works perfectly.
setHotelAmenities((prevAmenities) => {
return [...prevAmenities, inputValue];
});
}
};
return amenities.map((item) => {
return (
<div className={className} key={nanoid()}>
<input
onChange={handleChange}
className="mr-2"
type="checkbox"
name={item}
id={item}
data-amenitie-name={item}
/>
<label htmlFor={item}>{item}</label>
</div>
);
});
};
export default ListAmenities;
The problem is that you are using key={nanoid()}. Instead, using key={item] should solve your probem.
I believe your application that uses ListAmenities is something like this:
const App = () => {
const [hotelAmenities, setHotelAmenities] = useState([]);
return (
<ListAmenities
amenities={["A", "B", "C"]}
className="test"
setHotelAmenities={setHotelAmenities}
hotelAmenities={hotelAmenities}
/>
);
};
In your current implementation, when handleChange calls setHotelAmenities it changed hotelAmenities which is a prop of ListAmenities and causes the ListAmenities to rerender. Since you use key={nanoid()} react assumes that a new item has been added and the old one has been removed. So it re-renders the checkbox. Since there is no default value of checkbox, it is assumed that it is in unchecked state when it is re-rendered.
I am working on a component where the user searches a term and it is returned to them through a filter. I am using useContext hook to pass data from db via axios. I would like to use the button in the CompSearch component to render the results rather than having them show up on a keystroke. My question is how do I render the results via button click?
Here is the code
Follow these steps to achieve that.
Change the input element into an uncontrolled component.
Get the value using reference in React.
import React, { useContext, useRef, useState } from "react";
import CompanyInfoList from "./CompanyInfoList";
import { CompListContext } from "./CompListContext";
const CompSerach = () => {
const [company, setCompany] = useContext(CompListContext);
const [searchField, setSearchField] = useState("");
const [searchShow, setSearchShow] = useState(false);
const filtered = company.filter((res) => {
return res.company.toLowerCase().includes(searchField.toLowerCase());
});
const inputRef = useRef(null); // 1. Create the ref
const handleClick = () => {
const val = inputRef.current.value; // 3. Get the value
setSearchField(val);
if (val === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
};
const searchList = () => {
if (searchShow) {
return <CompanyInfoList filtered={filtered} />;
}
};
return (
<>
<div>
<em>
NOTE: if you search "ABC" or "EFGH" you will see results - my goal is
to see those results after clicking button
</em>
<br />
<input
type="search"
placeholder="search Company Title"
ref={inputRef} {/* 2. Assign the ref to the Input */}
/>
<button onClick={handleClick}>Enter</button>
</div>
{searchList()}
</>
);
};
export default CompSerach;
https://codesandbox.io/s/show-on-button-click-68157003-rot6o?file=/src/TestTwo/CompSearch.js
Let me know if you need further support.
const handleChange = (e) => {
setSearchField(e.target.value);
if (e.target.value === "") {
setSearchShow(false);
} else {
setSearchShow(true);
}
**setCompany(e.target.value);**
};
i think your question is similar with autocomplete.
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.
I want to change state value with change in the 'select' element. So I am calling the setFilter method in the onChange handler. But state is not getting updated. It's holding the previous value.
How to fix this issue?
I want to change state value with change in the 'select' element. So I am calling the setFilter method in the onChange handler. But state is not getting updated. It's holding the previous value.
How to fix this issue?
import React, { useState, useEffect } from 'react'
import { useHistory } from 'react-router-dom'
import { useApolloClient} from 'react-apollo';
import { Formik, Form, ErrorMessage } from 'formik';
import * as Yup from "yup";
import AsyncPaginate from 'react-select-async-paginate'
import { GET_ITEM_CODES } from '../../library/Query';
export default function SampleForm({initialData}){
const history = useHistory();
const [productFilter, setProductFilter] = useState('');
const client = useApolloClient();
const defaultAdditional = {
cursor : null
}
const shouldLoadMore = (scrollHeight, clientHeight, scrollTop) => {
const bottomBorder = (scrollHeight - clientHeight) / 2
return bottomBorder < scrollTop
}
const loadItemcodeOptions = async (q = 0, prevOptions, {cursor}) => {
console.log('qu',q*1)
const options = [];
console.log('load')
const response = await client.query({
query:GET_ITEM_CODES,
variables : {filter: {
number_gte : q*1
},skip:0, first:4, after: cursor}
})
console.log('res',response)
response.data.itemCodes.itemCodes.map(item => {
return options.push({
value: item.number,
label: `${item.number} ${item.description}`
})
})
console.log('0',options)
return {
options,
hasMore: response.data.itemCodes.hasMore,
additional: {
cursor: response.data.itemCodes.cursor.toString()
}
}
}
const handleFilter = (e) => {
console.log('e',e)
setProductFilter(e.value)
console.log('pf',productFilter) // output is previous State(wrong)
}
useEffect(() => {
console.log('epf',productFilter) // output the current state(expected)
})
return(
<Formik
initialValues = {{
itemCode: !!initialData ? {value: initialData.itemCode, label: initialData.itemCode} : '',
}}
validationSchema = {Yup.object().shape({
itemCode: Yup.number().required('Required'),
})}
>
{({values, isSubmitting, setFieldValue, touched, errors }) => (
<Form>
<label htmlFor="itemCode">Item Code</label>
<AsyncPaginate
name="itemCode"
defaultOptions
debounceTimeout={300}
cacheOptions
additional={defaultAdditional}
value={values.itemCode}
loadOptions={loadItemcodeOptions}
onChange={option => {
handleFilter(option)
setFieldValue('itemCode', option)
}}
shouldLoadMore={shouldLoadMore}
/>
<ErrorMessage name="itemcode"/>
<pre>{JSON.stringify(values, null, 2)}</pre>
</Form>
)}
</Formik>
)
}
Actually setProductFilter is setting state asynchronously, so you'll get updated state in the effect, not right after calling setState. But your effect is going to run every time when your component gets re-rendered so you should add productFilter as a dependency of useEffect.
One other thing I want to mention is, I don't know about your use case but you should stick to the rule: Single source of truth. You have two states for productFilter, one is in Formik, i.e. itemCode, and other in your local state. I think you can remove your local state and use item code from formikProps.values.itemCode.