file upload using react-step-builder form with multiple pages - reactjs

i am using react hook form to create a from with multiple pages
i am able to create it and it working with all filed except file-input-type how do i pass i file from another page and finaly pass it to api in the final page
i a have actualy 3 pages i have only added the 1st and final page (fist page has the file input filed and final page has the api to which it must be submitted)
form with file upload field
import { useForm } from "react-hook-form";
export default function Form(props) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
<input style={styles.file} type="file" />
</div>
<input {...register("name", { required: true })}
name="husband"
value={props.getState("name")}
onChange={props.handleChange}
style={styles.input}
type="text"
placeholder="Name"
/>
<input onClick={handleSubmit(props.next)}
type="submit"
value="Next"
/>
form with submit button and api to which it must be uploaded
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const submitValue = (e) => {
// e.preventDefault();
props.state.patient = "true";
const data = props.state;
axios
.post("registration/", data)
.then(() => {
alert("updated data");
window.location = "/clogin";
})
.catch((error) => {
//var my_obj_str = JSON.stringify(error.response.data);
alert(JSON.stringify(error.response.data));
});
};
codesandbox
https://codesandbox.io/s/wizardly-worker-zicnr?file=/src/App.js

There are 2 options
Single form wraps all the steps
You can wrap the <Steps /> component with one form. Make the <Step />s components stateless that accepts onInputChange which will called upon input changes.
onInputChange call setValue to update the form's state.
When the form submitted, you have the file (among other inputs) so you can send it to the server.
import { useEffect } from "react";
import { Steps, StepsProvider, useSteps } from "react-step-builder";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, setValue } = useForm();
useEffect(() => {
register("myFile");
}, [register]);
const onInputChange = (e) => {
setValue(e.target.name, e.target.files[0]);
};
const onSubmit = (data) => {
alert(`Your file name: ${data.myFile.name}, size: ${data.myFile.size}`);
};
return (
<StepsProvider>
<form onSubmit={handleSubmit(onSubmit)}>
<MySteps onInputChange={onInputChange} />
</form>
</StepsProvider>
);
}
const MySteps = ({ onInputChange }) => {
const { next, prev } = useSteps();
return (
<Steps>
<div>
<h1>Step 1</h1>
<input type="file" name="myFile" onChange={onInputChange} />
<button onClick={next}>Next</button>
</div>
<div>
<h1>Step 2</h1>
<button>Submit</button>
</div>
</Steps>
);
};
https://codesandbox.io/s/gifted-wozniak-of14l?file=/src/App.js
Multiple forms in each step
If you want need to have a form inside each step, you can pass the step's data up to the parent when upon step's form submission. Still the parent has the form state so it can handle when all the steps completed
import { useRef } from "react";
import { Steps, StepsProvider, useSteps } from "react-step-builder";
import { useForm } from "react-hook-form";
export default function App() {
const formState = useRef();
const onStepComplete = (data) => {
formState.current = {
...formState.current,
...data
};
};
const onComplete = (data) => {
onStepComplete(data);
const {
name,
myFile: [file]
} = formState.current;
alert(
`Your name: ${name} Your file name: ${file.name}, size: ${file.size}`
);
};
return (
<StepsProvider>
<MySteps onStepComplete={onStepComplete} onComplete={onComplete} />
</StepsProvider>
);
}
const MySteps = ({ onStepComplete, onComplete }) => {
return (
<Steps>
<Step1 onStepComplete={onStepComplete} />
<Step2 onComplete={onComplete} />
</Steps>
);
};
const Step1 = ({ onStepComplete }) => {
const { register, handleSubmit } = useForm();
const { next } = useSteps();
const onSubmit = (data) => {
onStepComplete(data);
next();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>Step 1</h1>
<input type="file" {...register("myFile")} />
<button>Next</button>
</form>
);
};
const Step2 = ({ onComplete }) => {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => {
onComplete(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>Step 2</h1>
<input type="text" {...register("name")} />
<button>Submit</button>
</form>
);
};
https://codesandbox.io/s/condescending-leaf-6gzoj?file=/src/App.js

Passing down the function and tracking changes at parent level is not a great idea. react-hook-form provides a form context option which allows you to do this independently. Such that errors, onChange are handled in each step separately. But when you need to submit the data you can get all of those in the parent component.
Refer to this documentation: https://react-hook-form.com/api/useformcontext/
Note: Many people make the mistake of placing the FormProvider inside the parent component. Remember that FormProvider should wrap the Parent component as well.

Related

refactor debounce function to useDeferredValue hook (React)

In my application I use Redux to manage its state.
The task is when user types query in search panel application dispatches an action with payload and then request goes to API. I want to delay dispatch of an action, so when user types query the request only sends after user stops typing.
I implemented it with debounce function but kinda want to refactor in with useDeferredValue.
And that's when I found it difficult to implement this functional.
import { useState, useMemo } from 'react';
import { FormRow, FormRowSelect } from '.';
import Wrapper from '../assets/wrappers/SearchContainer';
import { useSelector, useDispatch } from 'react-redux';
import { handleChange, clearFilters } from '../features/allJobs/allJobsSlice';
export default function SearchContainer() {
const { isLoading, search, searchStatus, searchType, sort, sortOptions } =
useSelector((store) => store.allJobs);
const { jobTypeOptions, statusOptions } = useSelector((store) => store.job);
const dispatch = useDispatch();
const [localSearch, setLocalSearch] = useState('');
function onHandleSearch(e) {
dispatch(handleChange({ name: e.target.name, value: e.target.value }));
}
function omHandleSubmit(e) {
e.preventDefault();
dispatch(clearFilters());
}
const debounce = () => {
let timeoutID;
return (e) => {
setLocalSearch(e.target.value);
clearTimeout(timeoutID);
timeoutID = setTimeout(() => {
dispatch(handleChange({ name: e.target.name, value: e.target.value }));
}, 1000);
};
};
const optimizedDebounce = useMemo(() => debounce(), []);
return (
<Wrapper>
<form className='form'>
<h4>search form</h4>
<div className='form-center'>
<FormRow
type='text'
name='search'
value={localSearch}
handleChange={optimizedDebounce}
/>
<FormRowSelect
labelText='status'
name='searchStatus'
value={searchStatus}
handleChange={onHandleSearch}
list={['all', ...statusOptions]}
/>
<FormRowSelect
labelText='type'
name='searchType'
value={searchType}
handleChange={onHandleSearch}
list={['all', ...jobTypeOptions]}
/>
<FormRowSelect
name='sort'
value={sort}
handleChange={onHandleSearch}
list={sortOptions}
/>
<button
className='btn btn-block btn-danger'
disabled={isLoading}
onClick={omHandleSubmit}
>
clear filters
</button>
</div>
</form>
</Wrapper>
);
}
From the react website this is how it is done:
function App() {
const [text, setText] = useState("hello");
const deferredText = useDeferredValue(text, { timeoutMs: 2000 });
return (
<div className="App">
{/* Continue to give the current text to the input */}
<input value={text} onChange={handleChange} />
...
{/* But the list of results is allowed to be "late" in case it is not load yet */}
<MySlowList text={deferredText} />
</div>
);
}
so in your case this might be this:
import { useDeferredValue } from 'react';
export default function SearchContainer() {
const [localSearch, setLocalSearch] = useState('');
const deferredValue = useDeferredValue(localSearch, { timeoutMs: 1000 });
...
return (
...
<FormRow
type='text'
name='search'
value={localSearch}
handleChange={e => setLocalSearch(e.target.value)}
/>
);
}

How to create a label and input dynamically using ReactJS

I'm working on creating a dynamic input form, where I want to click on a button and get a pop-up asking for label name and input type(Eg: number or text). Here is a mock-up of what I want to create. I should be able to even remove these newly created label and input.
Once this is entered, it should create a new label and input form as below:
Any help will be greatly appreciated.
Looks like I'm doing someone else's work but...)))
A quick example so you know which way to go:
YourMainComponent.tsx:
import React, { useState } from "react";
import { DynamicForm } from "./dynamic-form";
export const Fields = () => {
const [getFields, setFields] = useState([]);
const addField = (field) => {
setFields((prevState) => [...prevState, field]);
};
return (
<>
<DynamicForm onSubmit={addField} />
{getFields &&
getFields.map((field, index) => (
<fieldset key={index}>
<label>{field.label}</label>
<input type={field.type} />
</fieldset>
))}
</>
);
};
YourDynamicFieldCreateComponent:
import React, { useState } from "react";
export const DynamicForm = ({ onSubmit }) => {
const [getField, setField] = useState({});
const formSubmit = (e) => {
e.preventDefault();
if (Object.keys(getField).length > 0) {
onSubmit(getField);
setField({});
}
};
const onFieldChanged = (e) => {
if (e.target.id === "label-field") {
setField((prevState) => ({
...prevState,
label: e.target.value
}));
} else if (e.target.id === "type-field") {
setField((prevState) => ({
...prevState,
type: e.target.value
}));
}
};
return (
<form onSubmit={formSubmit}>
<fieldset>
<label htmlFor="label-field">Your label </label>
<input
type="text"
id="label-field"
name="label-field"
onChange={onFieldChanged}
/>
</fieldset>
<fieldset>
<label htmlFor="type-field">Your type of field </label>
<input
type="text"
id="type-field"
name="type-field"
onChange={onFieldChanged}
/>
</fieldset>
<button>Add more</button>
</form>
);
};
Need add conditions and modals and other...
This is not production code, use this only for learning

An object from arrays of objects - React - useState

help please I want to add value to state without overwriting. Currently, when adding a value, the array is overwritten. I want to use useState and I want to use the value from the form.
import {useState} from 'react';
const initialState = {
people: [
{email: 'Jan'},
{email: 'Izabela'},
{email: 'Michael'}
] }
const StateModification = () => {
const [names,setNames] = useState(initialState)
const handleSubmit = (e) => {
e.preventDefault();
}
const handleChange2 = (e) => {
setNames({
...names,
people: [{[e.target.name]: e.target.value}]
})
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>E-mail</label>
<input
id='email'
type='text'
name='email'
value={names.email}
onChange={handleChange2}
/>
<button type='submit'>Add</button>
</form>
</div>`enter code here`
) }
export default StateModification;
I think you need to add an email in your data and after click on add button that email will store in people variable with your previous data so i have update your code and it should work for you.
import {useState} from 'react';
const initialState = {
people: [
{email: 'Jan'},
{email: 'Izabela'},
{email: 'Michael'}
] }
const StateModification = () => {
const [names,setNames] = useState(initialState)
const [email,setEmail] = useState("")
const handleSubmit = (e) => {
e.preventDefault();
setNames({
people: [...names.people, { email }]
})
}
const handleChange2 = (e) => {
e.preventDefault();
setEmail(e.target.value)
}
return (
<div>
<form onSubmit={handleSubmit}>
<label>E-mail</label>
<input
id='email'
type='text'
name='email'
value={email}
onChange={handleChange2}
/>
<button type='submit'>Add</button>
</form>
</div>
) }
export default StateModification;

React - Form keeps re-rendering component while I type

This function renders a modal that shows a login form. As I type, the whole component is re-rendering, and I can only type 1 or 2 letters before it resets. I've never seen this kind of problem before.
I've tried factoring out the "opts" object, in case that was forcing the re-render. It did not change anything.
import React, {useState} from 'react';
import useApi, {IPayload} from './hooks/useApi';
import Modal from 'react-modal'
import './modal.css';
Modal.setAppElement('#root')
interface IProps {
payload:IPayload|null,
closeAction?:()=>void,
}
const defaultProps:IProps = {
payload:null,
closeAction: ()=>null,
}
function Auth(props:IProps):JSX.Element{
const [auth, updateAuth] = useState<boolean>(false)
const [authModalIsOpen, setAuthModal] = useState(true)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const opts = {
username: username,
password: password,
fail: forceLogin,
}
const defaultPayload:IPayload = {
path:'notes/validateAuth/',
method: 'GET',
body: null,
callback: loginAction
}
const [payload, setPayload] = useState(defaultPayload)
if(props.payload!==null){
setPayload(props.payload)
}
// console.log(_payload)
// useApi(opts, payload)
function forceLogin(){
setAuthModal(true)
updateAuth(false)
}
function closeAuthModal(){
if(auth){ setAuthModal(false) }
}
function SubmitLogin(){
setPayload(defaultPayload)
}
function loginAction(res:any){
updateAuth(true)
setAuthModal(false)
setUsername(res.auth)
}
function LoginWindow(){
return(
<Modal
isOpen={authModalIsOpen}
onRequestClose={closeAuthModal}
className='SearchModal'
overlayClassName='SearchOverlay'
>
<div className="IoLinks_wrapper">
<div className="IoLinks_incoming">
<form onSubmit={(e)=>{
e.preventDefault()
SubmitLogin()
}}>
<input name='username' className='greenput_narrow' onChange={(e)=>{
setUsername(e.target.value)
}}/><br />
<input type="password" name='password' className='greenput_narrow' onChange={(e)=>{
setPassword(e.target.value)
}}/><br />
<button className='greenput_narrow' type="submit">Login</button>
</form>
</div>
<div className="IoLinks_outgoing"><h1>NullDisk</h1><ul><li>Military Grade Encryption</li><li>Zettelkasten Schema</li><li>VIM Keybindings</li></ul></div>
</div>
</Modal>
)
}
return(
<LoginWindow/>
)
}
Auth.defaultProps = defaultProps
export default Auth;
How do I fix this?
Your function LoginWindow() is another component inside function Auth(props:IProps). All your setStates happens in your parent component as you type. In React any change in the Parent component will trigger a re-render of child components.
If you really wanted to keep a separate child component, you had to pass all those set methods you are using in LoginWindow as props. Then you will have to wrap your methods that are passed into your child component in a useCallback so every time your parent re-renders due to the state update your functions will not regenerate as new objects. That's a path you don't need for such a small component I believe.
I don't know why you are using a component inside another component in the wrong way. Why don't you directly return your JSX like,
import React, { useState } from 'react';
import useApi, { IPayload } from './hooks/useApi';
import Modal from 'react-modal';
import './modal.css';
Modal.setAppElement('#root');
interface IProps {
payload: IPayload | null;
closeAction?: () => void;
}
const defaultProps: IProps = {
payload: null,
closeAction: () => null
};
function Auth(props: IProps): JSX.Element {
const [auth, updateAuth] = useState < boolean > false;
const [authModalIsOpen, setAuthModal] = useState(true);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const opts = {
username: username,
password: password,
fail: forceLogin
};
const defaultPayload: IPayload = {
path: 'notes/validateAuth/',
method: 'GET',
body: null,
callback: loginAction
};
const [payload, setPayload] = useState(defaultPayload);
if (props.payload !== null) {
setPayload(props.payload);
}
// console.log(_payload)
// useApi(opts, payload)
function forceLogin() {
setAuthModal(true);
updateAuth(false);
}
function closeAuthModal() {
if (auth) {
setAuthModal(false);
}
}
function SubmitLogin() {
setPayload(defaultPayload);
}
function loginAction(res: any) {
updateAuth(true);
setAuthModal(false);
setUsername(res.auth);
}
return (
<Modal
isOpen={authModalIsOpen}
onRequestClose={closeAuthModal}
className="SearchModal"
overlayClassName="SearchOverlay"
>
<div className="IoLinks_wrapper">
<div className="IoLinks_incoming">
<form
onSubmit={e => {
e.preventDefault();
SubmitLogin();
}}
>
<input
name="username"
className="greenput_narrow"
onChange={e => {
setUsername(e.target.value);
}}
/>
<br />
<input
type="password"
name="password"
className="greenput_narrow"
onChange={e => {
setPassword(e.target.value);
}}
/>
<br />
<button className="greenput_narrow" type="submit">
Login
</button>
</form>
</div>
<div className="IoLinks_outgoing">
<h1>NullDisk</h1>
<ul>
<li>Military Grade Encryption</li>
<li>Zettelkasten Schema</li>
<li>VIM Keybindings</li>
</ul>
</div>
</div>
</Modal>
);
}
Auth.defaultProps = defaultProps;
export default Auth;
Had a similar issue here: in the input that you want to focus on put autofocus:
<input style={{width: "50%"}}maxLength="8" autoFocus value={inputValue} onChange={customTextChange.bind(this)} className={styles.patternSelector} />
You many want to conditionally add it based on an onChange event

How do I edit form data in a React function component?

I'm trying to set a form field value with useState.
The settings.values.apiKey variable has a value, but the textarea element is empty. What's wrong with my useState?
I tried to change value={apiKey} to value={settings.values.apiKey} and then the value is displayed, but then I can't change the value of the field. When I try to enter something, it always shows the original value.
App.js
const App = () => {
const [apiKey, setApiKey] = useState(settings.values.apiKey)
useEffect(() => {
const getSettings = async () => {
const settingsFromServer = await fetchSettings()
setSettings(settingsFromServer)
}
getSettings()
}, [])
const fetchSettings = async () => {
const res = await fetch('http://127.0.0.1/react-server/get.php')
return await res.json()
}
const saveSettings = async (settings) => {
}
return (
<div className="container">
<Header />
<Settings
settings={settings}
saveSettings={saveSettings}
/>
<Footer />
</div>
);
}
export default App;
Settings.js:
import { useState } from 'react';
const Settings = ({ settings, saveSettings }) => {
const [apiKey, setApiKey] = useState(settings.values.apiKey)
const onSubmit = (e) => {
e.preventDefault()
saveSettings({ apiKey})
}
return (
<div>
<form className='add-form' onSubmit={onSubmit}>
<div className='form-control'>
<label>Api key</label>
<textarea
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
</div>
<input type='submit' value='Save settings' className='mt15' />
</form>
</div>
)
}
export default Settings
It looks like by mistake you have used apiKey in App.js file as your state variable. It should be replaced by settings.
const [settings, setSettings] = React.useState();
The above code would make value={apiKey} work properly for textarea in Settings.js file.
And, then onChange will also start working properly.
UPDATE
In addition to the above mentioned error, in case settings props is undefined in Settings.js, this might cause your code to break at useState. So, instead put a check for settings values in useEffect and then set the value. The code would look like this or you can check the codesandbox link here for working demo.
Settings.js
import { useEffect, useState } from "react";
const Settings = ({ settings, saveSettings }) => {
const [apiKey, setApiKey] = useState();
useEffect(() => {
if (settings?.values?.apiKey) {
setApiKey(settings.values.apiKey);
}
}, [settings]);
const onSubmit = (e) => {
e.preventDefault();
saveSettings({ apiKey });
};
return (
<div>
<form className="add-form" onSubmit={onSubmit}>
<div className="form-control">
<label>Api key</label>
<textarea
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
</div>
<input type="submit" value="Save settings" className="mt15" />
</form>
</div>
);
};
export default Settings;
App.js
const [settings, setSettings] = useState()
const saveSettings = async (settings) => {
setSettings(settings);
}

Resources