How to handle multiple select options submittion in react js? - reactjs

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 😉 -

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

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

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

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

Set title onSubmit based on current SELECT OPTION VALUE - react

I found this tutorial on how to create a react quizz app on youtube link to tutorial
I am trying to set the title based on the current Select Option Value when submitting the form.
Currently I managed to change the title only when a different option is selected.
import React, { useState, useEffect, useRef } from "react";
import "./App.css";
import axios from "axios";
import FlashcardList from "./components/FlashcardList";
function App() {
const [flashcards, setFlashcards] = useState([]);
const [categories, setCategories] = useState([]);
const [title, setTitle] = useState("General Knowledge");
const categoryEl = useRef();
const amountEl = useRef();
useEffect(() => {
axios.get("https://opentdb.com/api_category.php").then((res) => {
setCategories(res.data.trivia_categories);
});
}, []);
function decodeString(str) {
const textArea = document.createElement("textarea");
textArea.innerHTML = str;
return textArea.value;
}
function handleSubmit(e) {
e.preventDefault();
axios
.get("https://opentdb.com/api.php", {
params: {
amount: amountEl.current.value,
category: categoryEl.current.value,
},
})
.then((res) => {
setFlashcards(
res.data.results.map((questionItem, index) => {
const answer = decodeString(questionItem.correct_answer);
const options = [...questionItem.incorrect_answers, answer];
return {
id: `${index} - ${Date.now()}`,
question: decodeString(questionItem.question),
answer: answer,
options: options.sort(() => Math.random() - 0.5),
};
})
);
});
}
function getTitle(e) {
setTitle(e.target.options[e.target.selectedIndex].text);
}
return (
<>
<form className="header" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="category">Category</label>
<select id="category" ref={categoryEl} onChange={getTitle}>
{categories.map((category) => {
return (
<option value={category.id} key={category.id}>
{category.name}
</option>
);
})}
</select>
</div>
<div className="form-group">
<label htmlFor="amount">Number Of Questions</label>
<input
type="number"
id="amount"
min="1"
step="1"
defaultValue={10}
ref={amountEl}
/>
</div>
<div className="form-group">
<button className="btn">Generate</button>
</div>
</form>
<div className="container">
<h1 className="title">{title}</h1>
<FlashcardList flashcards={flashcards} />
</div>
</>
);
}
export default App;
Code
Live demo
You can set the category as soon the categories are fetched. Can just use the zeroth element to set the title.
useEffect(() => { axios.get("https://opentdb.com/api_category.php").then((res) => {
setCategories(res.data.trivia_categories);
setTitle(res.data.trivia_categories[0]);
});
}, []);

Resources