How to configure 'value' in the input field in React? - reactjs

In React, I have a textbox with a Submit button that is visible when the user clicks 'Wave At Me' and is not visible after the user clicks 'Submit'. What do I put for value in the input tag? How do I get value should I can use it in the setFormText method? In the component class, value is below, but what is the equivalent for the function code?
<input type="text" value={this.state.value} onChange={this.handleChange} />
My code is below in the default App() function, currently the user is unable to change the text:
const [currentVisibility, setVisibility] = useState(false);
const [currentFormText, setFormText] = useState("");
//wave at me button just shows the form
const wave = async () => {
setVisibility(true);
console.log("form appears")
}
// I don't think it's correct to use changeValue function
const changeValue = async () => {
console.log("formed had some text");
}
const handleChange = async () => {
console.log("form was changed");
}
//the handle submit should read the value from the input field to use in the parameter string of the wave() function.
const handleSubmit = async () => {
try {
setVisibility(false);
const { ethereum } = window;
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);
let count = await wavePortalContract.getTotalWaves();
console.log("Retrieved total wave count...", count.toNumber());
//change to currentText instead of water bottle
const waveTxn = await wavePortalContract.wave("water bottle");
console.log("Mining...", waveTxn.hash);
await waveTxn.wait();
console.log("Mined -- ", waveTxn.hash);
count = await wavePortalContract.getTotalWaves();
console.log("Retrieved total wave count...", count.toNumber());
} else {
console.log("Ethereum object does not exist!");
}
} catch (error) {
console.log(error)
}
}
return {currentVisibility && (
<form onSubmit={handleSubmit}>
<label>
Message:
<input type="text" value="" onChange={handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
)}

The useState hook returns the state value and a setter function. So in your case, currentFormText is the state value and setFormText is the setter function. Thus, your input should read:
<input type="text" value={currentFormText} onChange={handleChange} />
If you do this, you'll notice you can't change the input's value. That's because React is now "controlling" the value of the input; in other words, React is now the "source of truth" for this input (rather than the browser/HTML itself). Because of this, you'll need to add to your handler function:
// we certainly don't need the `async` keyword here!
const handleChange = (event) => {
console.log("form was changed");
setFormText(event.target.value);
}

On form submit refresh the page, you need to put this code e.preventDefault(); in handleSubmitfunction !
When handleChange function call you need to set the input value in currentFormText state that's why you unable to change the text !
Try this code it's work for me
function App() {
const [currentVisibility, setVisibility] = useState(true);
const [currentFormText, setFormText] = useState("");
//wave at me button just shows the form
const wave = async () => {
setVisibility(true);
console.log("form appears")
}
const changeValue = async () => {
console.log("formed had some text");
}
const handleChange = async (value) => {
setFormText(value)
console.log("form was changed");
}
//the handle submit should read the value from the input field to use in the parameter string of the wave() function.
const handleSubmit = async (e) => {
try {
e.preventDefault();
setVisibility(false);
const { ethereum } = window;
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);
let count = await wavePortalContract.getTotalWaves();
console.log("Retrieved total wave count...", count.toNumber());
//change to currentText instead of water bottle
const waveTxn = await wavePortalContract.wave("water bottle");
console.log("Mining...", waveTxn.hash);
await waveTxn.wait();
console.log("Mined -- ", waveTxn.hash);
count = await wavePortalContract.getTotalWaves();
console.log("Retrieved total wave count...", count.toNumber());
} else {
console.log("Ethereum object does not exist!");
}
} catch (error) {
console.log(error)
}
}
return (
currentVisibility &&
<form onSubmit={(e) => handleSubmit(e)}>
<label>
Message:
<input type="text" value={currentFormText} onChange={(e) => handleChange(e.target.value)} />
</label>
<input type="submit" value="Submit" />
</form>
)
}
export default App;

Related

Unexpected behavior with my native react input validation

I created an input field which am trying to validate
const [name, setName] = useState('');
const [formErrors, setFormErrors] = useState({});
<p>Name</p>
<input
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Error> {formErrors.name}</Error>
<Button
onClick={handleSubmit}
>
Submit
</Button>
OnClick of the submit button it checks if the name field is empty in the handleSubmit function
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = 'Name is required!';
}
return errors;
};
const handleSubmit = async (e) => {
const val = {name};
setFormErrors(validate(val));
if (Object.keys(formErrors).length === 0) {
console.log('No empty');
}else{
console.log('Empty');
}
};
The issue am having is that it lags behind in response. For example if the name field is empty it console's Not empty, on first click of the buttton, if I then click the button again it then console's the correct data which is 'Empty'.
This is becasue the state is not set until the component is being re-render therefore the formErrors state is {} until the handle function ends. Create a new constant to hold the errors and use these to console the outcome instead of the state itself if you still need to do something during the event, however use the state inside the JSX to render correctly since state will have been changed by then.
const handleSubmit = async (e)=>{
const val = {name};
const errors = validate(val)
setFormErrors(errors);
if (Object.keys(errors).length === 0) {
console.log('No empty');
}else{
console.log('Empty');
}
}

Is there a way to assign multiple inputs to a state array in react?

I'm trying to have the user input multiple "themes" via a form input bar so that I can add it to the database. The schema model I have created for the object has the "theme" as an array so that part's done. I'm wondering if there's a way to add multiple input values to the same state variable theme in the code down below.
Here is what my code looks like:
import { useState } from "react";
const ProjectAdminForm = () => {
const [sdg, setSDG] = useState('')
const [goal, setGoal] = useState('')
const [orginization, setOrginization] = useState('')
const [source, setSource] = useState('')
const [location, setLocation] = useState('')
const [published, setPublished] = useState('')
const [website_url, setWebsiteURL] = useState('')
const [assignment_type, setAssignmentType] = useState('')
const [theme, setTheme] = useState('')
const [sharepoint_link, setSharepointLink] = useState('')
const [statement, setStatement] = useState('')
const [error, setError] = useState(null)
const handleSubmit = async (e) => {
e.preventDefault() // Prevents refresh of page from happening
console.log('button clicked')
const project = {sdg, goal, orginization, source, location, published, website_url, assignment_type, theme, sharepoint_link, statement}
console.log(project)
// Sending form response to backend
const response = await fetch('/api/projects', {
method: 'POST',
body: JSON.stringify(project),
headers: {
'Content-Type': 'application/json'
}
})
const json = await response.json
// Checking for error
if (!response.ok) {
setError(json.error)
}
if (response.ok) {
// Reset form inputs back to empty string
setSDG('')
setGoal('')
setOrginization('')
setSource('')
setLocation('')
setPublished('')
setWebsiteURL('')
setAssignmentType('')
setTheme('')
setSharepointLink('')
setStatement('')
setError(null)
console.log('new project added', json)
}
}
return (
<form className="create" onSubmit={handleSubmit}>
<h3>Add a New Project</h3>
<label>SDG (Num + Name):</label>
<input
type="text"
placeholder="e.g. SDG 2: Zero Hunger"
onChange={(e) => setSDG(e.target.value)}
value={sdg}
/>
<label>Goal:</label>
<input
type="text"
onChange={(e) => setGoal(e.target.value)}
value={goal}
/>
<label>Orginization:</label>
<input
type="text"
onChange={(e) => setOrginization(e.target.value)}
value={orginization}
/>
<label>Source:</label>
<input
type="text"
onChange={(e) => setSource(e.target.value)}
value={source}
/>
<label>Location:</label>
<input
type="text"
onChange={(e) => setLocation(e.target.value)}
value={location}
/>
<label>Published:</label>
<input
type="text"
onChange={(e) => setPublished(e.target.value)}
value={published}
/>
<label>Website URL:</label>
<input
type="text"
onChange={(e) => setWebsiteURL(e.target.value)}
value={website_url}
/>
<label>Assignment Type:</label>
<input
type="text"
onChange={(e) => setAssignmentType(e.target.value)}
value={assignment_type}
/>
<label>Theme:</label>
<input
type="text"
onChange={(e) => setTheme(e.target.value)}
value={theme}
/>
<label>Sharepoint Link:</label>
<input
type="text"
onChange={(e) => setSharepointLink(e.target.value)}
value={sharepoint_link}
/>
<label>Statement:</label>
<input
type="text"
onChange={(e) => setStatement(e.target.value)}
value={statement}
/>
<button>Add Project</button>
{error && <div className="error">{error}</div>}
</form>
)
}
export default ProjectAdminForm
Specifically for the theme input:
<label>Theme:</label>
<input
type="text"
onChange={(e) => setTheme(e.target.value)}
value={theme}
/>
, how can I have multiple inputs that link to the same theme state which holds them all as an array and then gets passed on to the backend via handleSubmit? Say a user wants to enter "magic", "joy", and "fun" as the themes, how could I make it so that all 3 of those get stored in the theme state variable via the input form?
As array:
//initialise an empty array as the default state for adding themes
const [themes, setThemes] = useState([]);
//create an array to hold your theme names
const available_themes = ['magic', 'joy', 'fun', 'more'];
//create a function for adding themes to your state array
const addTheme = (name) =>{
//if the theme to be added does not exists in the array
if(!themes.includes(name)){
//make a copy (...) of the current themes, and add your new theme name to a new array
setThemes([...themes, name])
}
}
//create a function for removing items from the array
const removeTheme = (name) =>{
//get the index (position) of the theme name in the current state array
const index = themes.indexOf(name);
//if the item exists in the themse array(has an index, is not -1)
if(index !== -1) {
//copy the current array to a tempory data store
let temp_themes = [...themes];
//remove the item from the array using its index
temp_themes.splice(index, 1);
//now save the new array to your state variable of themes
setThemes(temp_themes);
}
}
//for each available theme name create a check box
const themeselector = available_themes.map((name)=>{
//we check if the check box needs to be set to checked, i.e. selected
let checked = false;
//if the current theme is in the theme array it should be selected
if(themes.includes(name)){
checked = true;
}
// we add the checked=checked property {checked: 'checked'} to the check box if the list item should be checked using {...(checked ? {checked: 'checked'}: {})} where (boolean ? on true : else on false )
//we get the ev.target.checked variable to see if the check box has been selected or deselected. We either addTheme, or removeTheme based upon the value of teh variable.
return <div><input type="checkbox" {...(checked ? {checked: 'checked'}: {})} value={name} onClick={(ev)=>{ if(ev.target.checked) { addTheme(name) } else { removeTheme(name)} } } /> {name}</div>;
});
As object:
const [themes, setThemes] = useState({});
const available_themes = ['magic', 'joy', 'fun', 'more'];
const addTheme = (name) =>{
if(typeof themes[name] == 'undefined') {
let temp_themes = {...themes};
temp_themes[name] = name;
setThemes(temp_themes);
}
}
const removeTheme = (name) =>{
if(typeof themes[name] !== 'undefined') {
let temp_themes = {...themes};
delete temp_themes[name];
setThemes(temp_themes);
}
}
const themeselector = available_themes.map((name)=>{
let checked = false;
if(typeof themes[name] !== 'undefined'){
checked = true;
}
return <div><input type="checkbox" {...(checked ? {checked: 'checked'}: {})} value={name} onClick={(ev)=>{ if(ev.target.checked) { addTheme(name) } else { removeTheme(name)} } } /> {name}</div>;
});
<label>Select Themes</label>
{themeselector}
You could do something like this, I guess:
const [theme, setTheme] = useState([''])
And then define a function addTheme that would call setTheme and add a string if it's not already present in the array.
function addTheme(newTheme){
if(!theme.includes(newTheme){
setTheme([...theme, newTheme])
}
}
If you want to use a single text input, one could just create a new array with the values:
const [themes, setThemes] = useState([]);
const [themeText, setThemeText] = useState("");
onSubmit(e) {
e.preventDefault();
setThemes(themeText.split(" "));
}
<label>Theme:</label>
<input
type="text"
onChange={(e) => setThemeText(e.target.value)}
value={themeText}
/>

State not updating after setting state

I'm creating a contact form, and attempting to setState for each field, then pass that value to an email form.
When I console.log, I'm not getting any output. What am I doing wrong here?
Should I be using useEffect? From what I understand, useEffect is called whenever I set state, so I shouldn't need to. Is there something else I'm missing or doing wrong?
import style from "../styles/Contact.module.css";
import React, { useState } from 'react'
export default function Contact() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [subject, setSubject] = useState('')
const [message, setMessage] = useState('')
const [errors, setErrors] = useState({})
const [buttonText, setButtonText] = useState('Submit')
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [showFailureMessage, setShowFailureMessage] = useState(false)
const handleValidation = () => {
let tempErrors = {}
let isValid = true
setName(document.getElementById('name').value)
setEmail(document.getElementById('email').value)
setSubject(document.getElementById('subject').value)
setMessage(document.getElementById('message').value)
console.log(name, email, subject, message)
if (name.length <= 0) {
tempErrors['name'] = true
isValid = false
}
if (email.length) {
tempErrors['email'] = true
isValid = false
}
if (subject.length) {
tempErrors['subject'] = true
isValid = false
}
if (message.length <= 0) {
tempErrors['message'] = true
isValid = false
}
setErrors({ ...tempErrors })
console.log('errors', errors)
return isValid
}
const handleSubmit = async (e) => {
e.preventDefault()
let isValidForm = handleValidation()
if (isValidForm) {
setButtonText('Sending')
const res = await fetch('/api/sendgrid', {
body: JSON.stringify({
email: email,
name: name,
subject: subject,
message: message
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
})
const { error } = await res.json()
if (error) {
console.log(error)
setShowSuccessMessage(false)
setShowFailureMessage('Error, please complete all sections')
setButtonText('Submit')
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
return
}
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
setButtonText('Send')
}
console.log(name, email, subject, message)
}
return (
<div className={style.container}>
<h1 className={style.title}>Get in Touch</h1>
<form className={style.form}>
<input className={style.inputS} type="text" placeholder="Name" id='name' />
<input className={style.inputS} type="text" placeholder="Phone" id='phone' />
<input className={style.inputL} type="text" placeholder="Email" id='email' />
<input className={style.inputL} type="text" placeholder="Subject" id='subject' />
<textarea
className={style.textArea}
type="text"
rows={6}
placeholder="Message"
id='message'
/>
<button className={style.button} onClick={handleSubmit}
>{buttonText}
</button>
<p className='error' >
{showSuccessMessage}
{showFailureMessage}
'test'
</p>
</form>
</div>
);
}
Set state is an asynchronous operation, so log the state after setting it, will log the old value.
If you want to sure that the value has been setten correctly, you need to use setState.
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
setShowSuccessMessage(prev => {
console.log(prev)
return prev
})// log the 'Contact form submitted'
setShowFailureMessage(prev => {
console.log(prev)
return prev
})// log false
And setting state, in this case, will not make the component re-render as you return the same value.
The reason is that setXxx methods are async. If you click submit 10 times fast enough, you will see different results.
You have two options:
not preferred: If you don't use 'controlled' input (you are not using it now - in input elements, you are not using things like 'value={name}'). then you don't need the state at all.
Use controlled input. You can check document on reactjs org.
Simply put,
useState
on the input tag, add value={name} onChange={changeHandler},
in changHandler, setState
in formSubmit, use the value in state
There is a section on forms on this excellent tutorial:
Bob Ziroll free React Course
You are using react so you need to set data and use those data after that i have updated your code it should work for you. i.e. you need to set value in onChange function as below:-
import style from "../styles/Contact.module.css";
import React, { useState } from 'react'
const style = {};
export default function Contact() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [subject, setSubject] = useState('')
const [message, setMessage] = useState('')
const [errors, setErrors] = useState({})
const [buttonText, setButtonText] = useState('Submit')
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
const [showFailureMessage, setShowFailureMessage] = useState(false)
const handleValidation = () => {
let tempErrors = {}
let isValid = true
console.log(name, email, subject, message)
if (name.length <= 0) {
tempErrors['name'] = true
isValid = false
}
if (email.length) {
tempErrors['email'] = true
isValid = false
}
if (subject.length) {
tempErrors['subject'] = true
isValid = false
}
if (message.length <= 0) {
tempErrors['message'] = true
isValid = false
}
setErrors({ ...tempErrors })
console.log('errors', errors)
return isValid
}
const handleSubmit = async (e) => {
e.preventDefault()
let isValidForm = handleValidation()
if (isValidForm) {
setButtonText('Sending')
const res = await fetch('/api/sendgrid', {
body: JSON.stringify({
email: email,
name: name,
subject: subject,
message: message
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
})
const { error } = await res.json()
if (error) {
console.log(error)
setShowSuccessMessage(false)
setShowFailureMessage('Error, please complete all sections')
setButtonText('Submit')
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
return
}
setShowSuccessMessage('Contact form submitted')
setShowFailureMessage(false)
console.log('success is ', showSuccessMessage)
console.log('failure is ', showFailureMessage)
setButtonText('Send')
}
console.log(name, email, subject, message)
}
return (
<div className={style.container}>
<h1 className={style.title}>Get in Touch</h1>
<form className={style.form}>
<input className={style.inputS} type="text" placeholder="Name" id='name' onChange={(e) => setName(e.target.value)} />
<input className={style.inputS} type="text" placeholder="Phone" id='phone' />
<input className={style.inputL} type="text" placeholder="Email" id='email' onChange={(e) => setEmail(e.target.value)} />
<input className={style.inputL} type="text" placeholder="Subject" id='subject' onChange={(e) => setSubject(e.target.value)} />
<textarea
className={style.textArea}
type="text"
rows={6}
placeholder="Message"
id='message'
onChange={(e) => setMessage(e.target.value)}
/>
<button className={style.button} onClick={handleSubmit}
>{buttonText}
</button>
<p className='error' >
{showSuccessMessage}
{showFailureMessage}
'test'
</p>
</form>
</div>
);
}
And if you want same for phone you can also set data for the same and use after that.

Run tesseract.js OCR onFileUpload and extract text

I think the title is self-explanatory. What am I doing wrong below? What I want to achieve is getting the text out of a photo, right after the user selects a photo.
The error I get is:
createWorker.js:173 Uncaught Error: RuntimeError: null function or function signature mismatch
What am I doing wrong?
const { createWorker } = require("tesseract.js");
const [file,setFile] = useState();
const worker = createWorker({
logger: (m) => console.log(m),
});
const doOCR = async (image) => {
await worker.load();
await worker.loadLanguage("eng");
await worker.initialize("eng");
const {
data: { text },
} = await worker.recognize(image);
// } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
console.log(text);
setOcr(text);
};
const [ocr, setOcr] = useState("Recognizing...");
useEffect(() => {
file ? doOCR(file) : console.log('no file selected yet!');
}, [file]);
const getFile = (e) => {
console.log("Upload event:", e);
if (e) {
if (Array.isArray(e)) setFile(e[0]);
setFile(e)
}
}
....
<p>{ocr}</p> /* this only displays "Recognizing..." */
<Form.Item
name="uploadedPhoto"
label="Upload your photo scan"
getValueFromEvent={getFile}
// rules={[{ required: true }]}>
<Input type="file"
// onChange={onImageUpload}
/>
</Form.Item>
Solved it by doing it like this instead of the above (I applied the function to the onChange of the Input itself, not the Form.Item element)
const handleFileSelected = (e) => {
const files = Array.from(e.target.files);
setFile(files[0]);
};
<Input type="file" onChange={handleFileSelected} />

React - I have to click a button twice to display api data

I would like to display API data on a button click. My state is set to an empty array initially and I guess that's why it shows an empty array at first click, but what could I write differently to show data on first click ? After the initial click it works.
My code:
const Search = () => {
const [textInput, setTextInput] = useState('');
const [tickers, setTickers] = useState([]);
const [prices, setPrices] = useState([]);
const [isClicked, setIsClicked] = useState(false);
const inputHandler = (e) => {
setTextInput(e.target.value);
}
const showData = async (e) => {
e.preventDefault();
const url = `https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
try {
const data = await axios.get(url);
if(data) {
setPrices([data.data['Monthly Time Series']['2021-11-30']]);
}
} catch(err) {
console.log(err)
}
console.log(prices);
setIsClicked(true);
setTextInput('');
}
return (
<StyledSearch>
<h1>Security Price Monitor App </h1>
<form onSubmit={submitSearch}>
<input type="text" value={textInput} onChange={inputHandler} placeholder='Enter Ticker'/>
<button type="submit" onClick={showData}>Search</button>
</form>
{isClicked &&
<Chart tickers = {tickers} setTickers={setTickers} prices={prices} setPrices={setPrices}/>
}
</StyledSearch>
)
}
Try to change your conditional rendering condition to:
{prices.length > 0 && (
<Chart
tickers={tickers}
setTickers={setTickers}
prices={prices}
setPrices={setPrices}
/>
)}
I think that you can remove the isClicked state. It is redundant.

Resources