I have a user requirement when adding a form it should check if the name of the form is already exist. How can I do that in es6? I'm using AntDesign and ReactJS.
Here's my code
<Form.Item label="Form Name">
{getFieldDecorator('formName', {
rules: [formConfig],
})(<Input name="formName" onChange={onChange} />)}
</Form.Item>
const handleChange = e => {
const { name, value } = e.target;
setState(() => ({
...state,
[name]: value,
}));
let isExist = [...formDataSource];
let foundExistItem = isExist.filter(
item => item.formName === formName
);
};
If you want dynamically query the form fields, you should wrap your form with Form.create.
It injects useful functionalities like onFieldsChange listener:
const onFieldsChange = (_, changedFiels) => {
const { username } = changedFiels;
if (username) {
// Make your API checks
console.log(`Now changing ${username.name}`);
}
};
const ValidatedFields = Form.create({ onFieldsChange })(App);
Note: You should keep your Form.Items uncontrolled using getFieldDecorator therefore avoiding onChange while collecting form data.
If you still insist to make form items controlled, you should use getFieldValue:
const handleChange = e => {
const { name, value } = e.target;
const { getFieldValue } = form;
setState(() => ({
...state,
[name]: value
}));
// or use getFieldValue for a single query
const values = getFieldsValue(['formName',...more fields]);
if (values.length) {
// API CALL
}
};
Related
I have multiple state variables, that contains data entered in a form by the user. Since this form is only meant to update the existing values, I have to pass in only those values that have changed from its initial value (the one returned from the GET request).
State:
const [name, setName] = useState(props.user?.name ?? null);
const [lang, setLang] = useState(props.user?.lang ?? null);
const [enableChecks, setEnableChecks] = useState(props.user?.checkEnabled ?? false)
In the event that the user only changed the name, how can I pass in only name in the request body?
What I have tried: I have the user props, so I have multiple if statements that check if the props matches the state. If it doesn't, then I add it to the request payload. This works, but when there's a lot of state, there will be a lot of if statements, which isn't nice to look at.
Is there a better way to do this?
Instead of having multiple state variables, you can have a single state variable like
const [state, setState] = useState(props.user)
and then change handler should look like
const handleChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value,
});
};
finally, when submitting the form you can make your body data for post request like
const handleSubmit = () => {
const requestData = {}
for (const [key, value] of Object.entries(state)){
if(props.user[key] !== value) {
requestData[key] = value
}
}
axios.post('some api url', responseData)
}
You can keep your state in an object, and then only update field state when the updatedUser and user state values are different.
//use `import` in your real component instead
//import { useState } from 'react';
const { useState } = React;
//fake your user prop
const userProp = {
name: "Name",
lang: "English",
}
function App(props) {
const [user, setUser] = useState(props.user);
const [updatedUser, setUpdatedUser] = useState({});
const handleChange = (e) => {
const newlyUpdatedUser = {
...updatedUser,
}
if(props.user[e.target.name] === e.target.value) {
delete newlyUpdatedUser[e.target.name]
} else {
newlyUpdatedUser[e.target.name] = e.target.value
}
setUpdatedUser(newlyUpdatedUser);
setUser({
...user,
[e.target.name]: e.target.value
})
};
console.log(updatedUser)
return (
<React.Fragment>
<label>
Name:
<input value={user.name} name="name" onChange={handleChange} />
</label>
<label>
Lang:
<input value={user.lang} name="lang" onChange={handleChange} />
</label>
</React.Fragment>
);
}
ReactDOM.render(<App user={userProp} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I have an array of objects in my React state. I want to be able to map through them, find the one I need to update and update its value field. The body of my request being sent to the server should look like:
{ name: "nameOfInput", value:"theUserSetValue" type: "typeOfInput" }
What I thought would be simple is causing me some heartache. My reducer function calls, and I hit the "I AM RUNNING" log where it then jumps over my map and simply returns my state (which is empty). Please note that I NEVER see the "I SHOULD RETURN SOMETHING BUT I DONT" log.
NOTE: I have learned that I could be simply handingling this with useState
function Form(props) {
const title = props.title;
const paragraph = props.paragraph;
const formBlocks = props.blocks.formBlocks
const submitEndpoint = props.blocks.submitEndpoint || "";
const action = props.blocks.action || "POST";
const formReducer = (state, e) => {
console.log("I AM RUNNING")
state.map((obj) => {
console.log("I SHOULD RETURN SOMETHING BUT I DONT")
if (obj.name === e.target.name) {
console.log("OBJ EXISTS", obj)
return {...obj, [e.target.name]:obj.value}
} else {
console.log("NO MATCH", obj)
return obj
}
});
return state
}
const [formData, setFormData] = useReducer(formReducer, []);
const [isSubmitting, setIsSubmitting] = useState(false);
=====================================================================
Where I am calling my reducer from:
<div className="form-block-wrapper">
{formBlocks.map((block, i) => {
return <FormBlock
key={block.title + i}
title={block.title}
paragraph={block.paragraph}
inputs={block.inputs}
buttons={block.buttonRow}
changeHandler={setFormData}
/>
})}
</div>
Issues
When using the useReducer hook you should dispatch actions to effect changes to the state. The reducer function should handle the different cases. From what I see of the code snippet it's not clear if you even need to use the useReducer hook.
When mapping an array not only do you need to return a value for each iterated element, but you also need to return the new array.
Solution
Using useReducer
const formReducer = (state, action) => {
switch(action.type) {
case "UPDATE":
const { name, value } = action.payload;
return state.map((obj) => obj.name === name
? { ...obj, [name]: value }
: obj
);
default:
return state;
}
};
...
const [formData, dispatch] = useReducer(formReducer, []);
...
{formBlocks.map((block, i) => {
return (
<FormBlock
key={block.title + i}
title={block.title}
paragraph={block.paragraph}
inputs={block.inputs}
buttons={block.buttonRow}
changeHandler={e => dispatch({
type: "UPDATE",
payload: {...e.target}
})}
/>
);
})}
Using useState
const [formData, setFormData] = useState([]);
...
const changeHandler = e => {
const { name, value } = e.target;
setFormData(data => data.map(obj => obj.name === name
? { ...obj, [name]: value }
: obj
));
};
...
{formBlocks.map((block, i) => {
return (
<FormBlock
key={block.title + i}
title={block.title}
paragraph={block.paragraph}
inputs={block.inputs}
buttons={block.buttonRow}
changeHandler={changeHandler}
/>
);
})}
I have come to understand my problem much better now and I'll update my question to reflect this.
As the user interacted with an input I needed to figure out if they had interacted with it before
If they did interact with it before, I needed to find that interaction in the state[] and update the value as required
If they didn't I needed to add an entirely new object to my forms state[]
I wrote two new functions, an AddObjectToArray function and an UpdateObjectInArray function to serve these purposes.
const handleFormInputChange = (e) => {
const { name, value, type } = e.target;
const addObjectToArray = (obj) => {
console.log("OBJECT TO BE ADDED TO ARRAY:", obj)
setFormData(currentArray => ([...currentArray, obj]))
}
const updateObjectInArray = () => {
const updatedObject = formData.map(obj => {
if (obj.name === name) {
//If the name matches, Update the value of the input
return ({...obj, value:value})
}
else {
//if no match just return the object as is
return obj
}
})
setFormData(updatedObject)
}
//Check if the user has already interacted with this input
if (formData.find(input => input.name === name)) {
updateObjectInArray()
}
else {
addObjectToArray({name, value, type})
}
}
I could get more complicated with this now and begin to write custom hooks that take a setState function as a callback and the data to be handled.
I have an input field which is written in a child component
and its inside return function
const EditSectionComponent = ({
editCaption,
editName,
}) => {
const {name,caption} = details;
return (
<input
type="text"
className="imageNameDetails"
value={name}
onChange={e => editName(e.target)}
/>
)
}
and in parent Component, it's like
const onEditClick = id => {
const selectedAsset = All.find(i => i.id === id);
setDetails(selectedAsset);
}
const [details, setDetails] = useState([]);
const editName = target => {
setDetails({ ...details, [name]: target.value })
};
Initial page load I can see both caption and name in the text field, but I am not able to change its value
It's not updating the UI. Is the right way to reflect the newly edited text in the input field
Make sure to destructure the details from props in EditSectionComponent.
In your parent, the initial state i.e. details is defined as an array. It need to be an object.
Also while doing setDetails, you need to specify the key. (not a dynamic name in your case)
Updated code is here:
const EditSectionComponent = ({
editCaption,
editName,
details,
}) => {
const {name,caption} = details;
return (
<input
type="text"
className="imageNameDetails"
value={name}
onChange={e => editName(e.target)}
/>
)
}
const [details, setDetails] = useState({name: '', caption: ''});
const editName = target => {
setDetails(prev => ({...prev, name: target.value}))
};
In all the examples that I saw online, only the event was being passed into the handleChange and value was being used automatically. However, I get an error that value is not found. How can I use value in the handleChange? I am trying to validate the form using Formik here.
export default function MyPage() {
const [isSubmitted, setIsSubmitted] = useState(false);
const [isRemoved, setIsRemoved] = useState(false);
const [removeUser] = useMutation<Response>(USER);
let submitForm = (email: string) => {
User({
variables: {
email: email,
},
})
.then(({ data }: ExecutionResult<Response>) => {
if (data !== null && data !== undefined) {
setIsRemoved(true);
}
}) };
const formik = useFormik({
initialValues:{ email: '' },
onSubmit:(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
},
validationSchema:schema
})
const handleChange = (e: ChangeEvent<HTMLInputElement>)=>{
if (name!== null) && (value!==null){
const {name,value} = event.target;
formik.setFieldValue(name,value);
}
}
return (
<div>
<Form
onSubmit={e => {
e.preventDefault();
submitForm(formik.values.email);
}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={formik.touched.email ? formik.errors.email : ''}
error={formik.touched.email && Boolean(formik.errors.email)}
label="Email"
value={formik.values.email}
//onChange={change.bind(null, 'email')}
onChange={handleChange}
/>
<CustomButton
disabled={!isValid || !formik.values.email}
text={'Remove User'}
/>
</div>
</Form>
</div>
);
}
On this line:
const {name,value} = event.target;
I also get this error even though I am already checking this with an if statement:
Property 'name' does not exist on type 'EventTarget | null'.ts(2339)
You are trying to access the values that are not assigned yet. First, declare them then try to access them. Also, you are using event.target but you need to use e.target instead as you have declared event like:
const handleChange = (e: ChangeEvent<HTMLInputElement>)
So, the code inside the handleChange() function needs to be updated like:
const { name,value } = e.target;
if (name && value){
formik.setFieldValue(name, value);
}
In your handleChange, you use (e) as a parameter but then you reference event.target. Rename your parameter to (event).
handleChange= (event) => {
const {name,value} = event.target;
...
}
I am unable to save description as part of the component's state. I can only save the title. How do I save title and description to the database?
const BlogCreate = ({ history }) => {
const [blogCreate, setBlogCreate] = useState({
title: "",
description: ""
});
const [editorState, setEditorState] = useState(
EditorState.createEmpty(),
);
const handleChange = ({ currentTarget }) => {
const { name, value } = currentTarget;
setBlogCreate({...blogCreate, [name]: value});
};
const onEditorStateChange = editorState => {
setEditorState(editorState);
};
const handleSubmit = async event => {
event.preventDefault();
const data = draftToHtml(convertToRaw(editorState.getCurrentContent()));
try {
await blogAPI.create(blogCreate, data);
} catch (error) {
console.log(error)
}
}
console.log(data);
}
return(
<Field type="text" name="title" error={errors.title} value={blogCreate.title}
onChange={handleChange}
/>
<Editor name="description" editorState={editorState} onEditorStateChange={editorState => onEditorStateChange(editorState)}
/>
<button type="submit">Save</button>
);
}
export default BlogCreate;
Based on the full code you've provided me, I realised that you aren't properly updating the blogCreate state whenever there is a change at the Editor component.
The onEditorStateChange() should be updating the blogCreate state, and in addition, changeValue() needs to return the result value.
const changeValue = editorState => {
const value = ....
return value;
};
const onEditorStateChange = editorState => {
const description = changeValue(editorState);
setBlogCreate({
...blogCreate,
description,
});
setEditorState(editorState);
};
This way, description will be properly updated on your state, and it will be sent to your server side when you make the blogAPI.create() request.