Set object in state from list - javascript

I am working on a ToDo list in React where users can do CRUD operations.
The last function I want to implement is updating a task.
Currently, you can load all items you already made. These items are visible in a list, each with an edit button to update the corresponding information.
Whenever a user clicks the update button, I want to load all the information in the input fields. These fields show in a pop-up menu after clicking the update button. However, I can not seem to get this to work. The input fields stay empty.
I have the following local state, no redux:
const [formValue, setFormValue] = useState({
title: "",
category: "",
description: "",
priority: 1, //default is 1
});
The function that is bound to the "edit" button:
I also tried to set the setFormValue with the entire object but I could not get that to work either.
function handleEditOpen(event) {
for (const key in event) {
const value = event[key];
setFormValue({
...formValue,
key: value,
});
}
setEditDialogOpen(true);
}
I did just learn about the async React state, hence console.log()'s don't show information instantly. However, with the for-loop in function, the state still didn't update.
One of the input fields (made with Material UI):
<TextField
required
className="createItemCategoryTextfield"
type="category"
name="category"
id="outlined-basic"
variant="outlined"
margin="normal"
color="primary"
label="Category"
defaultValue={formValue.category}
onChange={handleInputChange}
focused
/>
How would I be able to load all information of a task in my local state, to show them in the edit screen and their corresponding input fields?

Firstly update state only one by directly by this you can ignore for loop
setFormValue((prevState) => ({
...prevState,
...event
}));
instead of setting defaultValue directly set value

I dont see the value property in your material ui element - TextField value properties, for example
const [name, setName] = useState([])
cosnt handleChangeValue = (someValue) => {
setName(someValue)
}
<TextField
value={name}
onChange={(e) => handleChangeValue(e.target.value}
/>

Related

input default value from api and change it to edit it. Like editing profile from instagram

i want to display default value of an input from the api data. but when i dont change the value it reads the default state value not the api value.
i want it to work just like the edit profile on instagram.
i did this
<input defaultValue={transaction.resi} onChange={(e)=> setNoResi(e.target.value)} />
<select defaultValue={transaction.courier} onChange={(e)=> setKurir(e.target.value)} />
<input defaultValue={transaction.ongkir} onChange={(e)=> setOngkir(e.target.value)} />
i managed to get the default value from api
but when i only change one of the field and not the other and input it, the field i didnt change returning the state default value which is 0, not the api default value.
any idea how to do it? any help would be appreciated. thanks
Instead of defaultValue use value and set it to the value of your api data. I've done it like this in the past:
First I check for the event that I want to edit by looking through the api data like this:
const editableEvent = valueOfApiDataState.find((event) => event.id.toString() === valueOfClickedOnEvent.id
Now you can check if this true by putting this in a useEffect. Once it's true you can set all required values like this:
useEffect(() => {
if(editableEvent){
setTitle(clickableEvent.title)
setLocation(clickableEvent.location)
etc..
},
[editableEvent]
})
**Edit for extra information:
Now you've set the title of your state to the value from your api.
const [title,setTitle] = useState("")
And in your input it will be like this:
<input value={title} onChange={(e)=>setTitle(e.target.value)}/>
Now the values you don't change won't be empty whenever you edit just one value.

Initial state value not being read (React)

I'm building an application that revolves around recording payments.
I have a state that's along the lines of
const [state, setstate] = useState({
amountPaid: Number(bill.total), // bill comes from another component fully populated
datePaid: new Date(),
});
I'm trying to render it in a MUI component (A modal)
return (
<div>
<form>
<TextField
type="number"
label="Enter the amount you are paying"
onChange={(e) =>
setState({ ...state, amountPaid: e.target.value })
}
value={state.amountPaid}
/>
When I submit the form, I update the bill as such, with the amount I paid (might be less than the whole amount needed)
useEffect(() => {
setPayment({
...payment,
amountPaid: Number(bill.total) - Number(bill.totalAmountReceived),
});
}, [bill]);
When I reopen the modal to check for the updated amount, the amountPaid row is correctly displayed according to the above logic. But the amountPaid is displayed as null initially, whereas the date is displayed correctly.
Why doesn't it initialize to bill.total?
Would be glad to provide any addition details that are needed.
You can only set component level initialization in any react component. If you want to set some value from previous/any parent component you will need to update the initial state value with the following expression:
If you are using a class based component the use componentDidMount(), if function based then use useEffect()
const[state,setState]=useState({
amountPaid: 0,
datePaid: new Date()
};
useEffect(()=>{
setState({...state,state.amountPaid:Number(bill.total) ,
//assuming bill is coming as a prop from another component
},[]);
One more suggestion is try not to use the state altering functions inside render, use a separate function and call state update logic in it

Submit and handle two forms with one handleSubmit in react-hook-form

In my react app, I have two address forms on one page that have two save address functions which save the address in the database. There's also one submit button that submits both two fields and navigates to the next page (The plus button in the circle adds the address to the saved addresses list):
What I want to do is to validate the form fields and I'm already doing that by calling two instances of the useForm() hook:
// useForm hook
const {
control: senderControl,
handleSubmit: handleSenderSubmit,
setValue: senderSetValue,
} = useForm({
defaultValues: {
senderName: '',
senderLastName: '',
senderPostalCode: '',
senderPhone: '',
senderAddress: '',
},
});
// useForm hook
const {
control: receiverControl,
handleSubmit: handleReceiverSubmit,
setValue: receiverSetValue,
} = useForm({
defaultValues: {
receiverName: '',
receiverLastName: '',
receiverPhone: '',
receiverPostalCode: '',
receiverAddress: '',
},
});
I've then added the handleSubmit method of these two hooks to the onClick (onPress in RN) of the plus button for each field respectively.
This does indeed validate the forms individually but the problem arises when I'm trying to submit the whole page with the SUBMIT button.
I still want to be able to validate both of these two address fields when pressing the general SUBMIT button but I have no idea how I can validate these two instances with one handleSubmit and get the return data of both fields' values.
EDIT (CustomInput.js):
const CustomInput = ({
control,
name,
rules = {},
placeholder,
secureTextEntry,
keyboardType,
maxLength,
textContentType,
customStyle,
}) => (
<Controller
control={control}
name={name}
rules={rules}
render={({field: {onChange, onBlur, value}, fieldState: {error}}) => (
<View style={customStyle || styles.container}>
<TextInput
value={value}
onBlur={onBlur}
onChangeText={onChange}
placeholder={placeholder}
keyboardType={keyboardType}
maxLength={maxLength}
textContentType={textContentType}
secureTextEntry={secureTextEntry}
style={[
styles.textInput,
{
borderColor: !error
? GENERAL_COLORS.inputBorder
: GENERAL_COLORS.error,
},
]}
/>
{error && <Text style={styles.errMsg}>{error.message || 'error'}</Text>}
</View>
)}
/>
);
Usage:
<CustomInput
control={control}
name="senderPostalCode"
rules={{
required: 'Postal Code is Required',
}}
placeholder="Postal Code"
keyboardType="number-pad"
textContentType="postalCode"
customStyle={{
width: '49%',
marginBottom: hp(1),
}}
/>
Is there even any way this can be possible at all?
Thanks to #TalgatSaribayev 's comment for leading me to this solution.
I didn't need to set any specific validation rules for the address field and in the end I separated the sender and receiver forms into two different pages.
First, I've got to point out that instead of getting the input values of postalCode and address fields with the getValues API, I used the useWatch hook to get the most updated values.
// Watch inputs
const watchedInputs = useWatch({
control,
name: ['senderPostalCode', 'senderAddress'],
});
When I saved the input values with getValues in a variable, I got the previous state instead of the most recent one and the only way to solve that was calling getValues('INPUT_NAME') whenever I wanted to get the most recent one. Even then I needed to call an instance of useWatch without saving it in any variable to keep track of the changes since typing in the input fields wouldn't update the getValues at all. So in the end I resolved to use useWatch and store its values in a variable and use that to access the values of the input fields.
///////////////////////////////////////////////////////////////////////////////////////////////////
As #TalgatSaribayev pointed out, creating just one useForm instance was sufficient enough. All I had to do was to create a function which would set the errors manually and check their validation upon pressing the save address button.
// Check if sender postal code input has error
const senderHasError = () => {
if (!/^\d+$/.test(watchedInputs[0])) {
setError('senderPostalCode', {
type: 'pattern',
message: 'Postal Code must be a number',
});
return true;
}
// Any other rules
if (YOUR_OWN_RULE) {
setError('senderPostalCode', {
type: 'custom',
message: 'CUSTOM_MESSAGE',
});
return true;
}
// Clear error and return false
clearErrors('senderPostalCode');
return false;
};
The problem was that the errors wouldn't get updated (cleared) even when they had passed the validation. As if the invalid input wouldn't attach onChange event listeners to re-validate it. Something that happens as default when you submit the form with the onSubmit mode of useForm. (https://react-hook-form.com/api/useform)
So I resolved to use the trigger API of useForm to manually trigger form validation and listen for the changes on the postalCode field when the save address button is pressed.
First I created a toggle state which changes the trigger state:
// Postal code onSubmit event state
const [triggered, setTriggered] = useState(false);
Then used the trigger API of useForm in a useMemo to trigger the input validation only if the triggered state is set to true and the input field's value has changed:
// Manually trigger input validation if postal code's onSubmit event is true
useMemo(() => {
if (triggered) trigger('senderPostalCode');
}, [watchedInputs[0]]);
I assume the way I triggered the input field with the trigger API works the same way as useForm's mode of onSubmit does it under the hood: Starting the trigger when the user presses the save address button by changing the trigger state with setTrigger.
// Add address to sender favorites
const handleAddSenderFavAddress = () => {
// Trigger on the press event
setTriggered(true);
// Return if sender postal code input has error
if (senderHasError()) return;
// SAVE ADDRESS LOGIC
///////////////////////////////
};
This was the only way I managed to validate separate input fields apart from the general validation that occurs with useForm's handleSubmit function.
I welcome any more answers that might lead to a better solution.
You can use a single instance of useForm hook and register all your fields in both the forms using the same register method.
This way whenever you click on submit, it will fetch the values from all the fields registered in different forms using the same register method.
Have attached a code sandbox link for your reference

How do I pre-populate a React Hook form MUI textfield after fetching data from Firestore?

I am using React Hook Forms with MUI TextField components.
I am fetching data from Firestore and I would like to prefill the form with this data.
I am using useForm with default value but I'm not really sure how to use it.
I was able to get some of the data on the fields by using a spread operator. I was trying to find a way to de-structure the eventData as an object so I can match the fields with the fetched Data.
Here is the code:
const [event, setEvent] = useState('')
const { register, handleSubmit, errors, reset } = useForm({
resolver: yupResolver(schema),
defaultValues: {...event}
})
// get the data to prefill the form
useEffect(() => {
getEvent(storyId, eventId, setEvent)
reset(event)
}, [reset])
Here's an example of a TextField
<TextField
variant="outlined"
margin="normal"
fullWidth
id="date"
label="date"
name="date"
autoComplete="date"
type="date"
autoFocus
inputRef={register}
error={!!errors.date}
helperText={errors?.date?.message}
/>
I can confirm that the data object does come in...
and this is what I get on the form...
As you can see some of the data is filling the form, but the MUI labels are not shrinking and the text has redability issues. Also since I am not destructuring the eventData, things like the date(which is a date format) and the coordinates which is Firestore geopoint object of latitude and longitude is not being filled in.
A few questions:
How do I make sure I set the data in my React component before React-hook-form initializes an object ? I was able to do this with reset. is this best practice?
How can I make the MUI label shrink when the form is filled with data?
How can I desctructure the eventData and add it to the form?
Unfortunately, uncontrolled MUI inputs don't reposition their labels if value was set programatically after initial render. Wrap your inputs in <Controller> to make them controlled and then everything will be fine. If you need to stay with uncontrolled components the only way I know to make them readable is to use <TextField InputLabelProps={{ shrink: true }} />.
Take a look here: https://material-ui.com/components/text-fields/#limitations
1: don't know if this is the best practice. but that's also how I did it.
2: I had the same problem once and setting defaultValue: '' in the input solved it, but what #Aitwar answered should help too.
3: create an object with the names of your fields and fill it with the data of eventData. If you can't do it, provide an example on codesandbox and i'll try to help you.
you can prevent the rendering process by conditional rendering to make sure you already fetched the data.
if you want to change the prefill the inputs during the render time, you can add the following props to your input:
<TextField InputLabelProps={{ shrink: !!getValues('name') }} />

React.js Using the same FORM for creating a NEW db object and EDIT various objects

I have a simple input field like this:
<TextField
required
variant="standard"
type="string"
margin="normal"
fullWidth = {false}
size="small"
id="orgName"
placeholder="Organisation Name"
label="Name"
name="orgName"
// defaultValue={orgData ? orgData.orgName : ""}
//inputRef={(input) => this.orgName = input}
value={this.state.orgName || ""}
onChange={this.handleChange("orgName")}
error={this.state.errors["orgName"]}
/>
I want to use the same input field for new and update? For new I just set the state to empty, and save the values. Which works fine. Now I have a select dropdown to edit the previously saved objects.
My problem is with editing, and I am tearing my head out trying to find the any way to do this. All these are the corresponding issues:
If i set the state from props - any edited changes are being reset
If i don't set the state from props, I get all blank fields, which is incorrect.
If I use defaultValue to load the form inputs from props, then its only called once. And it does not reload when I change the object to be edited.
If i just use onChange handler for, the form gets creepy slow, with many inputs on page
If I use refs, I am not able to reset the refs to reload the input when the object to be edit changes.
I have managed to make it work with componentWillReceiveProps, but it is deprecated, and react website is saying its dangerous to use it.
componentWillReceiveProps(nextProps) {
if (nextProps.orgData !== this.props.orgData) {
this.setState({
"orgName": nextProps.orgData.orgName,
"orgPath": nextProps.orgData.orgPath,
"orgAddress": nextProps.orgData.orgAddress,
"orgPhone": nextProps.orgData.orgPhone,
"orgEmail": nextProps.orgData.orgEmail,
})
}
}
So how can I actually create an editable form where the values have to be loaded from props for different instances from db, but they have to controlled by the state. There has to someplace where I have to check saying "hey if the props have changed, reset the state with the new props for edit???
This has been the most frustrating experience using react for me. How are there no examples anywhere on how to build a simple form to create new, and editable object using react and redux. It just seems overly complicated to do such a simple thing, the whole thing just sucks!
There has to someplace where I have to check saying "hey if the props have changed, reset the state with the new props for edit???
Yes you can use React.useEffect hook on the special prop or Array of props, then when that/those prop(s) change the internal hook function will be fire.
e.g. :
const MyComponent = (props) => {
let [myState,setMyState]= React.useState(null);
React.useEffect(()=>{
setMyState(props.prop1);
},[props.prop1]);
return ...
}

Categories