React beginner here, English is not my mother language so sorry for mistakes. I have url from there I want to get information I will show how I have done other pages(using antd form), but now I'm in different page where at the end of my url is payer=sender, so if payer is "sender" then do this. that 'payer=sender' at the end of my url comes by clicking a sender button, then when it is clicked form input should fill accordingly
const senderPays = () => {
history.push(`/bilInfo/${custId}?${query}&payer=sender`);
};
<Button onClick={senderPays}>Sender</Button>
In previous pages I could get pickUpName from url like this :
const query = window.location.toString().split("?")[1];
const urlParams = new URLSearchParams(query);
const PickUpName = urlParams.get("pickUpName") || props.customer?.name;
<Form.Item
label="pickUpName"
name="pickUpName"
initialValue={PickUpName || ""}
>
<Input type="string" />
</Form.Item>
This is my url: https://localhost:5001/#/billingInfo/5dc9d690-a549-4baf-868e-a441c6c4ff47?pickUpName=gholam&pickUpContactPerson=&pickUpPhone=8744444&pickUpAddress=gerbytnie&pickUpPostalCode=65230&pickUpPostalRegion=helsnki&deliveryName=shammms&deliveryContactPerson=kalle&deliveryPhone=2658265&deliveryAddress=&deliveryPostalCode=65230&deliveryPostalRegion=&payer=sender
As you can see end of url is &payer=sender
so if payer is sender then have these information and if payer is receiver then have these information:
this is my code:
useEffect(() => {
if (payer === "sender") {
billingName = PickUpName;
} else if (payer === "receiver") {
billingName = DeliveryName;
}
}, [payer]);
Component
<Form.Item
label="billingName"
name="billingName"
initialValue={billingName || ""}
>
<Input type="string" />
</Form.Item>
So I need somehow to get that billingName in my forms initialValue, depending who is payer.
Use the useForm react hook to manually initialize fields:
Summary:
Initialize a form instance using useForm().
Pass that FormInstance to your Form
Use useEffect hook to dynamically set the value of the Form.Item based on the value of payer
import { Form } from 'antd';
const Demo = () => {
// Note that `useForm` is a React Hook and will only works in functional components
const [form] = Form.useForm();
const payer = new URLSearchParams(window.location.search).get('payer');
// Manually initialize billingName based on `payer` value
useEffect(() => {
if (payer === "sender") {
form.setFieldsValue({ billingName: request.pickUpName });
} else if (payer === "receiver") {
form.setFieldsValue({ billingName: request.deliveryName });
}
}, [payer])
// Pass the FormInstance `form` to your Form
return (
<Form form={form}>
<Form.Item
label="billingName"
name="billingName"
>
...
</Form.Item>
...
)
Here's some documentation to help you out:
Antd: manually setting form field values
React: useEffect hooks
Related
I'm trying to setup a form. It has Edit feature where on edit I call an API and get the data into state.
I'm struggling to display data in the form after api call. There's no problem utilizing the API or calling the redux functions. Problem is that my Form only displays last data in the redux state but not the updated data.
That's how I'm doing the stuff.
Calling API if isEdit===True at the same time Form is being displayed on component mount.
Updateding state after success as an object called customer
accessing the customer object like this
const { customer } = useSelector((state) => state.customers)
Lets say I have a input field where I want to display the email of customer.
I'm handling this think like that:
email: isEdit ? customer?.email : '', // At this point there is some problem
It loads the previous data that was stored in the state.customer but not the new one.
I believe my email field is rendering first and then doesn't updated the value when change happens in state.customer.
So how I can fix this? So that email value should be changed at the same time if state.customer got changed
Here is the full component. Still removed irrelevant part.
const CustomerNewEditForm = ({ isEdit, id, currentUser}) => {
const dispatch = useDispatch()
const navigate = useNavigate()
console.log('isEdit', isEdit, 'id', id, 'currentUser', currentUser)
// get sales reps
const { customer } = useSelector((state) => state.customers)
// const customer = () => {
// return isEdit ? useSelector((state) => state.customers?.customer) : null
// }
const { enqueueSnackbar } = useSnackbar()
const defaultValues = useMemo(
() => ({
email: isEdit ? customer?.email : '',
name: isEdit ? customer?.name : '',
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[currentUser]
)
const methods = useForm({
resolver: yupResolver(NewUserSchema),
defaultValues
})
const {
reset,
watch,
control,
setValue,
handleSubmit,
formState: { isSubmitting }
} = methods
const values = watch()
useEffect(() => {
if (isEdit === true) {
dispatch(getCustomerDetails(id))
console.log(customer)
}
if (isEdit && currentUser) {
reset(defaultValues)
}
if (!isEdit) {
reset(defaultValues)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isEdit, currentUser])
const onSubmit = async () => {
try {
await new Promise((resolve) => setTimeout(resolve, 500))
reset()
let body = {
email: values.email,
name: values.name,
}
console.log(body)
dispatch(createCustomer(body))
enqueueSnackbar(!isEdit ? 'Create success!' : 'Update success!')
// navigate(PATH_DASHBOARD.admin.root)
} catch (error) {
console.error(error)
}
}
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Grid item md={3}>
{' '}
<RHFTextField name="name" label="Customer Name" />
</Grid>
<Grid item md={3}>
{' '}
<RHFTextField name="email" label="Email Address" />
</Grid>
</FormProvider>
)
}
export default CustomerNewEditForm
Here in the component defaultValues carries the previous data from customer object if its True and renders the form with those values. but new data comes a miliseconds later but form renders first.
First of all try to console.log your customer data and make sure that it gets a fresh data on last render.
If it gets fresh data, try take a look at your Input component, it might set some initial data, so the input will be editable and controlled by some state.
Try to modify your input's state on redux store update in useEffect.
Currently that's all that I can suggest, update your post with code with your form and input, also post your console.log result, if my answer doesn't helped you.
If the problem would be not in form\input state and console.log wouldn't show you actual updated data in last render, then I will need to see your redux store code to resolve this issue.
Hope it helped
I have some clearable select, and I want to reset the applets field in state to an empty array.
const defaultFormValues = { device: { ...initialDevice }, applets: [] };
const { control, getValues, setValue, reset, handleSubmit } = useForm<CreateDeviceFormData>({
mode: "all",
reValidateMode: "onChange",
defaultValues: defaultFormValues,
resolver: yupResolver(validationSchema),
});
const onChangeHandler = React.useCallback(
(value: Experience | null) => {
if (value) {
setValue("applets", getApplets(value));
} else {
setValue("applets", []);
// reset(defaultFormValues);
}
setValue("device.experience_id", value ? value.id : undefined);
},
[templateSelector, setValue],
);
console.log("current data", getValues(), control);
return (
<>
<SomeAutocompleteComponent control={control} onChange={onChangeHandler} />
<SelectAppletsComponent control={control} />
</>
);
export const SelectAppletsComponent = ({ control, onChange }) => {
const applets = useWatch({ control, name: "applets" }) as Applet[];
const device = useWatch({ control, name: "device" }) as Device;
if (!applets.length) {
return null;
}
return (
<SpanWrapper className="p-col-8">
{applets.map((applet) => (
<LabelRadio
key={applet.id}
inputId={applet.applet_type}
value={applet.applet_type}
label={applet.name}
checked={device.applet_type === applet.applet_type}
onChange={onChange}
/>
))}
</SpanWrapper>
);
};
the problem is that clearing the selection on UI with setValue("applets", []); not working for some reason, and I don't understand why, and how to do it without reset method, which resets the whole state, not just single property as I understand
You should always register fields if you want to use them as RHF's form state.
React.useEffect(() => {
register("applets");
}, [register]);
This fixes an issue.
Update:
Also a new method resetField is available
Just to follow up on this, it is indeed the right solution provided by AuthorProxy.
Using defaultValues doesn't register the fields (it seems that they are still added to the formData on submit, but since they are not registered, any user triggered changes to these fields won't reflect on the formData).
You have to register every field you want the user to be able to interact with.
We usually register fields via inputs in the JSX, but we also need to register the array since there is no input for it in the JSX.
As per shown by the author of the react hook form library.
https://github.com/react-hook-form/react-hook-form/discussions/3160
And sandbox
https://codesandbox.io/s/inspiring-wood-4z0n0?file=/src/App.tsx
I'm working on implementation of a multi step form with react-hook-form and my problem is that input fields do not get reinitialized with the form data when I return to the previous page.
I'm using <FormProvider /> component from react-hook-form to inject the form data into the pages and my input components are registered with register method from useFormContext() hook
const CreateAccount = () => {
const [currentStep, setCurrentStep] = useState(0);
const methods = useForm<FormData>({
mode: "onChange",
});
const onSubmit = (data) => console.log(data);
const handleNextStep = () => {
if (currentStep >= 5) return;
setCurrentStep(currentStep + 1);
};
const handlePreviousStep = () => {
if (currentStep <= 0) return;
setCurrentStep(currentStep - 1);
};
const renderContent = () => ({
[RegistrationSteps.UsernameEmail]: <UsernameEmail handleNextStep={handleNextStep} handlePreviousStep={handlePreviousStep} />,
[RegistrationSteps.Password]: <CreatePassword handleNextStep={handleNextStep} handlePreviousStep={handlePreviousStep} />,
});
return (
<Container maxWidth="sm">
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
{renderContent()[currentStep]}
</form>
</FormProvider>
</Container>
);
};
export default CreateAccount;
Here is what the input fields look like
const {
register
} = useFormContext();
<TextField
label="Email"
{...register("email")}
/>
Even though the form still holds the data in its state, it does not populate into corresponding fields when I switch back and forth between the form pages.
Instead of a single form at a global level, I recommend creating each component in your step as a form with its own instance of useForm() and wrapping steps in a state provider to store data across different steps. That way, you can assign values to the step forms from the respective state using defaultValues option of useForm on initialization.
You can check out this for the basic architecture that I'm trying to explain.
defaultValues in useForm
Iam using multiple inputs inside maps i want to set focus to next input when i click enter in react Hooks.
With the help of refs
Iam using material ui text field for getting input
I tried in react class component wihtout ref it works with error but in hooks it not works
class compomnent code:
constructor(props) {
this.state = {}
}
inputRefs = [];
_handleKeyPress = e => {
const {currentTarget} = e;
let inputindex = this.inputRefs.indexOf(currentTarget)
if (inputindex < this.inputRefs.length - 1) {
this.inputRefs[inputindex + 1].focus()
}
else {
this.inputRefs[0].focus()
}
};
Inside render in added this within map function
this.state.data.map((data) => return (
<TextField
inputProps = {{onKeyPress:(e) => this.function1(e, data)}}
onChange={this.changevaluefunction}
inputRef={ref => this.inputRefs.push(ref)}
onFocus={this.handleFocus} ref={`input${id}`} /> ))
I have implemented the solution in a different way with the functional component. I have taken the 4 fields and seated its ref with the createRef hook.
I can see from your solution, you wanted to move focus to the next input element whenever you press Enter key on the current element.
I am passing the next target element argument in the onKeyUp handler along with the actual event and then detecting whether the Enter key is pressed or not. If Enter key is pressed and the targetElem is present then I am moving focus to the passed targetElem. By this way you have better control over the inputs.
You can see my solution here
https://codesandbox.io/s/friendly-leftpad-2nx91?file=/src/App.js
import React, { useRef } from "react";
import TextField from "#material-ui/core/TextField";
import "./styles.css";
const inputs = [
{
id: "fName",
label: "First Name"
},
{
id: "lName",
label: "Last Name"
},
{
id: "gender",
label: "Gender"
},
{
id: "address",
label: "Address"
}
];
export default function App() {
const myRefs = useRef([]);
const handleKeyUp = (e, targetElem) => {
if (e.key === "Enter" && targetElem) {
targetElem.focus();
}
};
return (
<div>
{inputs.map((ipt, i) => (
<TextField
onKeyUp={(e) =>
handleKeyUp(e, myRefs.current[i === inputs.length - 1 ? 0 : i + 1])
}
inputRef={(el) => (myRefs.current[i] = el)}
id={ipt.id}
fullWidth
style={{ marginBottom: 20 }}
label={ipt.label}
variant="outlined"
key={ipt.id}
/>
))}
</div>
);
}
You can convert this.inputRefs into a React ref so it persists through renders, and other than this you pretty much remove all references to any this object.
Example Component:
const LENGTH = 10;
const clamp = (min, max, val) => Math.max(min, Math.min(val, max));
export default function App() {
const [data] = useState([...Array(LENGTH).keys()]);
const inputRefs = useRef([]); // <-- ref to hold input refs
const handleKeyPress = index => () => { // <-- enclose in scope
const nextIndex = clamp(0, data.length - 1, index + 1); // <-- get next index
inputRefs.current[nextIndex].focus(); // <-- get ref and focus
};
return (
<div className="App">
{data.map((data, index) => (
<div key={index}>
<TextField
inputProps={{ onKeyPress: handleKeyPress(index) }} // <-- pass index
inputRef={(ref) => (inputRefs.current[index] = ref)} // <-- save input ref
/>
</div>
))}
</div>
);
}
If you are mapping the input field and want to focus on click, you can directly give the id attribute to the input and pass the array id.
After that, you can pass id inside a function as a parameter, and get it by document.getElementById(id).focus().
English is not my mother language so sorry for mistakes, i'm react beginner, my question:
I am at route /#/billingInfo/
when user clicks 'sender' button payers name will be senders name and same for 'receiver' button, but the problem is i'm pushing same url which is this same page, i get senders or receivers name in my input as i should but only when i refresh the page, my question is how to not refresh the page and get those in my input (need to render/forceUpdate < Form > or that specific input when user clicks those buttons
??)
my code:
const [form] = Form.useForm();
const query = window.location.toString().split("?")[1];
const urlParamss = new URLSearchParams(query);
const payer = new URLSearchParams(query).get("payer");
const bNameUrl = urlParamss.get("bName");
const PickUpName = urlParamss.get("pName");
const DeliveryName = urlParamss.get("dName");
let bName;
if (payer === "sender") {
bName = PickUpName;
} else if (payer === "receiver") {
bName = DeliveryName;
}
const senderPays = () => {
history.push(`/billingInfo/${customerId}?${query}&payer=sender`);
};
const receiverPays = () => {
history.push(`/billingInfo/${customerId}?${query}&payer=receiver`);
};
return (
<div>
<Form>
<div>
<Button onClick={senderPays}>sender</Button>
<Button onClick={receiverPays}>receiver</Button>
</div>
<Form.Item
label="payers name"
name="bName"
initialValue={bNameUrl || bName}
>
<Input type="string" />
</Form.Item>
</Form>
</div>
);
}
export default BillingInfo;
If the payer query parameter is the only difference in urls when the new routes are pushed, then you can "listen" for changes on that value in an useEffect hook.
Create a custom query parameter hook. From the React-Router docs Query Parameters.
const useQuery = () => {
return new URLSearchParams(useLocation().search);
};
Use this hook to get the query parameters.
const {
payer,
bName: bNameUrl,
dName: DeliveryName,
pName: PickUpName
} = useQuery();
Move bName into component state.
const [name, setName] = useState(bNameUrl || bName);
Use useEffect to handle any changes to payer and update name state.
useEffect(() => {
setName(payer === 'sender' ? PickUpName : DeliveryName);
}, [payer]);
Render name as the default input value (retains whatever form logic you may have). Use a react key on the form to help "re-initialize" it by indicating to react it's a "new" component and needs to be mounted. See Fully uncontrolled component with a key.
<Form key={name}>
<div>
<Button onClick={senderPays}>sender</Button>
<Button onClick={receiverPays}>receiver</Button>
</div>
<Form.Item
label="payers name"
name="bName"
initialValue={name}
>
<Input type="string" />
</Form.Item>
</Form>