How to Change pre filled input values - javascript

I new to React. I have two input fields which are filled when the pages loads via axios but when I want to change the Values I am unable to change them even I can not type anything on them.
function MainView() {
const [InputFields, setInputFields] = useState({
name: "",
fullURL: "",
});
const changeHandler = (e) => {
setInputFields({
...InputFields,
[e.target.name]: e.target.value,
});
};
const [links, setLinks] = useState([]);
const getLinks = () => {
axios.get('../sanctum/csrf-cookie').then(response => {
axios.get("/userlinks/getdata").then((res) => {
console.log(res);
setLinks(res.data);
}).catch((err) => {
console.log(err);
});
});
};
useEffect(() => {
getLinks();
}, []);
return (<div>
{links.map((link) => {
return (<div className="card" key={link.id}>
<form>
<input className="form-control" value={link.name} type="text" name="name" placeholder="name" onChange={changeHandler} />
<input className="form-control" value={link.fullURL} type="text" name="fullURL" placeholder="fullURL" onChange={changeHandler} />
</form>
</div>);
})}
</div>
);
}
export default MainView;

This happens because in your onChange method, you are changing InputFields variable, where as your getLinks method changes links variable, which is being rendered on the screen.
If you want to set an initial value, and then allow the user to change it, change your input to :
<input className="form-control" defaultValue={link.name}
value={InputFields.name} type="text" name="name" placeholder="name" onChange={changeHandler} />
Likewise change for your other input, if you do not want the user to change the value later on, it's often better to add disable in the input to avoid confusing people. 🙂
I know that this has been done so that you can create a minimal reproducible example for us, but I would have directly called setInputFields in the axios.get section to avoid this problem in the first place, however, if not possible, use the defaultValue and value as I've shown above.

Related

how to handle these multiple forms?

the requirement is a bit tricky. dunno how to explain it but I'll try.
so I've 20 forms on my web page. and there's only one submit button that'll bulk-submit all the forms. but filling out all forms isn't required. the user will fill up as many forms as they like. but whichever form they fill up, must be fully filled. means all inputs are required on each form.
so I need to write a logic that'll make all the inputs required in a form if any of the input is filled there. I know I can use the onChange function and write a logic to make all the input required in one form. but I also need to remove the required from the inputs, if all the input field is again cleared. and I think removing the required from the inputs is the main complexity here. Because while adding required i can simply check if any of the inputs have value in it (using onChange on every input). but if I do the same for removing required, I can't be assured if one input field is cleared or all the inputs are cleared from that form. so in simpler words, I need to sync all the inputs on each form.
[NOTE-1: I'm in a React environment, so I've all the facilities of states and stuff]
[NOTE-2: I've made a react component for the form and looped over it 20 times. so you can think that as one form]
[NOTE-3: This is one of my client's projects. so I can't have changes to the requirements]
This is a pretty "business specific" problem, but I would tackle it along these lines. You may need to make adjustments to fit your exact requirements, but the general gist is there.
The key is to treat the "required" flag for each input as "derived" or calculated state. You said "but I also need to remove the required from the inputs" - I don't think that's entirely true, or doesn't fit the react model. You just need to check if other fields are populated in the current form in the current render.
const { useState } = React;
const { render } = ReactDOM;
const forms = [
{
inputs: ["field1", "field2"]
},
{
inputs: ["field3", "field4"]
}
];
function MegaForm() {
const [values, setValues] = useState(() => {
const values = {};
forms.forEach((form) => {
form.inputs.forEach((input) => {
values[input] = "";
});
});
return values;
});
const submit = () => {
console.log(values);
};
const isRequired = (formIndex) => {
return forms[formIndex].inputs.find(
(inputName) => values[inputName] !== ""
);
};
return (
<div>
{forms.map((form, i) => (
<form key={i}>
<h2>Form {i}</h2>
{form.inputs.map((input, j) => (
<div key={j}>
<label>
{input}
<input
value={values[input]}
onChange={(e) =>
setValues({ ...values, [input]: e.target.value })
}
required={isRequired(i)}
/>
{isRequired(i) ? "*" : ""}
</label>
</div>
))}
</form>
))}
<br />
<br />
<button type="button" onClick={submit}>
Submit
</button>
</div>
);
}
render(<MegaForm />, document.getElementById("app"));
CodePen: https://codepen.io/chrisk7777/pen/RwYWWqV?editors=0010
If you have all the forms with the same fields you could go with a solution like this:
export function FormsContainer() {
const [formData, setFormData] = React.useState({});
function onChangeGenerator(i: number) {
return (e) => {
setFormData((data) => ({
...data,
[i]: {
...data[i],
[e.target.name]: e.target.value,
},
}));
};
}
function fieldHasValue(value) {
return value !== null && value !== undefined && value !== '';
}
function formHasValidFields(i) {
return (
formData[i] &&
Object.keys(formData[i]).some((key) => fieldHasValue(formData[i][key]))
);
}
function submit() {
const result = Object.keys(formData).reduce((acc, i) => {
if (formHasValidFields(i)) {
acc.push(formData[i]);
}
return acc;
}, []);
console.log(result);
}
return (
<form
onSubmit={(e) => {
e.preventDefault();
submit();
}}
>
{[0, 1, 2, 3, 4, 5].map((i) => (
<SingleForm
key={i}
onChange={onChangeGenerator(i)}
required={formHasValidFields(i)}
/>
))}
<br />
<br />
<button type="submit">Submit</button>
</form>
);
}
function SingleForm({
required,
onChange,
}: {
required: boolean;
onChange: (e) => void;
}) {
return (
<React.Fragment>
<hr />
<input name="prop1" onChange={onChange} required={required} />
<input name="prop2" onChange={onChange} required={required} />
</React.Fragment>
);
}
StackBlitz: https://stackblitz.com/edit/react-ts-utrgbj

Clear sub-form values on conditional hide of nested fields

Using React+Formik, I want to create a reusable component that we can use to conditionally show/hide nested subforms (of any complexity).
Every time it becomes hidden, we wish to clear the values so that those values don't get submitted.
Below, a simple hide/show component called OptionalFeature is shown.
const OptionalFeature = ({
toggle,
children
}) => {
if (toggle) {
return <div>{children}</div>
} else {
return null;
}
}
It can be tested by pasting into https://codesandbox.io/s/zkrk5yldz
But as you can see in the demo, making the children invisible does not clear their values. Ideally each child can define it's own clearValue behavior (the example is very simple, we want to have more complex nested forms).
What's the clean solution to clear the fullname field by extending OptionalFeature class in a generic, reusable way?
I already tried creating a cleanup function and calling it from OptionalFeature inside the if-block, but it does not seem very idiomatic.
// Helper styles for demo
import "./helper.css";
import { DisplayFormikState } from "./helper";
import React from "react";
import { render } from "react-dom";
import { Formik } from "formik";
// Generic reusable component to show/hide sub-forms
const OptionalFeature = ({
toggle,
children
}) => {
if (toggle) {
return <div>{children}</div>
} else {
return null;
}
}
const App = () => (
<div className="app">
<Formik
initialValues={{ email: "", anonymous: false, fullname:"" }}
onSubmit={async values => {
await new Promise(resolve => setTimeout(resolve, 500));
alert(JSON.stringify(values, null, 2));
}}
>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleSubmit
} = props;
return (
<form onSubmit={handleSubmit}>
<input
id="email"
placeholder="Enter your email"
type="text"
value={values.email}
onChange={handleChange}
/>
{/* This checkbox should show/hide next field */}
<div style={{ display: "white-space:nowrap" }}>
<label htmlFor="anonymous" style={{ display: "inline-block"}}>Anonymous</label>
<input
id="anonymous"
type="checkbox"
name="anonymous"
value={values.anonymous}
onChange={handleChange}
style={{ display: "inline-block", width: "20%"}}
/>
</div>
<OptionalFeature
toggle={!values.anonymous}
>
{/* Imagine this subform comes from a different file */}
<input
id="fullname"
placeholder="Enter your full name"
type="text"
value={values.fullname}
onChange={handleChange}
/>
</OptionalFeature>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
<DisplayFormikState {...props} />
</form>
);
}}
</Formik>
</div>
);
render(<App />, document.getElementById("root"));
Here is my existing approach, waiting for a better answer:
const OptionalFeature = ({
toggle,
onHide,
children
}) => {
if (toggle) {
return <div>{children}</div>
} else {
// useEffect needed because onHide function could trigger anything.
// Also to avoid calling multiple times.
useEffect(() => {
onHide();
}, [toggle, onHide])
return null;
}
}
Then later invoke a cleanup function onHide:
<Formik
initialValues={{ email: "", anonymous: false, fullname:"" }}
...>
{props => {
const {
values,
isSubmitting,
handleChange,
handleSubmit
} = props;
// to clean up all prop values inside the OptionalFeature
const clearFullName = () =>
{
values.fullname = ""
}
return (
//...
<OptionalFeature
toggle={!values.anonymous}
onHide={clearFullName} // using cleanup Function
>
<input
id="fullname"
placeholder="Enter your full name"
type="text"
value={values.fullname}
onChange={handleChange}
/>
</OptionalFeature>
);
}}
</Formik>
What I don't like here is that as the for becomes more complex with more OptionalFeatures or more elements nested inside the optional feature, it becomes quite hard to check whether all fields inside the nested optional form are being cleaned up or not. Also the properties of useEffect seem hard to test.
I would prefer some kind of nested subform such that I could write something like onHide={handleReset}, and this would be scoped only to fields inside the nested subform, without me having to define a custom handleReset function for that.

React useState: Toggle boolean attribute inside object - how to identify onClick source?

This may be a simple question and I'm just missing something, but I haven't found an answer yet.
I would like to use useState with an object storing both onChange and onClick events.
With onChange, the attributes of event.target.name and event.target.value are provided from the virtual DOM. That makes it easy to identify which parameter the event should be targeting.
However, with onClick there is no attribution of the element from either of these event.target parameters.
How can one identify which tag is generating an onClick event in order to tell the handler which parameter it should affect in an object of several boolean parameters?
Here's an example I created in an effort to be both as simple and complete as possible:
import React from 'react';
const initialState = {
switches: {
menuOpen: false,
modalOpen: false,
},
fields: {
username: '',
password: '',
},
};
export default function Header(props) {
const [changeStateObj, setChangeStateObj] = useState(initialState.fields);
const [toggleStateObj, setToggleStateObj] = useState(initialState.switches);
useEffect(() => {
console.log(changeStateObj, toggleStateObj);
}, [changeStateObj, toggleStateObj]);
const changeHandler = (event) => {
let name = event.target.name; // these provide values
let value = event.target.value;
setChangeStateObj((values) => ({ ...values, [name]: value })); // and therefore, this works
};
const clickHandler = (event) => {
let name = event.target.name // nothing here
setToggleStateObj((prevState) => ({ [name]: !prevState.name })); // no dice
};
return (
<div>
<label htmlFor="username">
User Name
<input type="text" name="username" onChange={changeHandler} />
</label>
<label htmlFor="password">
Password
<input type="password" name="password" onChange={changeHandler} />
</label>
<button name="modalOpen" onClick={clickHandler}>
Open modal
</button>
<button name="menuOpen" onClick={clickHandler}>
Open menu
</button>
</div>
);
}
const handleClickModalOpen = () => clickHandler({target: {name : 'modalOpen'}});
<button name="modalOpen" onClick={handleClickModalOpen}>
Open modal
</button>
Second approach, with your existing code, try use event.currentTarget.name rather than event.target.name. The currentTarget should tell you which exact element you've attached the event handler.

Components re-render whenever I type something but I want re-render only after onClick of button

Currently, I'm working on a REACT application that takes two names, gives a percent, and then displays a poem based on that percent. The first submit gathers information and shows what is expected (the percent and poem). After that though, once I start typing in the input fields, LovePercentContent and LovePoem keep re-rendering. Note: the poem being rendered is picked randomly from an array. So the biggest issue is the poem keeps updating as you type into the input fields. I'm still pretty new to React and read through the handle events documentation. But I still seem to be having issues.
https://reactjs.org/docs/handling-events.html
Below is what I've tried so far.
<button type="submit" className="btn" onClick={() => handleSubmit(e)}>
<button type="submit" className="btn" onClick={() => handleSubmit}>
Live version of the App
https://love-poem-calculator.netlify.app/
import React, { useState } from "react";
import LovePercentContent from "../LovePercentContent/LovePercentContent";
import LovePoem from "../LovePoem/LovePoem";
const LovePercent = () => {
const [yourName, setYourName] = useState("");
const [yourCrushName, setYourCrushName] = useState("");
const [yourLovePercent, setYourLovePercent] = useState("");
const [yourLovePoems, setYourLovePoems] = useState("");
const [yourSwitch, setYourSwitch] = useState(false);
const [yourNameMissing, setYourNameMissing] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (yourName !== "" && yourCrushName !== "") {
let calcQueryURL =
"https://love-calculator.p.rapidapi.com/getPercentage?fname=" +
yourName +
"&sname=" +
yourCrushName;
console.log("yourName2", yourName);
getLovePercent(calcQueryURL);
setYourName("");
setYourCrushName("");
} else {
console.log("you most enter two names");
setYourNameMissing("please enter both names");
}
};
const getLovePercent = (url) => {
fetch(url, {
method: "GET",
headers: {
"x-rapidapi-key": process.env.REACT_APP_PERCENT_API_KEY,
"x-rapidapi-host": "love-calculator.p.rapidapi.com",
},
})
.then((response) => response.json())
.then((data) => {
poemLines(data);
return setYourLovePercent(data);
})
.catch((err) => {
console.error(err);
});
};
function poemLines(data) {
let percent = data.percentage;
let url = `https://poetrydb.org/linecount/${percent}`;
fetch(url, {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
return setYourLovePoems(data);
})
.then(() => {
return setYourSwitch(true);
})
.catch((err) => {
console.error(err);
});
}
if (yourSwitch) {
return (
<>
<h2>Enter Two Names:</h2>
<input
name="yourName"
value={yourName}
placeholder="Your Name"
onChange={(e) => setYourName(e.target.value)}
/>
<input
name="yourCrushName"
value={yourCrushName}
placeholder="Your Crush's Name"
onChange={(e) => setYourCrushName(e.target.value)}
/>
<button type="submit" className="btn" onClick={handleSubmit}>
Get Poem
</button>
<LovePercentContent data={yourLovePercent} />
<LovePoem data={yourLovePoems} />
</>
);
}
return (
<>
<h2>Enter Two Names:</h2>
<input
name="yourName"
value={yourName}
placeholder="Your Name"
onChange={(e) => setYourName(e.target.value)}
/>
<input
name="yourName"
value={yourCrushName}
placeholder="Your Crush's Name"
onChange={(e) => setYourCrushName(e.target.value)}
/>
<button type="submit" className="btn" onClick={handleSubmit}>
Get Poem
</button>
{yourNameMissing}
</>
);
};
export default LovePercent;
You can create a boolean state like
const [dataBool, setDataBool]=useState(false);
and set this true when you click on the button by creating a function or as you like. After that add condition while displaying the poem data to screen. If this boolean is true then return poems else not. Like this:
e.g. {dataBool ? showPoems : notShowPoems}
This way it will only show when you click on it.
I would say that behaviour is normal to Reactjs and you should not worry about it too much
What you meant by reloading is actually re-rendering to be exact.
Every time a state changes (yourName and yourCrushName in your case) will cause Reactjs to re-render the whole component.
It's totally fine and it's actually how Reactjs works.
Re-rendering is not bad, only updating DOM is worth considerable, and re-rendering does not mean DOM is updated since Reactjs has applied Virtual Dom mechanism
If you don't want your component to re-render consider using ref instead.

using ...prev in setState prohibits userEvent.type to type into input when react testing

I want to test my Log In Component, which consists of two input fields, whose values are determined by a single react state that is an object with two parameters. However, when I try the test only the first letter appears in the value of the selected input and not the rest of the word. I determined my use of ...prev when updating the state to be the issue. If I only use a single input field with one state it works fine!
Here is my component:
import {useState} from 'react';
export function Login () {
//Login Credentials
const [loginCredentials, setLoginCredentials] = useState({ name: '' });
const handleChange = ({target}) => {
setLoginCredentials({[target.name]: target.value});
}
return (
<div className="login-container">
<h1>Log In</h1>
<div className="login-label-input">
<label htmlFor="name">Account Name
<input
type="name"
id="name"
name="name"
onChange={handleChange}
value={loginCredentials.name}
/>
</label>
<label htmlFor="name">Password
<input
type="password"
id="password"
name="password"
onChange={handleChange}
value={loginCredentials.password}
/>
</label>
</div>
State name: {loginCredentials.name}. State password: {loginCredentials.password}.
</div>
)
}
This works but if I include the password state:
export function Login () {
//Login Credentials
const [loginCredentials, setLoginCredentials] = useState({ name: '', password: '' });
const handleChange = ({target}) => {
setLoginCredentials((prev) => ({...prev, [target.name]: target.value}));
}
...
it does not pass the test. I does not throw an error but simply only adds the first letter of the string I am testing with:
test("log in with empty name input returns error message", async () => {
render(
<Login />
);
const nameField = screen.getByLabelText(/account name/i);
userEvent.type(nameField, 'test');
await waitFor(() => expect(nameField).toHaveValue('test'));
});
with the error:
expect(element).toHaveValue(test)
Expected the element to have value:
test
Received:
t
Is using ...prev bad or is this is a bug or what is going on?
It seems like you have to assign the new value to a different variable, I am not sure why this is necessary for the test but not in the app itself.
const handleChange = ({target}) => {
const newValue = target.value
setLoginCredentials((prev) => ({ ...prev, [target.name]: newValue }));
}

Categories

Resources