I am trying to update my MongoDB database containing data for a project information form through a PUT request on my action, but I am unable to get the handleSumbit function on my component to call my project action. I am receiving a 404 error. I have no clue what I am missing or have done wrong. Any insight would be appreciated.
My Component:
import React, { useState } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import '../styles/ViewProject.css'
import { updateProjects } from '../actions/project.actions'
const EditProject = ({ project, match }) => {
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [projectName, setProjectName] = useState('')
const [projectType, setProjectType] = useState('')
const [multiple, setMultiple] = useState(false)
const [dueDate, setDueDate] = useState(Date)
const [url, setUrl] = useState('')
const [description, setDescription] = useState('')
const handleSubmit = e => {
e.preventDefault()
updateProjects(name, email, projectName, projectType, dueDate, url, description)
console.log('Data ', name, email, projectName)
}
return(
<form className="viewProjectContainer" onSubmit={handleSubmit}>
{project.map((task, i) => (
<div key={i++} >
{match.params.id === task._id ?
<div className="vp">
<div className="vpLeft">
<div className="vpTitle">
<input className='vpProjectName' defaultValue={task.projectName} onChange={e => setProjectName(e.target.value)} />
</div>
<div className="vpSub">
<p>Submitted by: </p>
<label>Name: </label>
<input className='vpName' defaultValue={task.name} onChange={e => setName(e.target.value)}/>
<label>Email: </label>
<input className='vpEmail' defaultValue={email} onChange={e => setEmail(e.target.value)}/>
</div>
<div className="vpDescription">
<label>{task.multiple === true ? "(Multiple)" : "Single"}</label>
<p>Project Type: </p>
<select className='formInput' onChange={e => setProjectType(e.target.value)} defaultValue={projectType}>
<optgroup label='Digital'>
<option value='banner'>Banner</option>
<option value='emailBlast'>Email Blast</option>
<option value='productImage'>Product Image</option>
<option value='socialMediaAd'>Social Media Ad</option>
<option value='video'>Video</option>
<option value='websiteMockup'>Website Mockup</option>
<option value='other'>Other (Please be specific in Description)</option>
</optgroup>
<optgroup label='Physical Product'>
<option value='bottleOpener'>Bottle Opener</option>
<option value='clothing'>Clothing</option>
<option value='keyChain'>Key Chain</option>
<option value='popSocket'>Pop Socket</option>
<option value='usb'>USB</option>
<option value='other'>Other (Please be specific in Description)</option>
</optgroup>
<optgroup label='Print'>
<option value='businessCard'>Business Card</option>
<option value='brochureBiFold'>Brochure - Bi-fold</option>
<option value='brochureTriSquare'>Brochure - Trifold Square</option>
<option value='brochureTriStandard'>Brochure - Trifold Standard</option>
<option value='catalogMore'>Catalog - Less than 50 pages</option>
<option value='catalogLess'>Catalog - More than 50 pages</option>
<option value='documentFull'>Document - Full Sheet</option>
<option value='documentHalf'>Document - Half Sheet</option>
<option value='flyer4x6'>Flyer - 4x6</option>
<option value='flyer5x7'>Flyer - 5x7</option>
<option value='flyer6x9'>Flyer - 6x9</option>
<option value='postCard4x6'>Post Card 4x6</option>
<option value='postCard5x7'>Post Card 5x7</option>
<option value='postCard6x9'>Post Card 6x9</option>
<option value='poster11x17'>Poster 11x17</option>
<option value='poster24x36'>Poster 24x36</option>
<option value='other'>Other (Please be specific in Description)</option>
</optgroup>
<optgroup label='Other'>
<option value='consultation'>Consultation</option>
<option value='labeling'>Labeling</option>
<option value='other'>Other (Please be specific in Description)</option>
</optgroup>
</select>
<div className="vpDescBox">
<textarea className='updateDescription' defaultValue={description} onChange={e => setDescription(e.target.value)}></textarea>
</div>
</div>
</div>
<div className="vpRight">
<div className="vpTicket">
<p>Ticket Number: {task.ticketNumber}</p>
<label>Submitted: {task.date} </label>
<label>Preferred due date: </label>
<input type='date' defaultValue={task.dueDate} onChange={e => setDueDate(e.target.value)}/>
</div>
<div>
<label>Reference links: </label>
<input type='text' defaultValue={task.url} onChange={e => setUrl(e.target.value)}/>
<label></label>
</div>
<div>
<p>Project Status:</p>
<p>Task Recieved: {task.recieved === true ? "Recieved" : "Not Revieved"}</p>
<p>Task in progress: {task.inProgress === true ? "In Progress" : "Not in Progress"}</p>
<p>Completed: {task.completed === true ? "Complete" : "Incomplete"}</p>
</div>
</div>
</div>: null}
</div>
))}
<div className='buttonsContainer'>
<button type='submit' className='projectViewButton1'><Link to={'/' + match.params.id}>Save</Link></button>
</div>
</form>
)
}
const mapStateToProps = state => ({
project: state.projects.project,
id: state.projects.id
})
export default connect(
mapStateToProps,
{ updateProjects }
)(EditProject)
My action:
export function updateProjects(name, email, projectName, projectType, dueDate, url, description) {
console.log('reducer ', name)
axios.put('api/projects/update/:id', {
name: name,
email: email,
projectName: projectName,
projectType: projectType,
// multiple: multiple,
dueDate: dueDate,
url: url,
description: description
}).then(resp => {
console.log("Data updated: ", resp.data)
})
}
My Backend:
// #route PUT api/projects/update/:id
// #desc Update projects
// #access Private
router.put('/update/:id', async (req, res) => {
try {
const {
name,
email,
projectName,
projectType,
multiple,
dueDate,
reference,
attachment,
description,
ticketNumber,
received,
inProgress,
completed
} = req.body
const project = await Project.findByIdAndUpdate(req.params.id, {
name,
email,
projectName,
projectType,
multiple,
dueDate,
reference,
attachment,
description,
ticketNumber,
received,
inProgress,
completed
})
res.json(project);
} catch (err) {
console.error(err.message)
res.status(500).send('Server Error')
}
})
Thank you for helping!
The endpoint you are hitting with the axios.put is api/projects/update/:id, which is incomplete. You should put your host as well. For example: http://localhost:8000/api/projects/update/:id
And also, you need to specify a proper value for :id in the URL. Not sure if you left that out in purpose for example's sake but just wanna point that one out too haha.
in your react project folder's package.json file :
"proxy":"http://localhost:8000"
your http url should be below :
axios.put('/api/projects/update/', {
params: {
id: 'value of id'
}
});
or :
axios.put(`/api/projects/update/${value of id}`)
Related
My crypto calculator should change the opposite field (Amount in, Amount out) based on
API calls. For example, if I choose Ethereum - Bitcoin pair
and I enter 1 in Ethereum field, I will get 0.06902 in Bitcoin field.
If I enter 1 in the Bitcoin field, I get 14,49 in the Ethereum field (real rates).
And my calculator does do that, but I can't see the change right away. I have to delete the value to see the reflection
in another input box, or I have to change the value to see the reflection in another box (but it will be the previous value).
Same story with Select items, if I select Ethereum and Bitcoin, for example, it doesn't work. If I change the second token
to DASH, it will show me the price of Ethereum Bitcoin (the previous pair). I'm doing something wrong in my code, I'll be glad to get any advice
or solution to my problem
Here is the code:
import React, { useState } from 'react';
const Exchange = () => {
const [SelectOne, setSelectOne] = useState(null);
const [SelectTwo, setSelectTwo] = useState(null);
const [DataOne, setDataOne] = useState(null);
const [DataTwo, setDataTwo] = useState(null);
const crypto = [
'btc',
'eth',
'dash',
'dot',
'xmr',
'bnb',
'bch',
'etc',
'zec',
'sol',
'ltc',
'trx',
'zrx',
'xrp',
'usdt',
'usdt',
'xtz',
'matic',
'shib',
'doge'
];
function makeRequest(from, to, amount, which) {
let accessor = crypto[to].toUpperCase();
fetch(`https://min-api.cryptocompare.com/data/price?fsym=${crypto[from]}&tsyms=${crypto[to]}`)
.then(res => res.json())
.then(data => {
if (which) {
setDataTwo(data[accessor] * amount);
} else {
setDataOne(data[accessor] * amount);
}
});
}
function getSelectorTwo(val) {
setSelectTwo(val.target.value);
console.log(val.target.value);
if (SelectOne || SelectTwo != null && SelectOne != SelectTwo) {
makeRequest(SelectOne, SelectTwo, DataOne, true);
}
}
function getSelectorOne(val) {
setSelectOne(val.target.value);
console.log(val.target.value);
if (SelectOne || SelectTwo != null && SelectOne != SelectTwo) {
makeRequest(SelectOne, SelectTwo, DataOne, true);
}
}
function getValueOne(val) {
setDataOne(val.target.value);
console.log(val.target.value);
if (SelectOne || SelectTwo != null && SelectOne != SelectTwo) {
makeRequest(SelectOne, SelectTwo, DataOne, true);
}
}
function getValueTwo(val) {
setDataTwo(val.target.value);
console.log(val.target.value);
if (SelectOne || SelectTwo != null && SelectOne != SelectTwo) {
makeRequest(SelectTwo, SelectOne, DataTwo, false);
}
}
return (
<div className="bg-sky-500 h-screen w-screen">
<div className='container mx-auto '>
<div className='grid grid-cols-1 gap-4'>
<div>
<select className='rounded-full text-xl' onChange={getSelectorOne}>
<option value={27}>Select currency to send:</option>
<option value={0}>Bitcoin</option>
<option value={1}>Ethereum</option>
<option value={2}>DASH</option>
<option value={3}>Polkadot</option>
<option value={4}>Monero</option>
<option value={5}>Binance Smart Chain</option>
<option value={6}>Bitcoin Cash</option>
<option value={7}>Ethereum Classic</option>
<option value={8}>Zcash</option>
<option value={9}>Solana</option>
<option value={10}>Litecoin</option>
<option value={11}>Tron</option>
<option value={12}>0x</option>
<option value={13}>Ripple</option>
<option value={14}>Tether (ERC20)</option>
<option value={15}>Tether (TRC20)</option>
<option value={16}>Tezos</option>
<option value={17}>Polygon</option>
<option value={18}>Shiba Inu</option>
<option value={19}>Dogecoin</option>
</select>
</div>
<div>
<select className='rounded-full text-xl' onChange={getSelectorTwo}>
<option value={27}>Select currency to send:</option>
<option value={0}>Bitcoin</option>
<option value={1}>Ethereum</option>
<option value={2}>DASH</option>
<option value={3}>Polkadot</option>
<option value={4}>Monero</option>
<option value={5}>Binance Smart Chain</option>
<option value={6}>Bitcoin Cash</option>
<option value={7}>Ethereum Classic</option>
<option value={8}>Zcash</option>
<option value={9}>Solana</option>
<option value={10}>Litecoin</option>
<option value={11}>Tron</option>
<option value={12}>0x</option>
<option value={13}>Ripple</option>
<option value={14}>Tether (ERC20)</option>
<option value={15}>Tether (TRC20)</option>
<option value={16}>Tezos</option>
<option value={17}>Polygon</option>
<option value={18}>Shiba Inu</option>
<option value={19}>Dogecoin</option>
</select>
</div>
<div >
<input type={'number'} onChange={getValueOne} value={DataOne} placeholder={'Amout in'} className='p-6 rounded-lg bg-sky-600 outline-none'></input>
</div>
<div className=''>
<input type={'number'} onChange={getValueTwo} value={DataTwo} placeholder={'Amount out'} className='p-6 rounded-lg bg-sky-600 outline-none'></input>
</div>
</div>
</div>
</div>
)
}
I just don't know where to start researching my problem. I couldn't find the mistake while reading the React documentation. I'm stuck on it.
And I can't continue my project without this feature.
You're gonna slap yourself on the head for this one
import { useState } from 'react';
import './App.css';
function App() {
const [SelectOne, setSelectOne] = useState(null);
const [SelectTwo, setSelectTwo] = useState(null);
const [DataOne, setDataOne] = useState(null);
const [DataTwo, setDataTwo] = useState(null);
const crypto = [
'btc',
'eth',
'dash',
'dot',
'xmr',
'bnb',
'bch',
'etc',
'zec',
'sol',
'ltc',
'trx',
'zrx',
'xrp',
'usdt',
'usdt',
'xtz',
'matic',
'shib',
'doge'
];
function makeRequest(from, to, amount, which) {
let accessor = crypto[to].toUpperCase();
fetch(`https://min-api.cryptocompare.com/data/price?fsym=${crypto[from]}&tsyms=${crypto[to]}`)
.then(res => res.json())
.then(data => {
console.log('DATA', data[accessor] * amount);
if (which) {
setDataTwo(data[accessor] * amount);
} else {
setDataOne(data[accessor] * amount);
}
});
}
function getSelectorTwo(val) {
setSelectTwo(val.target.value);
console.log(val.target.value);
if ((SelectOne !== null || SelectTwo !== null) && SelectOne !== SelectTwo) {
makeRequest(SelectOne, SelectTwo, DataOne, true);
}
}
function getSelectorOne(val) {
setSelectOne(val.target.value);
if ((SelectOne !== null || SelectTwo !== null) && SelectOne !== SelectTwo) {
makeRequest(SelectOne, SelectTwo, DataOne, true);
}
}
// Here, you were using DataOne instead of the new value from your input
function getValueOne(val) {
setDataOne(val.target.value);
console.log(val.target.value);
if ((SelectOne !== null || SelectTwo !== null) && SelectOne !== SelectTwo) {
makeRequest(SelectOne, SelectTwo, val.target.value, true);
}
}
// Here, you were using DataTwo instead of the new value from your input
function getValueTwo(val) {
setDataTwo(val.target.value);
if ((SelectOne !== null || SelectTwo != null) && SelectOne !== SelectTwo) {
makeRequest(SelectTwo, SelectOne, val.target.value, false);
}
}
return (
<div className="bg-sky-500 h-screen w-screen">
<div className='container mx-auto '>
<div className='grid grid-cols-1 gap-4'>
<div>
<select className='rounded-full text-xl' onChange={getSelectorOne}>
<option value={27}>Select currency to send:</option>
<option value={0}>Bitcoin</option>
<option value={1}>Ethereum</option>
<option value={2}>DASH</option>
<option value={3}>Polkadot</option>
<option value={4}>Monero</option>
<option value={5}>Binance Smart Chain</option>
<option value={6}>Bitcoin Cash</option>
<option value={7}>Ethereum Classic</option>
<option value={8}>Zcash</option>
<option value={9}>Solana</option>
<option value={10}>Litecoin</option>
<option value={11}>Tron</option>
<option value={12}>0x</option>
<option value={13}>Ripple</option>
<option value={14}>Tether (ERC20)</option>
<option value={15}>Tether (TRC20)</option>
<option value={16}>Tezos</option>
<option value={17}>Polygon</option>
<option value={18}>Shiba Inu</option>
<option value={19}>Dogecoin</option>
</select>
</div>
<div>
<select className='rounded-full text-xl' onChange={getSelectorTwo}>
<option value={27}>Select currency to send:</option>
<option value={0}>Bitcoin</option>
<option value={1}>Ethereum</option>
<option value={2}>DASH</option>
<option value={3}>Polkadot</option>
<option value={4}>Monero</option>
<option value={5}>Binance Smart Chain</option>
<option value={6}>Bitcoin Cash</option>
<option value={7}>Ethereum Classic</option>
<option value={8}>Zcash</option>
<option value={9}>Solana</option>
<option value={10}>Litecoin</option>
<option value={11}>Tron</option>
<option value={12}>0x</option>
<option value={13}>Ripple</option>
<option value={14}>Tether (ERC20)</option>
<option value={15}>Tether (TRC20)</option>
<option value={16}>Tezos</option>
<option value={17}>Polygon</option>
<option value={18}>Shiba Inu</option>
<option value={19}>Dogecoin</option>
</select>
</div>
<div >
<input type={'number'} onChange={getValueOne} value={DataOne} placeholder={'Amout in'} className='p-6 rounded-lg bg-sky-600 outline-none'></input>
</div>
<div className=''>
<input type={'number'} onChange={getValueTwo} value={DataTwo} placeholder={'Amount out'} className='p-6 rounded-lg bg-sky-600 outline-none'></input>
</div>
</div>
</div>
</div>
)
}
export default App;
Your functions were using the old values calling makeRequest(). You were getting val.target.value, which is the new value, but your original value was null, so it would get the value 14.49 and multiply it by 0, giving you a DataTwo of 0!
Setting react state is asynchronous.
If you create a state like this:
const [SelectOne, setSelectOne] = useState(null);
And then you call:
setSelectOne(val.target.value);
console.log(val.target.value); // new value
console.log(SelectOne); // old value
Then SelectOne will not have a new value until your component renders again, some milliseconds later.
What you want here is an effect that calls makeRequest when one of the state values it depends on changes.
useEffect(() => {
if (SelectOne || SelectTwo != null && SelectOne != SelectTwo) {
makeRequest(SelectOne, SelectTwo, DataOne, true);
// You will have to figure out this last boolean argument, though
}
}, [SelectOne, SelectTwo, DataOne, DataTwo])
And now getSelectorOne() (and its friends) is simply something like:
function getSelectorOne(val) {
setSelectOne(val.target.value);
}
Then when that is called:
The component re-renders due to the state change.
The effect notices that SelectOne in the dependency array has changed since the last render
The function is executed, making your request
In summary, your functions that change state should not also directly trigger hidden side effects.
Instead, your state changes themselves should trigger side effects via useEffect() with a dependency array that includes the state values that the effect depends on.
This form works correctly as a regular form - submitting retains the dropdown values. When I use RHF's reset() to load data from the URL parameters, The input boxes show the default values but the select fields do not reflect the default values.
The functionality I'm going for here, is to load the values from the URL querystring params, which look like this:
http://localhost:3000/search?search=dev&location=&environment=On-Site&job_type=Internship&industry=&industry_category=&date_posted=&offset=0&count=25
...effectively making it "stateless", so we're able to pass this URL to anyone and it loads the form values, performs the search, etc.
import React, { useContext, useEffect, useState, useRef } from "react";
import { useForm, Controller } from "react-hook-form";
import InputMask from "react-input-mask";
import useLookupService from "../../service/lookup_service";
const Search = props => {
const { onSearch } = props;
const lookupSvc = useLookupService();
const [searchFormDefaults, setSearchFormDefaults] = useState(null);
const [locationOptions, setLocationOptions] = useState([]);
const [jobTypeOptions, setJobTypeOptions] = useState([]);
const [workEnvironmentOptions, setWorkEnvironmentOptions] = useState([]);
const [industryOptions, setIndustryOptions] = useState([]);
const [industryCategoryOptions, setIndustryCategoryOptions] = useState([]);
const {
register,
setValue,
handleSubmit,
control,
reset,
formState: { errors }
} = useForm({
defaultValues: searchFormDefaults,
context: JobSearchForm.type,
resolver: validator.validateResolver
});
useEffect(() => {
loadData();
}, []);
useEffect(() => {
if (!window.location.search) {
return;
}
const queryParams = new URLSearchParams(window.location.search);
let defaults = Object.fromEntries(queryParams);
setSearchFormDefaults(defaults);
reset(defaults);
}, [reset]);
const loadData = async () => {
let [
jobTypesData,
workEnvironmentsData,
industriesData,
industryCategoriesData
] = await Promise.all([
lookupSvc.getJobTypes(),
lookupSvc.getWorkEnvironments(),
lookupSvc.getIndustries(),
lookupSvc.getIndustryCategories()
]);
setJobTypeOptions([
<option key={-1} value="">Please select...</option>,
jobTypesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setWorkEnvironmentOptions([
<option key={-1} value="">Please select...</option>,
workEnvironmentsData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIndustryOptions([
<option key={-1} value="">Please select...</option>,
industriesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIndustryCategoryOptions([
<option key={-1} value="">Please select...</option>,
industryCategoriesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
};
return (
<div>
<div>
<div>
Filters
</div>
<div>
<a onClick={onClearClick}>
Clear Filters
</a>
</div>
</div>
<form onSubmit={handleSubmit(onSearch)} autoComplete="off">
<div>
<div>
<div>
<label>Search</label>
</div>
<input type="text" {...register("search")} />
{errors.search && <span className="validation">{errors.search}</span>}
</div>
<div>
<div>
<label>Location</label>
</div>
<input type="text" {...register("location")} onChange={onLocationChange} />
{errors.location && <span className="validation">{errors.location}</span>}
</div>
<div>
<div>
<label>Work Environment</label>
</div>
<select {...register("environment")}>
{workEnvironmentOptions}
</select>
</div>
<div>
<div>
<label>Date Posted</label>
</div>
<Controller
control={control}
name="date_posted"
render={({ field }) => (
<InputMask {...field} mask="99/99/9999" />
)}
/>
{errors.date_posted && <span className="validation">{errors.date_posted}</span>}
</div>
<div>
<div>
<label>Job Type</label>
</div>
<select {...register("job_type")}>
{jobTypeOptions}
</select>
</div>
<div>
<div>
<label>Job Industry</label>
</div>
<select {...register("industry")}>
{industryOptions}
</select>
</div>
<div>
<div>
<label>Industry Category</label>
</div>
<select {...register("industry_category")}>
{industryCategoryOptions}
</select>
</div>
<button type="submit">
Search
</button>
</div>
</form>
</div>
);
};
export default Search;
Note that the selected values and display values are set to the same - a string such as "On-Site".
While debugging, the first useEffect() is firing and loading the data before the second one, with reset as its param. I thought perhaps the lists were being reloaded and wiping out the selected value, but I don't see evidence of that.
When setting a breakpoint on the return line of the component, searchFormDefaults contains the expected values, yet they're always set to the default value on the screen (the initial "Please select..." when they're created.)
This is what I see for searchFormDefaults on the final render:
{
"search": "dev",
"location": "",
"environment": "On-Site",
"job_type": "Internship",
"industry": "",
"industry_category": "",
"date_posted": "",
"offset": "0",
"count": "25"
}
I'm fairly new to react-hook-form so I'm sure I've done something goofy, but I'm unable to spot it or find an explanation anywhere.
Figured it out. Was just an async issue that a simple flag solved.
const [isDataLoaded, setIsDataLoaded] = useState(false);
...then in my data loading function:
const loadData = async () => {
let [
jobTypesData,
workEnvironmentsData,
industriesData,
industryCategoriesData
] = await Promise.all([
lookupSvc.getJobTypes(),
lookupSvc.getWorkEnvironments(),
lookupSvc.getIndustries(),
lookupSvc.getIndustryCategories()
]);
setJobTypeOptions([
<option key={-1} value="">Please select...</option>,
jobTypesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setWorkEnvironmentOptions([
<option key={-1} value="">Please select...</option>,
workEnvironmentsData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIndustryOptions([
<option key={-1} value="">Please select...</option>,
industriesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIndustryCategoryOptions([
<option key={-1} value="">Please select...</option>,
industryCategoriesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIsDataLoaded(true);
};
...and the hook that needs it:
useEffect(() => {
if (!isDataLoaded) {
return;
}
if (!window.location.search) {
return;
}
const queryParams = new URLSearchParams(window.location.search);
let defaults = Object.fromEntries(queryParams);
setSearchFormDefaults(defaults);
reset(defaults);
}, [reset, isDataLoaded]);
I need to have this feature, when the cancel button is clicked, all the previous inputs shall be cleared. I mean, if the value in the textfield or scroll-bar should be reset to default. This is how I got now, the value still exists even when I click the cancel button (<button type="delete" onClick={deleteFilter} >X</button>) although the filter is disabled:
const [stateSelected, setStateSelected] = useState('')
const [jobType, setJobType] = useState('');
const [type, setType] = useState('');
var states = statepicker.getStates('us');
document.body.style = 'position:absolute;';
const stateChange = e => {
setStateSelected(e.target.value)
}
const jobChange = e => {
setJobType(e.target.value)
}
const typeChange = e => {
setType(e.target.value)
}
function filterOn(){
if(filter.position != "" || filter.state != "" || filter.type != ""){
return true;
} else{
return false;
}
}
function deleteFilter(){
setFilter({
position: "",
state: "",
type: ""
})
}
const handleSignUp = useCallback(async event => {
event.preventDefault();
setFilter({
position: jobType,
state: stateSelected,
type: type
});
setFilterOpen(false);
setJob([]);
})
return (
<div>
<div className="settings__Section">
<div className="settings__SectionHeader">
Filter
</div>
<form onSubmit={handleSignUp}>
<input className="settings__inputBox" name="occupation" type="occupation" placeholder="Job Title" onChange={jobChange}/>
<select className="settings__inputSelect" onChange={stateChange} value={stateSelected}>
<option value={""} disabled={true}>Select State</option>
{
states.map(state => (
<option value={state} name="state">{state}</option>
))
}
</select>
<select className="settings__inputSelect" onChange={typeChange} value={type}>
<option value={""} disabled={true}>Select Job Type</option>
<option value={"Entry Level"} name="type">Entry Level</option>
<option value={"Internship"} name="type">Internship</option>
<option value={"Fellowship"} name="type">Fellowship</option>
<option value={"Apprenticeship"} name="type">Apprenticeship</option>
</select>
<button type="submit" className="settings__signBtn">Save Filter</button>
</form>
{filterOn() &&
<div>
<p>Position: {filter.position} Location: {filter.state} Job Type: {filter.type}</p>
<button type="delete" onClick={deleteFilter} >X</button>
</div>
}
</div>
</div>
);
<form onSubmit={handleSignUp}>
<input className="settings__inputBox" name="occupation" type="occupation" placeholder="Job Title" onChange={jobChange}/>
<select className="settings__inputSelect" onChange={stateChange} value={stateSelected}>
<option value={""} disabled={true}>Select State</option>
{
states.map(state => (
<option value={state} name="state">{state}</option>
))
}
</select>
<button type="submit" className="settings__signBtn">Save Filter</button>
<button type="reset" >X</button>
</form>
Try with reset button it does the thing here I suppose
Ok, although there is some lines missing in your code i think i know what the problem is.
The filter renders if either position, state, or type is different from empty string.
But when you delete the filter you are only resetting position and state:
function deleteFilter(){
setFilter({
position: "",
state: ""
})
}
So to fix it, just add type to this state function being called:
function deleteFilter () {
setFilter({
position: '',
state: '',
type: ''
})
}
So I have a React component like:
export default function ExampleComponent() {
return (
<div>
<select required name="select1">
<option label=" "></option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
<select name="select2">
<option label=" "></option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
</div>
);
}
Continuing on for multiple more selects...
I want it so that when I select one option in any of the select tags, it gets removed as an option in all of the others. If I change the selected option, it then becomes available again in all of the others. Does anyone know how I would be able to do this in React?
There may be a more elegant way to do this, but my thought was to keep track of which select have chosen which option with a object called chosenObjects then filtering results based on that information.
import React, {useState} from "react";
export default function ExampleComponent() {
const selectNames = ["select1", "select2"];
const [options] = useState([
{
label: 'Option 1',
value: 'option1'
},
{
label: 'Option 2',
value: 'option2'
},
{
label: 'Option 3',
value: 'option3'
}
]);
const [chosenOptions, setChosenOptions] = useState({});
const isChosenByOther = (optionValue, selectName) => {
for (let key in chosenOptions) {
if (key !== selectName) {
if (chosenOptions[key] === optionValue) {
return true;
}
}
}
return false;
};
const handleChange = (ev) => {
setChosenOptions({...chosenOptions, [ev.target.name]: ev.target.value});
};
return (
<div>
{selectNames.map((name, index) => {
return (
<select name={name} key={index} onChange={handleChange} value={chosenOptions[name] || ''}
required={index === 0}>
<option value=''/>
{options.filter(({value}) => !isChosenByOther(value, name))
.map(({label, value}, oIndex) =>
<option value={value} key={oIndex}>{label}</option>)
}
</select>
)
})}
</div>
);
}
You can also conditionally disable your empty string valued option after something is chosen:
<div>
{selectNames.map((name, index) => {
return (
<select name={name} key={index} onChange={handleChange} value={chosenOptions[name] || ''}
required={index === 0}>
<option value='' disabled={chosenOptions[name]}>Choose Option</option>
{options.filter(({value}) => !isChosenByOther(value, name))
.map(({label, value}, oIndex) =>
<option value={value} key={oIndex}>{label}</option>)
}
</select>
)
})}
</div>
You have to use state to manage the value and share it between two selects.
import React, {useState} from 'react'
export default function ExampleComponent() {
const [value, setValue] = useState('option0')
handleChange = (e) => {
setValue(e.target.value)
}
return (
<div>
<select value={value} onChange={handleChange}>
<option value="option0"></option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
<select value={value} onChange={handleChange}>
<option value="option0"></option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
</div>
);
}
Why don't you use state? You can use same datasource for options and also same event handlers for all selects.
export default function ExampleComponent() {
state = {
options: [
{
name: 'Option 1',
value: 'option1'
},
{
name: 'Option 2',
value: 'option2'
},
{
name: 'Option 3',
value: 'option3'
}
]
};
onChangeSelect = event => {
// do something
this.setState({
options: this.state.options.filter(
option => option.value !== event.target.value
)
});
};
return (
<div>
<select required name="select1" onChange={onChangeSelect}>
<option label=" "></option>
{this.state.options.map(option => (
<option value={option.value}>{option.name}</option>
))}
</select>
<select name="select2" onChange={onChangeSelect}>
<option label=" "></option>
{this.state.options.map(option => (
<option value={option.value}>{option.name}</option>
))}
</select>
</div>
);
}
You need to track the state of multiple Select elements, and then conditionally render the options for each one.
I'll use some bits from the other answers and show how to make it work. Edited to keep the options in the same order.
import React, {useState} from "react";
function ExampleComponent() {
const [myState, setMyState] = useState({});
const options = [
{
name: 'Option 1',
value: 'option1'
},
{
name: 'Option 2',
value: 'option2'
},
{
name: 'Option 3',
value: 'option3'
}
]
// list of select names
const selectNames = [
"select1",
"select2"
]
handleChange = (e) => {
// update the value for the given select
myState[e.target.name] = e.target.value
setMyState(myState)
}
const inUseValues = Object.values(myState);
console.log(inUseValues)
// get the options for given name
getOptions = (name) =>
options.filter(option => !inUseValues.includes(option.value) || myState[name]==option.value)
.map(option => (
<option key={option.value} value={option.value}>{option.name}</option>
))
// render all select controls
const selectRender = selectNames.map(name => (
<select key={name} required name="{name}" value={myState[name]} onChange={handleChange}>
<option label=" "></option>
{getOptions(name)}
</select>
))
return (
<div>
{selectRender}
</div>
)
}
🚨 CODE DUPLICATION 🚨
So I am trying to have an inital state object like: {prop1: val1, prop2: val2} and get seperate values from two <selection> fields.
I am having problems with getting each event.target.value on submit.
The only solution I came up with is splitting the original state into different objects, duplicate the handler functions and merge them on submit. But as you can see this is getting ugly pretty quick, I am sure that this could be optimized but I am getting nowhere with the unique event handlers.
const [firstSelection, setFirstSelection] = useState({color: 'green'})
const [secondSelection, setSecondSelection] = useState({time: 'evening'})
const handleFirstChange = e => setFirstSelection({ color: e.target.value })
const handleSecondChange = e => setSecondSelection({ time: e.target.value })
const getSelection = e => {
e.preventDefault()
console.log({...firstSelection, ...secondSelection})
}
const Form = () => {
return (
<form>
<div className='selectWrapper'>
<select name='color' value={firstSelection.color} onChange={handleFirstChange}>
<option value='red'>Red</option>
<option value='blue'>Blue</option>
<option value='yellow'>Yellow</option>
<option value='green'>Green</option>
</select>
</div>
<div className='selectWrapper'>
<select name='time' value={secondSelection.time} onChange={handleSecondChange}>
<option value='morning'>Morning</option>
<option value='evening'>Evening</option>
<option value='night'>Night</option>
</select>
</div>
<input type='submit' value='Submit' onClick={getSelection}/>
</form>
)
}
Try this :
export default function Form() {
const [selection, setSelection] = useState({ color: "red", time: "morning" });
const handleChange = (e) => {
setSelection({ ...selection, [e.target.name]: e.target.value });
};
const getSelection = (e) => {
e.preventDefault();
console.log(selection);
};
return (
<form>
<div className="selectWrapper">
<select
name="color"
value={selection.color}
onChange={(e) => handleChange(e)}
>
<option value="red">Red</option>
<option value="blue">Blue</option>
<option value="yellow">Yellow</option>
<option value="green">Green</option>
</select>
</div>
<div className="selectWrapper">
<select
name="time"
value={selection.time}
onChange={(e) => handleChange(e)}
>
<option value="morning">Morning</option>
<option value="evening">Evening</option>
<option value="night">Night</option>
</select>
</div>
<input type="submit" value="Submit" onClick={getSelection} />
</form>
);
}
You can try to create just one fuction to update state that use the names of the <select>s tags as keys of the state object. And concentrate all the data into one state. I will call this single state as formState
const [formState, setFormState] = useState({color: 'green', time: 'evening'})
const handleInput = e => setFormState({
...formState,
[e.target.name]: e.target.value
})
const getSelection = e => {
e.preventDefault()
console.log(formState)
}
const Form = () => {
return (
<form>
<div className='selectWrapper'>
<select name='color' value={firstSelection.color} onChange={handleInput}>
<option value='red'>Red</option>
<option value='blue'>Blue</option>
<option value='yellow'>Yellow</option>
<option value='green'>Green</option>
</select>
</div>
<div className='selectWrapper'>
<select name='time' value={secondSelection.time} onChange={handleInput}>
<option value='morning'>Morning</option>
<option value='evening'>Evening</option>
<option value='night'>Night</option>
</select>
</div>
<input type='submit' value='Submit' onClick={getSelection}/>
</form>
)
}
You could create a reusable component for select and use object for selection value, retrieved by input name
const CustomSelect = ({ name, options, value, onChange }) => {
return (
<div className="selectWrapper">
<select name={name} value={value} onChange={onChange}>
{options.map((option) => (
<option value={option.value}>{option.label}</option>
))}
</select>
</div>
);
};
export default function Form() {
const selectData = [
{
name: "color",
options: [
{ label: "Red", value: "red" },
{ label: "Blue", value: "blue" },
{ label: "Yellow", value: "yellow" },
{ label: "Green", value: "green" }
]
},
{
name: "time",
options: [
{ label: "Morning", value: "morning" },
{ label: "Evening", value: "evening" },
{ label: "Night", value: "night" }
]
}
];
const [selection, setSelection] = useState({
color: "green",
time: "evening"
});
const getSelection = (e) => {
e.preventDefault();
console.log(selection);
};
return (
<div className="App">
<form>
{selectData.map((sd) => (
<CustomSelect
name={sd.name}
options={sd.options}
value={selection[sd.name]}
onChange={(event) =>
setSelection({
...selection,
[event.target.name]: event.target.value
})
}
/>
))}
<input type="submit" value="Submit" onClick={getSelection} />
</form>
</div>
);
}
Codesandbox demo