ideas to use useAsync hook inside submit function - reactjs

I've imported useAsync(hook from 'react-async') and I'm trying to use it after the client submits the form to send a post request.
Now, I'm getting an error that I can't use hooks inside functions based on the rules of hooks.
how can solve it? so that I'll be able to use useAsync after the client submits the form.
handleSubmit is my onSubmit function.
Here's my code :
import { Modal } from 'react-bootstrap';
import "react-datepicker/dist/react-datepicker.css";
import DatePicker from 'react-datepicker'
import { useState } from 'react';
import { useAsync } from 'react-async';
import useFetch from '../../hooks/useFetch'
import { useLocation } from 'react-router-dom';
const TodoPopup = (props : {show : boolean, onHide : () => void, title : string, values?
: {title : string, expirationDate : any, description : string}}) => {
const [name, setName] = useState(props.values?.title || '');
const [date, setDate] = useState(props.values?.expirationDate || '');
const [description, setDescription] = useState(props.values?.description || '');
const url = useLocation().pathname;
const handleSubmit = (e : React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// const fetchRelevant = useFetch.apply(this,
// (props.values? [url, 'PUT', {name, date, description}] : [url, 'POST',{name,date, description}]));
const fetchRelevant = useFetch.apply(this, [url, props.values? 'PUT' : 'POST',
{name, date, description}]);
const { data, error, isPending} = useAsync({promiseFn : () => {
return fetchRelevant;
}})
if(isPending) console.log('loading...');
if(error) console.log('error');
if(data) props.onHide();
}
return (
<Modal {...props} centered>
<Modal.Header closeButton>
<Modal.Title>{props.title}</Modal.Title>
</Modal.Header>
<form onSubmit={(e) => handleSubmit(e)}>
<Modal.Body>
<div className = "form-group">
<label>Task Name</label>
<input type="text" className = "form-control" value = {name} onChange = {(e) =>
setName(e.target.value)} required />
</div>
<div className = "form-group">
<label>Expired</label>
<DatePicker selected={date} onChange={e => setDate(e)} className="form-control"
minDate={new Date()} required />
</div>
<div className = "form-group">
<label>Description</label>
<textarea rows = {5} className = "form-control" value = {description} onChange =
{(e) => setDescription(e.target.value)}></textarea>
</div>
</Modal.Body>
<Modal.Footer>
<button className="btn btn-primary" type="submit">Save changes</button>
</Modal.Footer>
</form>
</Modal>
);
};
export default TodoPopup;
*Note - I've tried to name the onSubmit function with an uppercase letter but it caused a runtime error.

There is a function exposed by useFetch called run , you can call it like so :
import React, { useState } from "react"
import { useFetch } from "react-async"
const TodoPopup = (props) => {
const { isPending, error, run } = useFetch("URL", { method: "POST" })
const [name, setName] = useState(props.values?.title || '');
const [date, setDate] = useState(props.values?.expirationDate || '');
const [description, setDescription] = useState(props.values?.description || '');
const handleSubmit = e => {
e.preventDefault()
run({ body: JSON.stringify({name, date, description}) })
}
return (
<form onSubmit={handleSubmit}>
...
</form>
)
}
export default TodoPopup;

Related

How to handle multiple select options submittion in react js?

I want to submit a form into mongoDB using nodejs API & reactJs. With the exception of the multiple select option, everything is operating as it should be.
Being new to react, I have no idea how to handle the multi select option's onChange method.
Here is what I've tried:
import React, { useState, useRef } from "react";
import { useForm } from "react-hook-form";
import { v4 as uuidv4 } from 'uuid';
import axios from "axios";
import Select from 'react-select';
export default function EventForm(props) {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm();
const form = useRef();
const [loading, setLoading] = useState(false);
const [info, setInfo] = useState("");
const [analysis, setAnalysis] = useState("Undefined");
const [relatedEvent, setRelatedEvent] = useState([]);
const handleInfoChange = (e) => {
setInfo(e.target.value)
}
const handleAnalysisChange = (e) => {
setAnalysis(e.target.value)
}
const handleRelatedEvents = (e) => {
setRelatedEvent(e.target.value)
}
const relatedEventsData = props.data.map(opt => ({ label: opt.info, value: opt._id }));
const onSubmit = async () => {
setLoading(true);
const MySwal = withReactContent(Swal);
const eventData = {
UUID: uuidv4(),
info: info,
analysis: analysis,
relatedEvent: relatedEvent,
}
axios
.post(`${process.env.REACT_APP_PROXY}/api/events`, eventData)
.then((res) => {
console.log(res);
setLoading(false);
MySwal.fire(
"Success!",
"A new event has been saved successfully",
"success"
);
})
.catch((error) => {
console.log(error);
});
};
return (
<div className="panel-body">
<Form
ref={form}
onSubmit={handleSubmit(onSubmit)}
className="form-horizontal"
>
<div className="row">
<div className="col-lg-6">
<div className="mb-3">
<Form.Label>Info</Form.Label>
<Form.Control
type="text"
placeholder="Enter info..."
{...register("info", { required: true })}
value={info}
onChange={handleInfoChange}
/>
{errors.info && (
<ul className="parsley-errors-list filled" id="parsley-id-7" aria-hidden="false">
<li className="parsley-required">This value is required.</li>
</ul>
)}
</div>
</div>
<div className="col-lg-6">
<div className="mb-3">
<Form.Label>Related events</Form.Label>
<Select
options={relatedEventsData}
value={relatedEvent}
isMulti
onChange={handleRelatedEvents}
/>
</div>
</div>
<div className="col-lg-12">
<Button variant="primary" type="submit">
{loading ? "Saving..." : "Save"}
</Button>
</div>
</div>
</Form>
</div>
);
}
Could you please guide me how to make it work!
Thank you
you can make use of Select onChange event handler which passes the selected options as an array as argument ..
from that you can map over it to get the values as required
something as below:
const handleChange = (opts) => {
const selectedValues = opts.map((opt) => opt.value);
setSelectedValues(selectedValues);
};
Please check the working sample for better clarity 😉 -

Why is react-google-recaptcha-v3 spamming network requests in my ReactJS component?

Using the package react-google-recaptcha-v3, I am able to get a score for the v3 captcha from google when I submit my form, great! However... If I hope the network tab of chrome I see a neverending loop of requests going out to recaptcha (way before I ever submit the form). Many every second:
https://www.google.com/recaptcha/api2/reload?k=xxxx (where xxxx is my recaptcha site key)
Is it something from my reactJS component? I can't imagine this is supposed to happen right.
My code is below, I have stripped out the irrelevant content and made the form small for readability.
import React, { useState, useCallback } from 'react'
import config from 'config'
import {
GoogleReCaptchaProvider,
GoogleReCaptcha
} from "react-google-recaptcha-v3"
function ContactForm(props) {
/*form data*/
const [name, setName] = useState('')
/*validation state*/
const [noNameError, setNoNameError] = useState(false)
/*recaptcha state*/
const [token, setToken] = useState();
const [refreshReCaptcha, setRefreshReCaptcha] = useState(false);
const key = config.RECAPTCHA_V3_SITEKEY
const onVerify = useCallback((token) => {
setToken(token);
});
const getIP = async()=>{
const response = await fetch('https://geolocation-db.com/json/');
const data = await response.json();
return(data.IPv4)
}
const handleSubmit = (event) => {
event.preventDefault()
if(!doValidationStuff()){
setNoNameError(true)
}
setNoNameError(false)
const userIpGetter = getIP()
userIpGetter.then(function(ipResult){
myService.doStuff(
name,
token,
ipResult
)
.then(()=>{
doOtherStuff()
setRefreshReCaptcha(r => !r)
})
})
}
const setFormName = (event)=>{
setName(event.target.value)
}
return (
<GoogleReCaptchaProvider reCaptchaKey={key}>
<form id="contactForm" onSubmit={handleSubmit} className="needs-validation">
<GoogleReCaptcha
action='ContactForm'
onVerify={onVerify}
refreshReCaptcha={refreshReCaptcha}
/>
<div className="mb-3">
<label className="form-label">Name</label>
<input className="form-control" type="text" placeholder="Name" value={name}
onChange={setFormName}/>
<span style={{ color: "red", display: noNameError ? 'block' : 'none' }}>Please enter your name.</span>
</div>
<div className="d-grid">
<button className="btn btn-primary btn-lg" type="submit">Submit</button>
</div>
</form>
</GoogleReCaptchaProvider>
)
}
export { ContactForm };
I ended up having to use the hook from this lib in case anyone else runs into this. Unsure if the refresh is needed in this case, so far I am not doing manual refreshes of the token, leaving that up to the recaptcha magic. Here is the code I ended up with that works, I have stripped out the other parts of the component for readability, but it should still build/run for you:
Way out at the top level of the app:
<GoogleReCaptchaProvider reCaptchaKey={config.RECAPTCHA_V3_SITEKEY}>
<App />
</GoogleReCaptchaProvider>
Then way drilled down into a specific component:
import React, { useState, useEffect, useCallback } from 'react'
import { useGoogleReCaptcha } from 'react-google-recaptcha-v3'
function ContactForm(props) {
const [isSaving, setIsSaving] = useState(false)
/*form data*/
const [name, setName] = useState('')
/*validation state*/
const [noNameError, setNoNameError] = useState(false)
/*recaptcha state*/
const [recToken, setRecToken] = useState()
/*START: recaptcha code*/
const { executeRecaptcha } = useGoogleReCaptcha()
const handleReCaptchaVerify = useCallback(async () => {
if (!executeRecaptcha) {
console.log('Execute recaptcha not yet available');
return;
}
const recTokenResult = await executeRecaptcha('contactForm')
setRecToken(recTokenResult)
}, [executeRecaptcha]);
useEffect(() => {
handleReCaptchaVerify();
}, [handleReCaptchaVerify]);
/*END: recaptcha code*/
const getIP = async()=>{
const response = await fetch('https://geolocation-db.com/json/');
const data = await response.json();
return(data.IPv4)
}
const handleSubmit = (event) => {
event.preventDefault()
/*validation start*/
if(!name || name.length < 3){
setNoNameError(true)
return
}
else{
setNoNameError(false)
}
/*validation end*/
const userIpGetter = getIP()
handleReCaptchaVerify().then(function(){
userIpGetter.then(function(ipResult){
blahService.sendContactForm(
name,
recToken,
ipResult
)
.then(()=>{
blahService.success('Thank you!')
})
})
})
}
const setFormName = (event)=>{
setName(event.target.value)
}
return (
<form id="contactForm" onSubmit={handleSubmit} className="needs-validation">
<div className="mb-3">
<label className="form-label">Name</label>
<input className="form-control" type="text" placeholder="Name" value={name}
onChange={setFormName}/>
<span style={{ color: "red", display: noNameError ? 'block' : 'none' }}>Please enter your name.</span>
</div>
<div className="d-grid">
<button className="btn btn-primary btn-lg" type="submit">Submit</button>
</div>
</form>
)
}
export { ContactForm };

Dynamic arrayfield delete operation in Reactjs?

As a Begineer in a react,i have just implementing a dynamic array field for learning but got a problem in delete operation of removing inputs fields from the row field with passing its id in deletefunction.How to overcome this problem?
Code
import React, { useState} from "react";
import "./styles.css";
const initialValues = [
{number: "",options: ""}
];
const Newrow = (props) => {
const [number, setNumber] = useState("");
const [options, setoption] = useState([]);
const addRow = () => {
let _row = {
number: "",
options: ""
};
props.setData(_row);
};
const delrow = (i) => {
data.splice(i,2)
setData({})
}
return <div>
<input
type="number"
value={number}
onChange={(e) => {
setNumber(e.target.value);
}}
/>
<input type="text"
className="input"
value={options}
onChange={e=>{setoption(e.target.value)}}
/>
<button
type="submit"
onClick={delrow}
className="btn btn-danger">remove</button>
</div>
};
export default function App(props) {
const [data, setData] = useState([]);
const addRow = (row) => {
setData([...data, row]);
};
return (
<div className="App">
{[...data, ...initialValues].map((row, idx) => {
return <Newrow setData={addRow} data={row} key={idx} delrow{idx} />;
})}
<button
type="submit"
onClick={() =>
addRow({number: "",options: "" })}
className="btn btn-success">Add</button>
</div>
);
}

How do I retrieve an item data by id and pass it to another component in React16 with Hooks

I want to create an edit screen. I have a component called Task that looks like this
const Task = ({task}) => {
return (
<li>
<div>
<div>{task.text}</div>
{task.day}
</div>
<div className="icons">
<Link
to={`/edit/${task.id}`} >
<RiEdit2FillIcon />
</Link>
</div>
</li>
)
}
That goes to a parent component with a tasks.map() and then to the main component that will render the list of tasks. But from this component, I want to click on that Edit Icon and open an Edit screen that is already Routed like this <Route path='/edit/:id' component={EditTask}/> That EditTask component is what I am working on now
import React from 'react'
import {useState, useEffect} from 'react'
import { Link } from 'react-router-dom'
import Task from './components/Task'
const EditTask = () => {
const api ="http://localhost:5000"
const [tasks, setTasks] = useState([])
const [task, setTask] = useState([])
const [text, setText] = useState('')
const [day, setDay] = useState('')
const [reminder, setReminder] = useState(false)
const onSubmit = (e) => {
e.preventDefault()
updateData()
}
//Get Request
useEffect(() => {
const getTask = async () => {
const tasksFromServer = await fetchTask()
setTasks(tasksFromServer)
}
getTask()
},[])
const fetchTask = async (id) => {
const res = await fetch(`${api}/tasks/${id}`)
const data = await res.json()
console.log(data)
return data
}
//Update request
const updateData = async (id) => {
const taskToEdit = await fetchTask(id)
const updateTask = {
...taskToEdit,
reminder: !taskToEdit.reminder,
text: taskToEdit.text,
day: taskToEdit.day
}
const res = await fetch(`${api}/tasks/${id}`, {
method: 'PUT',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(updateTask)
})
const data = await res.json()
setTasks(
tasks.map((task) =>
task.id === id
? {
...task,
reminder: data.reminder,
text: data.text,
day: data.day
}
: task
)
)
}
return (
<div>
<header className='header'>
<h1>Edit</h1>
<Link to="/" className="btn btn-primary">Go Back</Link>
</header>
<form className="add-form" onSubmit={onSubmit}>
<Task task={task}/>
<div className="form-control">
<label>Task</label>
<input type="text" placeholder="Add Task" value={text} onChange={(e)=> setText(e.target.value)} />
</div>
<div className="form-control">
<label>Day & Time</label>
<input type="text" placeholder="Add Day & Time" value={day} onChange={(e)=> setDay(e.target.value)}/>
</div>
<div className="form-control form-control-check">
<label>Set Reminder</label>
<input type="checkbox" checked={reminder} value={reminder} onChange={(e)=> setReminder(e.currentTarget.checked)}/>
</div>
<input className="btn btn-block" type="submit" value="Save Task" />
</form>
</div>
)
}
export default EditTask
I'm a bit lost here. I can't figure out how to pass the ID from Task.js to EditTask.js and populate the form with the data form that ID.
Thanks in advance
You can get id in EditTask with useParams in "react-router
import { useParams } from "react-router";
const EditTask = () => {
const { id } = useParams();
}

Input not re-rendering onChange with hooks

When typing and logging the input e.target.value, I get the default value + the last key stroke, but nothing re-renders. I guess that React doesn't recognize that the state changed, but I'm having a problem finding out the correct way to do this.
This is the code in question:
const [text, setText] = useState(task.text);
console.log(text);
const handleInputChange = (e) => {
setText(e.target.value);
};
const taskInput = (
<form>
<input type='text' value={text} onChange={handleInputChange} />
</form>
);
And the full file:
import React, { useContext, useState } from "react";
import { TaskContext } from "../context/TaskState";
const Task = ({ task }) => {
const { deleteTask } = useContext(TaskContext);
const { changeStatus } = useContext(TaskContext);
const taskText = (
<div
className='task-text'
onClick={() => changeStatus({ ...task, done: !task.done })}
style={task.done ? { textDecoration: "line-through" } : null}
>
{task.text}
</div>
);
const [text, setText] = useState(task.text);
console.log(text);
const handleInputChange = (e) => {
setText(e.target.value);
};
const taskInput = (
<form>
<input type='text' value={text} onChange={handleInputChange} />
</form>
);
const [option, setOption] = useState(taskText);
return (
<div className='task-container'>
<button className='task-edit' onClick={() => setOption(taskInput)}>
edit
</button>
<button className='task-delete' onClick={() => deleteTask(task.id)}>
x
</button>
{option}
</div>
);
};
export default Task;
I'am using global state for the rest of the app and reducers.
I think, onChange in your input might cause this error. Try replacing this:
onChange={handleInputChange}
with this:
onChange={(e) => handleInputChange(e)}
e object might be not passed to your method.
Please try wrapping your taskInput value in useMemo with dependency text as when you store JSX as variable during re-render they are refering to the previous value as they don't know the variable they used have value changed.
import React, { useMemo, useContext, useState } from "react";
const taskInput = useMemo(() => (
<form>
<input type='text' value={text} onChange={handleInputChange} />
</form>
), [text]);
The problem was the way I passed option inside the jsx.
I made the option state a boolean, converted taskText and taskInput to functions and passed option conditionally inside the jsx.
import React, { useContext, useState } from "react";
import { TaskContext } from "../context/TaskState";
const Task = ({ task }) => {
const { deleteTask } = useContext(TaskContext);
const { changeStatus } = useContext(TaskContext);
const taskText = () => {
return (
<div
className='task-text'
onClick={() => changeStatus({ ...task, done: !task.done })}
style={task.done ? { textDecoration: "line-through" } : null}
>
{task.text}
</div>
);
};
const [text, setText] = useState(task.text);
console.log(text);
const handleInputChange = (e) => {
setText(e.target.value);
};
const taskInput = () => {
return (
<form>
<input type='text' value={text} onChange={handleInputChange} />
</form>
);
};
const [option, setOption] = useState(true);
return (
<div className='task-container'>
<button className='task-edit' onClick={() => setOption(!option)}>
edit
</button>
<button className='task-delete' onClick={() => deleteTask(task.id)}>
x
</button>
{option ? taskText() : taskInput()}
</div>
);
};
export default Task;

Resources