I want to make a submit form by Redux-Form which has a image upload field along with other text fields. I have tried the following approach for image upload and the problem is whenever I try to upload image the form gets re-rendered. How can I do it in a proper way? And another thing is How can I send entire form data (including uploaded image) to Back end? I have used here react,redux-form and material-ui
<Box className={classes.controlTitle}>
Upload Organization Logo
</Box>
<Field
name="logo"
type="file"
component={renderField}
placeholder="Upload your organization logo"
className={classes.field}
/>
I suggest using something like react-uploady. It takes care of the file upload for you and you can use any form/components/ui libraries with it:
import React, { useState, useCallback, useMemo, forwardRef } from "react";
import styled, { css } from "styled-components";
import Uploady, {
useBatchAddListener,
useBatchFinishListener,
useUploadyContext
} from "#rpldy/uploady";
import { asUploadButton } from "#rpldy/upload-button";
const MyUploadField = asUploadButton(
forwardRef(({ onChange, ...props }, ref) => {
const [text, setText] = useState("Select file");
useBatchAddListener((batch) => {
setText(batch.items[0].file.name);
onChange(batch.items[0].file.name);
});
useBatchFinishListener(() => {
setText("Select file");
onChange(null);
});
return (
<div {...props} ref={ref} id="form-upload-button" title={text}>
{text}
</div>
);
})
);
const MyForm = () => {
const [fields, setFields] = useState({});
const [fileName, setFileName] = useState(null);
const uploadyContext = useUploadyContext();
const onSubmit = useCallback(() => {
uploadyContext.processPending({ params: fields });
}, [fields, uploadyContext]);
const onFieldChange = useCallback(
(e) => {
setFields({
...fields,
[e.currentTarget.id]: e.currentTarget.value
});
},
[fields, setFields]
);
const buttonExtraProps = useMemo(
() => ({
onChange: setFileName
}),
[setFileName]
);
return (
<Form>
<MyUploadField autoUpload={false} extraProps={buttonExtraProps} />
<br />
<input
onChange={onFieldChange}
id="field-name"
type="text"
placeholder="your name"
/>
<br />
<input
onChange={onFieldChange}
id="field-age"
type="number"
placeholder="your age"
/>
<br />
<button>
id="form-submit"
type="button"
onClick={onSubmit}
disabled={!fileName}
>
Submit Form
</button>
</Form>
);
};
export default function App() {
return (
<div className="App">
<Uploady
clearPendingOnAdd
destination={{ url: "[upload-url]" }}
multiple={false}
>
<MyForm />
</Uploady>
</div>
);
}
You can check out this sandbox for a complete example.
Related
I have two components inside contact page. One is contact form and another is FormData where I'm showing what the user is typing in forms field. I also have a context where I want to store the form data. But in formik there are built-in props, from there I can access the values but from inside the form. But I want to pass the values outside the form to the context so that I can access this data from the FormData component.
Contact Form
import React, { useContext, useEffect } from "react";
import { useFormik } from "formik";
import { Formik } from "formik";
import { ContentContext } from "../Context";
import { FormData } from "./index";
const ContactForm = () => {
const [content, setContent] = useContext(ContentContext);
// useEffect(() => {
// setContent({
// ...content,
// contactFormData: props,
// });
// }, [props]);
// console.log(content);
return (
<Formik
initialValues={{ email: "" }}
onSubmit={async (values) => {
await new Promise((resolve) => setTimeout(resolve, 500));
alert(JSON.stringify(values, null, 2));
}}
>
{(props) => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
// setContent({
// ...content,
// contactFormData: props,
// });
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email" style={{ display: "block" }}>
Email
</label>
<input
id="email"
placeholder="Enter your email"
type="text"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.email && touched.email
? "text-input error"
: "text-input"
}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
<button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</button>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
{/* <FormData props={props} />; */}
</form>
);
}}
</Formik>
);
};
export default ContactForm;
context
import React, { useState, createContext } from "react";
export const ContentContext = createContext();
export const ContentProvider = ({ children }) => {
const [content, setContent] = useState({
contactFormData: {
email: "",
},
});
return (
<ContentContext.Provider value={[content, setContent]}>
{children}
</ContentContext.Provider>
);
};
setting the context inside the form causes infinite loop. How do I save props to context?
I'm working on a SignUp Form, I'm trying to fetch the data entered by the user but for some reason I'm getting just null values, like the next example:
So, for the task I'm using two components, Signup and Input, the bellow code is from Signup.js:
import React, { useContext, useState, useRef } from "react";
import Modal from "../UI/Modal";
import classes from "./Login.module.css";
import Input from "../UI/Input/Input";
const Signup = (props) => {
const firstnameInputRef = useRef();
const lastnameInputRef = useRef();
const emailInputRef = useRef();
const passwordInputRef = useRef();
const [isCanceling, setIsCanceling] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [didSave, setDidSave] = useState(false);
const [isErrorOnSave, setIsErrorOnSave] = useState(false);
//const cartCtx = useContext(CartContext);
const errorOnSignupHandler = () => {
setIsErrorOnSave(true);
};
const signupHandler = async (clientData) => {
setIsSaving(true);
const enteredFirstname = firstnameInputRef.current.value;
const enteredLastname = lastnameInputRef.current.value;
const enteredEmail = emailInputRef.current.value;
const enteredPassword = passwordInputRef.current.value;
const newClientData = {
firstname: enteredFirstname,
lastname: enteredLastname,
email: enteredEmail,
password: enteredPassword,
};
console.log(newClientData);
const response = await fetch("http://localhost:3000/clients", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newClientData),
});
if (!response.ok) {
errorOnSignupHandler();
} else {
setIsSaving(false);
setIsCanceling(false);
setDidSave(true);
//cartCtx.clearCart();
}
};
const isSavingModalContent = <p>Saving new user...</p>;
/* incluir transaccion para verificar si es exitoso o hubo algun error */
const errorOnSavingModalContent = (
<React.Fragment>
<p>The user account could not be created. Please try again later</p>
<div className={classes.actions}>
<button className={classes.button} onClick={props.onClose}>
Close
</button>
</div>
</React.Fragment>
);
const didSaveModalContent = (
<React.Fragment>
<p>User account created, welcome!</p>
<div className={classes.actions}>
<button className={classes.button} onClick={props.onClose}>
Close
</button>
</div>
</React.Fragment>
);
const SignupButtons = (
<React.Fragment>
<button className={classes["button--alt"]} onClick={signupHandler}>
Sign-Up
</button>
<button className={classes["button--alt"]} onClick={props.onClose}>
Close
</button>
</React.Fragment>
);
const modalActions = (
<div className={classes.actions}>{!isCanceling ? SignupButtons : ""}</div>
);
const SignupModalContent = (
<React.Fragment>
<Input
ref={firstnameInputRef}
id="firstname"
label="First Name"
type="text"
//isValid={emailIsValid}
//value={emailState.value}
//onChange={emailChangeHandler}
//onBlur={validateEmailHandler}
/>
<Input
ref={lastnameInputRef}
id="lastname"
label="Last Name"
type="text"
//isValid={emailIsValid}
//value={emailState.value}
//onChange={emailChangeHandler}
//onBlur={validateEmailHandler}
/>
<Input
ref={emailInputRef}
id="email"
label="E-Mail"
type="email"
autodata="off"
//isValid={emailIsValid}
//value={emailState.value}
//onChange={emailChangeHandler}
//onBlur={validateEmailHandler}
/>
<Input
ref={passwordInputRef}
id="paswword"
label="Password"
type="password"
autodata="new-password"
//isValid={passwordIsValid}
//value={passwordState.value}
//onChange={passwordChangeHandler}
//onBlur={validatePasswordHandler}
/>
<Input
//ref={passwordInputRef}
id="paswword2"
label="Confirm-Password"
type="password"
autodata="new-password"
//isValid={passwordIsValid}
//value={passwordState.value}
//onChange={passwordChangeHandler}
//onBlur={validatePasswordHandler}
/>
{modalActions}
</React.Fragment>
);
return (
<Modal onClose={props.onClose}>
{!isCanceling && !isSaving && !isErrorOnSave && !didSave && SignupModalContent}
{isSaving && isSavingModalContent}
{isErrorOnSave && errorOnSavingModalContent}
{!isSaving && didSave && didSaveModalContent}
</Modal>
);
};
export default Signup;
And this is the code from Input.js:
import React from 'react';
import classes from './Input.module.css';
const Input = React.forwardRef((props, ref) => {
return(
<div className={classes.input}>
<label htmlFor={props.input.id}>{props.label}</label>
<input ref={ref} {...props.input}/>
</div>
);
});
export default Input;
well, basically my question is what am I missing to the get the input data from the user?
Thanks a lot for your comments.
You generally shouldn't use refs the way you're using them.
Instead, have a state for each input, and have the input value equal to the state. When the input changes, update the state.
This way, the state always reflects the state of the form, and is synced up with it. It's a patter called "controlled components". See here for more details.
At a cursory look (without creating a sample app), it seems Input.js has an issue.
I have updated your component below:
import React from 'react';
import classes from './Input.module.css';
const Input = React.forwardRef((props, ref) => {
return(
<div className={classes.input}>
<label htmlFor={props.id}>{props.label}</label>
// It shouldn't be props.input
<input ref={ref} {...props}/>
</div>
);
});
export default Input;
I hope this fixes your issue, otherwise it would be a nice if you could create a sample app in codesandbox or another shareable place.
I'm confused as I have managed to get my data to be logged via different means, but confused as to why when I use props for the data (rather than repeating code) it will not log the input.
For reference, I have a field component that will take props to drive what my react-hook-form TextField will request. I'd like to expand on the component but until it logs my data, I cannot proceed!
Below is the code that actually logs my data:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField, Button } from "#material-ui/core/";
const NewRequest = () => {
const { register, handleSubmit, control } = useForm();
const onSubmit = (data) => console.log(data);
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name='firstName'
render={({ field: { onChange, onBlur, value, name, ref } }) => (
<TextField
label='First Name'
variant='filled'
size='small'
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
<br />
<br />
<Button type='submit' variant='contained'>
Submit
</Button>
</form>
</div>
);
};
export default NewRequest;
I have then moved the Controller, TextField to create a component:
import React from "react";
import { Controller, useForm } from "react-hook-form";
import { TextField } from "#material-ui/core/";
const TextFieldComponent = (props) => {
const { name, label, size, variant } = props;
const { control } = useForm();
return (
<div>
<Controller
control={control}
name={name}
render={({ field: { onChange, onBlur, value, ref } }) => (
<TextField
label={label}
variant={variant}
size={size}
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
</div>
);
};
export default TextFieldComponent;
Which I am using inside of another component (to generate a full form) and passing through my props (I will make a different component for Button, but for now it is where it is):
import React from "react";
import { useForm, Controller } from "react-hook-form";
import TextFieldComponent from "./form-components/text-field";
import { Button } from "#material-ui/core/";
const NewRequest= () => {
return (
<div>
<TextFieldComponent
name='firstName'
label='First Name'
size='small'
variant='filled'
/>
<br />
<br />
<Button type='submit' variant='contained'>
Submit
</Button>
</div>
);
};
export default NewRequest;
Now pushing that component into an index.js file to render a form:
import React from "react";
import NewVendorForm from "../components/new-vendor-request";
import { useForm } from "react-hook-form";
const Home = () => {
const { handleSubmit } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<NewVendorForm />
</form>
);
};
export default Home;
I'm stumped as to why this way would
a) customise my TextField in my form as intended
b) but not log my data as requested
I'm sure there is a very valid, basic reason as to why and it is my lack of understanding of console logging, but am in need of help to resolve!
Many thanks in advance.
The issue is that, in the refactored code, you're calling useForm twice, each of which generates a different control and data. You probably want to call useForm at the top level only, and pass in whatever you need (in particular control) to the form fields.
const Home = () => {
const { handleSubmit, control } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<NewVendorForm control={control} />
</form>
);
};
const NewRequest= ({control}) => {
return (
<div>
<TextFieldComponent
name='firstName'
label='First Name'
size='small'
variant='filled'
control={control}
/>
<br />
<br />
<Button type='submit' variant='contained'>
Submit
</Button>
</div>
);
};
const TextFieldComponent = (props) => {
const { name, label, size, variant, control } = props;
return (
<div>
<Controller
control={control}
name={name}
render={({ field: { onChange, onBlur, value, ref } }) => (
<TextField
label={label}
variant={variant}
size={size}
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
</div>
);
};
i'd like to simplify form creation avoiding to write for each fied value={} and onChange={} using a custom hook.
this is my code:
https://codesandbox.io/s/busy-noether-pql8x?file=/src/App.js
the problem is that, each time i press the button, the state is cleaned except for the field i've currently edited
import React, { useState } from "react";
import "./styles.css";
const useFormField = (initialValue) => {
const [values, setValues] = React.useState(initialValue);
const onChange = React.useCallback((e) => {
const name = e.target.name;
const value = e.target.value;
setValues( (prevValues) => ({...prevValues,[name]:value}));
}, []);
return { values, onChange };
};
export default function App() {
//hoot to simplify the code,
const {values,onChange} = useFormField({
Salary: "",
Email: ""
})
const onCreateUser = () => {
console.log(values);
};
return (
<div className="App">
<form>
<label htmlFor="Email">Email: </label>
<input name="Email" onChange={onChange} />
<label htmlFor="Email">Salary: </label>
<input name="Salary" onChange={onChange} />
<button type="button" onClick={onCreateUser}>
Send
</button>
</form>
</div>
);
}
Here my App.js code, I am trying to bind and capture the "handlesubmit" function, and then append to an empty list which will be populated. Thanks.
import React from 'react';
const App = () => {
const [songs, setSongs] = React.useState([]);
React.useEffect(() => {
const data = localStorage.getItem('songs');
if (!data) { }
setSongs(JSON.parse(data));
}, []);
React.useEffect(() => {
localStorage.setItem('songs', JSON.stringify(songs));
});
const handleSubmit = data => {
setSongs([data]);
}
return (
<main>
<h1>Music Editor</h1>
<form onSubmit={this.props.handleSubmit(this.handleSubmit.bind(this))} autoComplete="false">
<label for="title">Title:</label>
<input type="text" id="title" name="title" placeholder="Type title/name of song" value="" />
<input type="submit" value="Add song" />
</form>
</main>
);
}
export default App;
The explanation is commented in the code itself.
Here is the codesandbox link to see the App working.
import React from 'react';
const App = () => {
const [songs, setSongs] = React.useState([]);
// use another state for song title
const [songTitle, setSongTitle] = React.useState('');
React.useEffect(() => {
const data = localStorage.getItem('songs');
// only update the state when the data persists
if (data) setSongs(JSON.parse(data));
}, []);
// update the localStorage whenever the songs array changes
React.useEffect(() => {
localStorage.setItem('songs', JSON.stringify(songs));
}, [songs]);
// inside the functional component, there is no "this" keyword
const handleSubmit = (event) => {
event.preventDefault();
// append the new song title with the old one
setSongs([
...songs,
songTitle
]);
}
return (
<main>
<h1>Music Editor</h1>
<form onSubmit={handleSubmit} autoComplete="false">
<label htmlFor="title">Title:</label>
<input
type="text"
id="title"
name="title"
placeholder="Type title/name of song"
value={songTitle}
onChange={e => setSongTitle(e.target.value)}
/>
<input type="submit" value="Add song" />
</form>
</main>
);
}
export default App;