Why is useRef focus not working in React? - javascript

In React, I want that if something is typed into a form, another form is immediately set to focus.
For example:
<html>
<head>
<title>React App</title>
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone#6.15.0/babel.min.js"></script>
</head>
<body>
<div id="mydiv"></div>
<script type="text/babel">
//Now its basically like React
function Inputs() {
const [otp, setOtp] = React.useState([]); //Here are stored the Values of the numbers
const itemsRef = React.useRef([]); //Here are stored the refs of the numbers
const setOtpfunction = (e, i) => {
const key = parseInt(e.key); //I first parse the key that i got from onKeyPress to a number
if (key) {
let OTP = [...otp];
OTP[i] = key;
setOtp(OTP); //Then i update the numbers
if (i < 5) {
const nextForm = i + 1; //Here i say that the next item schould be the item with the index + 1
ref[i].focus(); //And here i set the focus. ref.current.focus() wouldnt work in this case
}
}
};
return (
<div className="space-x-2">
<input
placeholder="first input"
type="number"
className="otpNumber"
onChange={(e) => (e = null)} //that there is no error because there is no onChange function
required
onKeyPress={(e) => setOtpfunction(e, 0)}
ref={(ref) => (itemsRef[0] = ref)}
autoFocus
value={otp[0] || ""}
/>
<input
placeholder="second input"
type="number"
className="otpNumber"
onChange={(e) => (e = null)} //that there is no error because there is no onChange function
onKeyPress={(e) => setOtpfunction(e, 1)}
ref={(ref) => (itemsRef[1] = ref)}
value={otp[1] || ""}
/>
<input
placeholder="third input"
type="number"
className="otpNumber"
onChange={(e) => (e = null)} //that there is no error because there is no onChange function
onKeyPress={(e) => setOtpfunction(e, 2)}
ref={(ref) => (itemsRef[2] = ref)}
value={otp[2] || ""}
/>
<p>Now you can type in 1 number in the inputs and they are stored in UseState. But i want that if in the first input, one number is entered, it focuses the second input and so on</p>
</div>
);
}
class Render extends React.Component {
render() {
return (
<div>
<Inputs />
</div>
);
}
}
ReactDOM.render(<Render />, document.getElementById("mydiv"));
</script>
</body>
</html>
The console's error was only a warning that you should compile your scripts for production. That's why I hid it.
I also tested it with document.getElemntbyId, which also didn´t work.
I hope you can understand what I mean. Thanks for any answers that can be provided!

I would rather make a single Input component, that notifies the parent when the user types something and updates the state accordingly:
const OTP_LENGTH = 4;
export default function OTPForm() {
const [index, setIndex] = useState(0);
function done() {
if (index === OTP_LENGTH - 1) {
console.log("All inputs filled");
return;
}
setIndex(index + 1);
}
return (
<form>
{Array.from({ length: OTP_LENGTH }, (_, i) => (
<Input key={i} isCurrent={i === index} done={done} />
))}
</form>
);
}
function Input({ isCurrent, done }) {
const ref = useRef(null);
useEffect(() => {
if (isCurrent && ref.current) {
ref.current.focus();
}
}, [isCurrent]);
function onInput(event) {
if (event.target.value.length > 1) {
event.preventDefault();
return;
}
if (event.target.value.length === 1) {
done();
}
}
return <input ref={ref} onInput={onInput} type="number"/>;
}
Codesandbox
Up to you to expose proper onError / onSubmit handlers from the parent component.

Related

Why isn't my child component updating data when changing the state in React?

I have a list of users and I want to display in another component on the same page the user data in input fields for every user that I click on.
When no user is selected, I want the component to just render some text and a button to add a user. When the button is clicked the component renders the form with empty input fields so that we can add a new user.
I tried the following, but it's just showing the data for the first one I click on. It's not updating.
The main page:
const index = (props) => {
const [selectedUser, setSelectedUser] = useState(null);
const [state, setState] = useState("Index");
const onChange = (item) => {
setState("Edit");
setSelectedUser(item);
};
const onClick = (e, item) => {
if (e.type === "click" && e.clientX !== 0 && e.clientY !== 0) {
onChange(item);
} else {
console.log('prevented "onClick" on keypress');
}
};
const renderComponent = () => {
switch (state) {
case "Index":
return (
<>
<div className="btn" onClick={(e) => setState("Edit")}>
+ New Staff
</div>
<img src="/storage/illustrations/collaboration.svg" />
</>
);
case "Edit":
return (
<div>
<StaffForm profile={selectedUser} />
</div>
);
}
};
return (
<>
<div>
<div>
<h1>Staff</h1>
</div>
<div>
<div>
{profiles.map((item, index) => {
return (
<div key={index} onClick={(e) => onClick(e, item)}>
<input
type={"radio"}
name={"staff"}
checked={state === item}
onChange={(e) => onChange(item)}
/>
<span>{item.user.name}</span>
</div>
);
})}
</div>
<div>{renderComponent()}</div>
</div>
</div>
</>
);
};
The Staff Form Component:
const StaffForm = ({ profile }) => {
const { data, setData, post, processing, errors, reset } = useForm({
email: profile ? profile.user.email : "",
name: profile ? profile.user.name : "",
phone_number: profile ? profile.user.phone_number : "",
avatar: profile ? profile.user.avatar : "",
});
const [file, setFile] = useState(data.avatar);
const handleImageUpload = (e) => {
setFile(URL.createObjectURL(e.target.files[0]));
setData("avatar", e.target.files[0]);
};
const onHandleChange = (event) => {
setData(
event.target.name,
event.target.type === "checkbox"
? event.target.checked
: event.target.value
);
};
return (
<div>
<ImageUpload
name={data.name}
file={file}
handleImageUpload={handleImageUpload}
/>
<TextInput
type="text"
name="name"
value={data.name}
autoComplete="name"
isFocused={true}
onChange={onHandleChange}
placeholder={t("Name")}
required
/>
<TextInput
type="text"
name="phone_number"
value={data.phone_number}
autoComplete="phone_number"
placeholder={t("Phone Number")}
onChange={onHandleChange}
required
/>
<TextInput
type="email"
name="email"
value={data.email}
autoComplete="email"
onChange={onHandleChange}
placeholder={t("Email")}
required
/>
</div>
);
};
First of all something you should avoid is the renderComponent() call.Check here the first mistake mentioned in this video. This will most likely fix your problem but even if it doesn't the video explains why it should not be used.
Something else that caught my eye(possibly unrelated to your question but good to know) is the onChange function. When two pieces of state change together it is a potential source of problems, check out this article on when to use the useReducer hook.
Also be careful with naming React Components, they need to be capital case, this question contains appropriate answers explaining it.
(To only solve your problem stick to no.1 of this list, there are some improvements i'd do here overall for code quality and beauty, msg me for more details)

how to store value of 2 inputs dynamically generated in state at once

i am using react.i have 2 inputs that by clicking a button dynamically ganerats.
this the code:
useEffect(() => {
const myFields = fieldsArray.map((field) => {
return (
<div key={field} className="col-12 m-auto d-flex">
<input id={field} name={`question${field}`} onChange={handleChangeFields} placeholder='سوال' className="form-control col-4 m-2" />
<input id={field} name={`answer${field}`} onChange={handleChangeFields} placeholder='جواب' className="form-control col-4 m-2" />
</div>
)
})
setFields(myFields)
}, [fieldsArray])
i need to save value of 2 inputs together in an opjects like this :
[{question : '',answer : ''}]
and the new 2 input's value are going to update the above array like this:
[{question : '',answer : ''}, {question : '',answer : ''}]
i can save each input's value separately like this :
const handleChangeFields = (e) => {
const { name, value } = e.target
setFieldsValue([...fieldsValue, { [name]: value }])
}
but i want each 2 input's value together
i need to save each 2 inputs together.
how do i do that?
This is a general example of how I think you can tie in what you're currently doing, and add in as little new code as possible. This logs the new data state when the button is clicked.
Have two states. One for collecting the updated form information (form), and another for the combined form data array (data).
Have a form with a single onChange listener (event delegation) so you can catch events from all the inputs as they bubble up the DOM. That listener calls the handleChange function which updates the form state.
Have a button with an onClick listener that calls handleClick which takes the current form state and adds it to the data state.
I would be a bit wary of storing JSX in state. You should have that map in the component return and only updating the actual field data with basic information.
One final issue - your inputs cannot have the same id. ids must be unique. I'd get rid of them altogether.
const { useEffect, useState } = React;
function Example() {
// Initialise two states. `data` is an array
// and `form` is an object
const [ data, setData ] = useState([]);
const [ form, setForm ] = useState({});
// Add the form data to the `data` array
function handleClick() {
setData([ ...data, form ]);
}
// We're using event delegation so we need to
// check what element we changed/clicked on
// - in this case the INPUT element. We can then
// update the form state with the name/value pair of that input
function handleChange(e) {
const { nodeName, name, value } = e.target;
if (nodeName === 'INPUT') {
setForm({ ...form, [name]: value });
}
}
// Just logs the data state after a change
useEffect(() => console.log(data), [data]);
return (
<form onChange={handleChange}>
<input name="question" type="text" />
<input name="answer" type="text" />
<button
type="button"
onClick={handleClick}
>Update form
</button>
</form>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
this problem solved for me :
function App() {
const [results, setResults] = useState([{ question: "", answer: "" }])
// handle input change
const handleInputChange = (e, index) => {
const { name, value } = e.target
const list = [...results]
list[index][name] = value
setResults(list)
}
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...results]
list.splice(index, 1)
setResults(list)
}
// handle click event of the Add button
const handleAddClick = () => {
setResults([...results, { question: "", answer: "" }])
}
// handle submit to servers
const handlesubmit = () => {
// axios
console.log(results)
}
return (
<div className="App">
{results?.map((result, index) => {
return (
<div key={index}>
<input
name="question"
value={result.question}
onChange={(e) => handleInputChange(e, index)}
/>
<input
name="answer"
value={result.answer}
onChange={(e) => handleInputChange(e, index)}
/>
{results.length !== 1 && (
<button onClick={() => handleRemoveClick(index)}>
Remove
</button>
)}
{results.length - 1 === index && (
<button onClick={handleAddClick}>add</button>
)}
</div>
)
})}
<div style={{ marginTop: 20 }}>{JSON.stringify(results)}</div>
<button onClick={handlesubmit}>submit</button>
</div>
)
}
export default App

How to get the value of ckeckbox element from a form submission in React.js?

For some reason when I submit a form I can't get the value of the checked boxes I don't mean the check value but the value attribute of the HTML element itself.
This is my element in the parent component :
function parent(props){
const [filterCriteria, setFilterCriteria] = useState([]);
const [companies, setCompanies] = useState([]);
const [categories, setCategories] = useState([]);
useEffect(() => {
axios
.get("http://localhost:4000/api/companies")
.then((res) => {
companiesData = res.data.companies;
setCompanies([...new Set(companiesData.map((item) => item.company))]);
})
.finally(() => {
setIslLoading(false);
});
}, []);
useEffect(() => {
axios
.get("http://localhost:4000/api/categories")
.then((res) => {
categoriesData = res.data.categories;
setCategories([
...new Set(categoriesData.map((item) => item.category)),
]);
})
.finally(() => {
setIslLoading(false);
});
}, []);
const onFilteringHandler = (e) => {
e.preventDefault();
//fun fact the e.target is looped useing "for of" not "for in" although it's an
object
for (const element of e.target) {
if (element.type === "checkbox" &&
element.checked === true &&
companies.includes(element.value)) {
setFilterCriteria([...filterCriteria, element.value]);
}
if (element.type === "checkbox" &&
element.checked === true &&
categories.includes(element.value)) {
setFilterCriteria([...filterCriteria, element.value]);
}
}
};
return (
//this element is in the medal of tother elements but for simplification i
removed them
<FilterBox
categoriesCriteria={categories}
settingFilterCriteria={onFilteringHandler}
companiesCriteria={companies}
/>
)
}
This is my element in the child "FilterBox" component :
function FilterBox(props) {
// this is to map the categories into check boxes with values, keys and label
let categoriesFilters = props.categoriesCriteria.map((category, index) => {
return (
<label key={index}>
{category}
//this is the value i'm tring to get in the form submition
<input type="checkbox" value={category} />
</label>
);
});
// this is to map the categories into check boxes with values, keys and label
let companiesFilters = props.companiesCriteria.map((company, index) => {
return (
<label key={index}>
{company}
//this is the value i'm tring to get in the form submition
<input type="checkbox" value={company} />
</label>
);
});
return (
<form
onSubmit={props.settingFilterCriteria}
className="product_creation_form"
>
<div>
<h5 className="filter_title">Categories</h5>
{categoriesFilters}
</div>
<div>
<h5 className="filter_title">Companies</h5>
{companiesFilters}
</div>
<button type="submit" className="form_button">
Apply
</button>
<button type="reset" className="form_button">
Clear All
</button>
</form>
);
}
Nowhere is the issue, I can't get the value in this line:
setFilterCriteria([...filterCriteria, element.value]);
Although the element.value exist on the e.target?
How to do that?
FilterBox Component
function FilterBox(props) {
const categoriesFilters = props.categoriesCriteria.map((category, index) => {
return (
<label key={index}>
{category}
<input type="checkbox" value={category} name={category} />
</label>
);
});
const companiesFilters = props.companiesCriteria.map((company, index) => {
return (
<label key={index}>
{company}
<input type="checkbox" value={company} name={company} />
</label>
);
});
return (
<form
onSubmit={props.settingFilterCriteria}
className="product_creation_form"
>
<div>
<h5 className="filter_title">Categories</h5>
{categoriesFilters}
</div>
<div>
<h5 className="filter_title">Companies</h5>
{companiesFilters}
</div>
<button type="submit" className="form_button">
Apply
</button>
<button type="reset" className="form_button">
Clear All
</button>
</form>
);
}
App Component:
function App() {
const [companies] = React.useState(["companyOne", "companyTwo"]);
const [categories] = React.useState(["categoryOne", "categoryTwo"]);
const onFilteringHandler = (e) => {
e.preventDefault();
const filterCriteria = { companies: [], categories: [] };
for (const element of e.target) {
if (element.type === "checkbox" && element.checked) {
if (companies.includes(element.value)) {
filterCriteria.companies.push(element.name);
}
if (categories.includes(element.value)) {
filterCriteria.categories.push(element.name);
}
}
}
console.log(filterCriteria);
};
return (
<FilterBox
categoriesCriteria={categories}
settingFilterCriteria={onFilteringHandler}
companiesCriteria={companies}
/>
);
}
I took the liberty to disconnect the initial API loading of the companies & categories react state arrays and hardcoded them for simplicity. The below code should be enough to send meaningful data to be able to filter out on the backend.
I took the approach with a non-controlled form state, as your code seemed to favour this kind of approach. You can try and keep the filter values in the react state too but I believe this complicates the solution a bit (especially that state update is async) and it seems that you only need to collect the current filters state value on a button press. If however, you need it for some other purposes, then a controlled form state would most likely be needed.

input initial Value react js

I have questionand I hope you can help me to find right answer.
I have the following code.
import Slider from '#material-ui/core/Slider';
export default function RangeSlider({setValue1}) {
const location = useLocation();
const classes = useStyles();
const [value, setValue] = useState(['', 150000]);
const handleChange = (event, newValue) => {
setValue(newValue);
setValue1(value);
};
useEffect(() => {
setValue(['', 150000]);
setValue1(value);
}, [location.search]);
return (
<div className={`filter-slider-wrapper ${classes.root} `}>
<p>
<input
type="number"
className="slider-input"
onChange={({target}) => {
if (target.value.length > 7) {
return false;
}
setValue([+target.value, value[1]]);
setValue1(value);
}}
value={value[0]}
/>
</p>
<Slider value={value} onChange={handleChange} min={1} max={150000} />
<p>
<input
type="number"
className="slider-input"
onChange={({target}) => {
if (target.value.length > 7) {
return false;
}
setValue([value[0], +target.value]);
setValue1(value);
}}
value={value[1]}
/>
</p>
</div>
);
}
When I typeing something in input and after that I delleting form input everything was deleted, but after that "0" number was appeared and it was not deleteable, how can I avoid that case?
Thank you everybody.
I alredy resolved this problem.
I just deleted "+" symbol in setValue([+target.value, value[1]]);
Now the correct way to avoid "0" number to write without "+" symbol
Right way is: setValue([target.value, value[1]]);

React Controlled Form with Child /Parent component

I'm building a controlled form with dynamic fields.
The Parent component get data from a redux store and then set state with the values.
I don't want to make it with too much code lines so I turn the dynamic fields into a component.
States stay in the parent component and I use props to pass the handlechange function.
Parent :
function EditAbout(props) {
const [img, setImg] = useState("");
const [body, setBody] = useState(props.about.body);
const [instagram, setInstagram] = useState(props.about.links.instagram);
const [linkedin, setLinkedIn] = useState(props.about.links.linkedin);
const [press, setPress] = useState(props.about.press)
const handleSubmit = (e) => {
// Submit the change to redux
};
// set states with redux store
useEffect(() => {
setBody(props.about.body);
setInstagram(props.about.links.instagram);
setLinkedIn(props.about.links.linkedin);
setPress(props.about.press);
}, []);
const handleChangeChild = (e, index) => {
e.preventDefault();
let articles = press
const {value, name } = e.target
if (name === "title") {
articles[index].title = value;
} else {
articles[index].link = value;
}
setPress(articles)
console.log(articles[index])
}
return (
<Box>
<h1>CHANGE ABOUT ME</h1>
<Input
label="Image"
name="img"
type="file"
variant="outlined"
margin="normal"
onChange={(e) => setImg(e.target.files)}
/>
<Input
label="body"
value={body}
name="body"
onChange={(e) => setBody(e.target.value)}
variant="outlined"
multiline
rowsMax={12}
margin="normal"
/>
<Input
label="instagram"
value={instagram}
name="instagram"
variant="outlined"
margin="normal"
onChange={(e) => setInstagram(e.target.value)}
/>
<Input
label="Linkedin"
value={linkedin}
name="linkedin"
variant="outlined"
margin="normal"
onChange={(e) => setLinkedIn(e.target.value)}
/>
<Child press={press} onChange={handleChangeChild} />
{props.loading ? (
<CircularProgress color="black" />
) : (
<Button onClick={handleSubmit} variant="contained">
Send
</Button>
)}
</Box>
);
}
Child :
function Child(props) {
const { press, onChange } = props;
const inputsMarkup = () =>
press.map((article, index) => (
<div key={`press${index}`} style={{ display: "flex" }}>
<input
name="title"
value={press[index].title}
onChange={(e) => onChange(e, index)}
/>
<input
name="link"
value={press[index].link}
onChange={(e) => onChange(e, index)}
/>
<button>Delete</button>
</div>
));
return (
<div>
<h1>Press :</h1>
{inputsMarkup()}
</div>
);
}
Everything is fine when I'm typing in the Parent inputs. But when I'm using Child fields state update for one character but come back at its previous state right after.
It also doesn't display the character change. I can only see it in the console.
Thanks you in advance for your help
The problem is that you're mutating the state directly. When you create the articles variable (let articles = press) you don't actually create a copy and articles doesn't actually contain the value. It's only a reference to that value, which points to the object’s location in memory.
So when you update articles[index].title in your handleChangeChild function, you're actually changing the press state too. You might think that's fine, but without calling setPress() React will not be aware of the change. So, although the state value is changed, you won't see it because React won't re-render it.
You need to create a copy of the press array using .map() and create a copy of the updated array element. You can find the updated handleChangeChild() below:
const handleChangeChild = (e, index) => {
e.preventDefault();
const { value, name } = e.target;
setPress(
// .map() returns a new array
press.map((item, i) => {
// if the current item is not the one we need to update, just return it
if (i !== index) {
return item;
}
// create a new object by copying the item
const updatedItem = {
...item,
};
// we can safely update the properties now it won't affect the state
if (name === 'title') {
updatedItem.title = value;
} else {
updatedItem.link = value;
}
return updatedItem;
}),
);
};

Categories