custom hook to simplify react controlled form - reactjs

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>
);
}

Related

Onchange in input field is not working while editing a form

I am developing a small application in react, in which I have an edit option. On clicking the edit button, it will load the existing data and allows the user to edit any of the fields and submit.
Fetching the data and loading it in a form are working fine, but when I edit a textbox, the value changes to the existing fetched value, and it is not allowing me to hold the edited value.
Please note, the problem is with editing the input in a form not in submitting. Below is the edit component that I am using.
mport { useState, useEffect } from 'react';
import { json, Link } from 'react-router-dom';
import { useParams } from 'react-router-dom';
const EditTask = ({ onEdit }) => {
const [text, setText] = useState('');
const [day, setDay] = useState('');
const [reminder, setReminder] = useState(false);
const params = useParams();
useEffect(() => {
fetchTask();
});
const fetchTask = async () => {
const res = await fetch(`http://localhost:5000/tasks/${params.id}`);
const data = await res.json();
setText(data.text);
setDay(data.day);
setReminder(data.reminder);
};
const onSubmit = async (e) => {
e.preventdefault();
if (!text) {
alert('Please enter task name');
return;
}
onEdit({ text, day, reminder });
setText('');
setDay('');
setReminder(false);
};
const handleChange = ({ target }) => {
console.log(target.value); // displaying the input value
setText(target.value); // changes to existing value not the one I entered
};
return (
<form className="add-form" onSubmit={onSubmit}>
<div className="form-control">
<label>Task</label>
<input
id="AddTask"
type="text"
placeholder="Add Task"
value={text}
onChange={handleChange}
/>
</div>
<div className="form-control">
<label>Date & Time</label>
<input
id="Date"
type="text"
placeholder="Date & Time"
value={day}
onChange={(e) => setDay(e.target.value)}
/>
</div>
<div className="form-control form-control-check">
<label>Set Reminder</label>
<input
id="Reminder"
type="checkbox"
checked={reminder}
value={reminder}
onChange={(e) => setReminder(e.currentTarget.checked)}
/>
</div>
<input className="btn btn-block" type="submit" value="Save Task" />
<Link to="/">Home</Link>
</form>
);
};
export default EditTask;
Can someone explain what I am missing here? Happy to share other information if needed.
Expecting the input fields to get the value entered and submitting.
You missed adding dependency to useEffect
Yours
useEffect(() => {
fetchTask()
}
)
Should be changed
useEffect(()=>{
fetchTask()
}, [])
becasue of this, fetchTask is occured when view is re-rendered.

How to do validation using useRef()

How do I validate input box value using useRef .
Initial validation is not required once user clicks on input box and comes out then it should validate if input box is empty it should show input box cannot be empty.
Codesandbox Link
code i tried. using onBlur
export default function App() {
const name = React.useRef("");
const nameBlurData = (name) => {
console.log("name", name);
};
return (
<div className="App">
<form>
<input
onBlur={() => nameBlurData(name.current.value)}
type="text"
ref={name}
placeholder="Enter First Name"
/>
// show error message here
</form>
</div>
);
}
You can use "useRef" to validate the value of an input field.
No need to use "useState".
Below code is a basic implementation of OP's question
You can replace the "console.log" with your alert component.
import { useRef } from "react";
const ComponentA = () => {
const emailRef = useRef(null);
const passwordRef = useRef(null);
const onBlurHandler = (refInput) => {
if (refInput.current?.value === "") {
console.log(`${refInput.current.name} is empty!`);
}
}
return (
<form>
<input ref={emailRef} onBlur={onBlurHandler.bind(this, emailRef)} />
<input ref={passwordRef} onBlur={onBlurHandler.bind(this, passwordRef)} />
<form/>
)
}
Link to "useRef"
Note: Not tested, code typed directly to SO's RTE
You can use a local state and conditionally render an error message like this:
const [isValid, setIsValid] = useState(true)
const nameBlurData = (name) => {
setIsValid(!!name);
};
return (
<div className="App">
<form>
<input
onBlur={() => nameBlurData(name.current.value)}
type="text"
ref={name}
placeholder="Enter First Name"
/>
{!isValid && <span> input must not be empty </span> }
</form>
Note that you don't really need a ref in this case, you can just use the event object like:
onBlur={(event) => nameBlurData(event.target.value)}
You need to use useState hook to update the value of the name property. Using ref is not ideal here.
Live demo https://stackblitz.com/edit/react-apqj86?devtoolsheight=33&file=src/App.js
import React, { useState } from 'react';
export default function App() {
const [name, setName] = useState('');
const [hasError, setError] = useState(false);
const nameBlurData = () => {
if (name.trim() === '') {
setError(true);
return;
}
setError(false);
};
return (
<div className="App">
<form>
<input
onBlur={nameBlurData}
type="text"
value={name}
onChange={e => setName(e.target.value)}
placeholder="Enter First Name"
/>
{hasError ? <p style={{ color: 'red' }}>Name is required</p> : null}
</form>
</div>
);
}

How to add field to upload image by Redux Form?

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.

Trying to bind submit and save to localStorage

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;

React TypeScript: Alternatives to UseRef in functional components

Is there another way of getting/setting the values from the dom that is less expensive than useRef()? Is useRef() to be used lightly as the docs suggest?
import React, { useRef, useEffect } from 'react';
const Join: React.FC = () => {
const fullName = useRef<HTMLInputElement>(null);
const email = useRef<HTMLInputElement>(null);
const password = useRef<HTMLInputElement>(null);
const myForm = useRef<HTMLFormElement>(null);
useEffect(() => {
if (myForm.current) myForm.current.reset();
if (fullName.current) fullName.current.focus();
}, []);
return (
<div>
<form ref={myForm}>
<input type='text' ref={fullName} />
<input type='text' ref={email} />
<input type='text' ref={password} />
</form>
</div>
)
}
When the component loads I want to clear the form and focus the
fullName input
You don't need refs for that
I want to clear the form
Make your inputs controlled
Declare an empty string as initial value
const Component = () =>{
const [state, setState] = useState({
email : '',
password : ''
})
const onChange = ({ target: { value, name } }) =>{
setState(prev => ({
...prev,
[name] : value
}))
}
const { email, password } = state
return(
<>
<input value={email} onChange={onChange} id='email'/>
<input value={password} onChange={onChange} id='password' />
</>
)
}
Automatically focus a given input
Just use autofocus for that
<input autofocus/>

Resources