So I have the below code:
function Crafting(props) {
const craftItems = [
{
key: 1,
name: "Bronze sword",
},
{
key: 2,
name: "Iron sword",
},
{
key: 3,
name: "Steel sword",
},
];
const [item, setItem] = useState();
const [itemKey, setItemKey] = useState();
function onChangeHandler(event) {
setItem(event.target.value);
setItemKey(event.target.itemID);
console.log(event);
}
function onSubmitHandler(event) {
event.preventDefault();
console.log(item);
console.log(itemKey);
}
return (
<Card className={classes.renderwin}>
<form onSubmit={onSubmitHandler}>
<label htmlFor="items">Select an item</label>
<select name="items" onChange={onChangeHandler}>
{craftItems.map((item) => (
<option key={item.key} itemID={item.key}> {item.name} </option>
))}
</select>
<Button type="submit">Craft item</Button>
</form>
</Card>
);
}
I want to be able to retrieve the original key value against the item. I tried using "key", then added in a custom prop called "itemID" but they both just return as undefined. How do I fetch the ID back based on the value selection?
The issue is that you can basically store only an option value and retrieve that in the handler to save into state.
I would place the item.key on the option element's value attribute.
<select name="items" onChange={onChangeHandler}>
{craftItems.map((item) => (
<option key={item.key} value={item.key}>
{item.name}
</option>
))}
</select>
Access the onChange event object's value property in the handler and convert it back to a Number type before storing in state.
function onChangeHandler(event) {
const { value } = event.target;
setItemKey(Number(value));
}
When accessing in the submit handler use the stored itemKey to search the craftItems array for the matching object.
function onSubmitHandler(event) {
event.preventDefault();
const item = craftItems.find(({ key }) => key === itemKey);
console.log(item?.name);
console.log(item?.key);
}
function Crafting(props) {
const craftItems = [
{
key: 1,
name: "Bronze sword"
},
{
key: 2,
name: "Iron sword"
},
{
key: 3,
name: "Steel sword"
}
];
const [itemKey, setItemKey] = React.useState();
function onChangeHandler(event) {
const { value } = event.target;
setItemKey(Number(value));
console.log({ value });
}
function onSubmitHandler(event) {
event.preventDefault();
const item = craftItems.find(({ key }) => key === itemKey);
console.log(item && item.name);
console.log(item && item.key);
}
return (
// <Card className={classes.renderwin}>
<form onSubmit={onSubmitHandler}>
<label htmlFor="items">Select an item</label>
<select name="items" onChange={onChangeHandler}>
{craftItems.map((item) => (
<option key={item.key} value={item.key}>
{item.name}
</option>
))}
</select>
<button type="submit">Craft item</button>
</form>
// </Card>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<Crafting />,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root" />
Place a value on the option like this:
{craftItems.map((item) => (
<option value={item.key} key={item.key}>{item.name}</option>
))}
Related
This is my checkbox components for multi selection.
const MultiselectCheckbox = ({ options, onChange, limitedCount }) => {
const [data, setData] = React.useState(options);
const toggle = index => {
const newData = [...data];
newData.splice(index, 1, {
label: data[index].label,
checked: !data[index].checked
});
setData(newData);
onChange(newData.filter(x => x.checked));
};
return (
<>
{data.map((item, index) => (
<label key={item.label}>
<input
readOnly
type="checkbox"
checked={item.checked || false}
onClick={() => toggle(index)}
/>
{item.label}
</label>
))}
</>
);
};
const options = [{ label: 'Item One' }, { label: 'Item Two' }];
ReactDOM.render(
<MultiselectCheckbox
options={options}
onChange={data => {
console.log(data);
}}
/>,
document.getElementById('root')
);
I want to limit the items I can choose by putting limitedCount in my code.
props for example
limitedSelectCount = 1
Only one check box can be selected
limitedSelectCount = n
Multiple n check boxes available
You can add a condition inside the toggle function.
const handleOptionSelection = (optionKey: string, isChecked: boolean) => {
let selectedOptionKeysCopy = [...selectedOptionKeys];
if (isChecked && selectedOptionKeysCopy.length <= maxSelections) {
selectedOptionKeysCopy = unique([...selectedOptionKeysCopy, optionKey]);
} else {
selectedOptionKeysCopy = selectedOptionKeysCopy.filter(
(selectedOptionKey) => selectedOptionKey !== optionKey
);
}
onChangeSelected(selectedOptionKeysCopy);
};
Here is a sample for reference.
its very easy just put a condition in your toggle function to check if
the length of data array is not greater or equal to the limitedCount. Also
check if the limitedCount prop has been passed to the component or
not.
UPDATE
Also just you need to see if the user has checked the option or unchecked the option, so pass a check into toggle function.
const MultiselectCheckbox = ({ options, onChange, limitedCount }) => {
const [data, setData] = React.useState(options);
const toggle = (check,value) => {
// add below line to code
if(limitedCount && data.length>=limitedCount && check) return;
const newData = [...data];
newData.splice(index, 1, {
label: data[index].label,
checked: !data[index].checked
});
setData(newData);
onChange(newData.filter(x => x.checked));
};
return (
<>
{data.map((item, index) => (
<label key={item.label}>
<input
readOnly
type="checkbox"
checked={item.checked || false}
onClick={(e) => toggle(e.target.checked,index)}
/>
{item.label}
</label>
))}
</>
);
};
const options = [{ label: 'Item One' }, { label: 'Item Two' }];
ReactDOM.render(
<MultiselectCheckbox
options={options}
onChange={data => {
console.log(data);
}}
/>,
document.getElementById('root')
);
I feel like i've read so many examples/docs and watched videos but i can't get my head around using selectors with recoil. Maybe i'm just having a bad day cause i don't think it's that hard.
This is where i'm at...
lets say i have data like:
const data = [
{
name: "wiggles",
type: "cat",
age: "244",
},
{
name: "wobbles",
type: "dog",
age: "55",
},
{
name: "flobbles",
type: "dog",
age: "98",
},
];
in my index i have:
export const typeFilterState = atom({
key: 'typeFilterState',
default: '',
});
const UserPage: NextPage<Props> = ({data}) => {
const [typeFilter, setTypeFilter] = useRecoilState(typeFilterState);
return(
<select onChange={(e) => setLeagueFilter(e.target.value)}>
<option value='cat'>Cat</option>
<option value='dog'>Dog</option>
<option value='elephant'>Elephant</option>
</select>
{
data.map((d) => (
<DataComponent data={d} />
))
}
)};
DataComponent would be like:
const DataComponent: FunctionComponent<Animals> = ({data}) => {
return (
<Text>{data.name}</Text>
<Text>{data.type}</Text>
<Text>{data.age}</Text>
)
};
I want it so if users select cat it only shows data with cats, but also to show ALL data if nothing is selected...
There's a typo in your <select> onChange function:
// <select onChange={(e) => setLeagueFilter(e.target.value)}>
<select onChange={(e) => setTypeFilter(e.target.value)}>
Just filter the array based on the typeFilter value before mapping as components:
// data.map((d) => (
// <DataComponent data={d} />
// ))
data
.filter(({type}) => {
// nothing selected yet, keep all:
if (!typeFilter.trim()) return true;
// else keep if item.type equals filter:
return type === typeFilter;
})
.map((d) => (
<DataComponent data={d} />
))
In my application i use a search input to search values, and select input also to filter values from my data. Now my component looks like below:
export default function App() {
const [myData, setMydata] = useState([]);
const searchData = e => {
const v = e.target.value;
const res= data.filter(i =>
i.name.toLowerCase().includes(v.toLowerCase())
);
setMydata(res);
};
function handleChange(value) {
const res= data.filter(i => i.age === Number(value));
setMydata(res);
}
return (
<div className="App">
<Select defaultValue="" style={{ width: 120 }} onChange={handleChange}>
<Option value="2">2</Option>
<Option value="32">32</Option>
</Select>
<input onChange={searchData} />
<h1>Data</h1>
{myData.map((i, k) => (
<div key={k}>
{i.name} is <span>{i.age}</span>
</div>
))}
</div>
);
}
Now, the functionality works. If you search something, appear results, and if you try to select a value, also appears the value that you selected.
Issue: If i select from dropdown, for example: 32, appears:
Julia is 32
Bill is 32
Bill is 32
And now if i want to search from the list above just Julia, i type Julia in search, it search from the whole list of data, not just from the list which i get after i selected 32. How to solve this, and how to get the result from the last results, not to search from the whole list, but from the last result?
Note: the same issue is when i search first and after that i select a value from dropdown.
Your two filters always work with the same object data, and not previously filtered state data myData. Best practice save value of filters in state and each render filter data:
export default function App() {
const [age, setAge] = useState('');
const [name, setName] = useState('');
const filteredData = data
.filter(i => Boolean(age) ? i.age === Number(age) : true)
.filter(i => i.name.toLowerCase().includes(name.toLowerCase()));
return (
<div className="App">
<Select value={age} style={{ width: 120 }} onChange={setAge}>
<Option value="2">2</Option>
<Option value="32">32</Option>
</Select>
<input value={name} onChange={e => setName(e.target.value)} />
<h1>Data</h1>
{filteredData.map((i, k) => (
<div key={k}>
{i.name} is <span>{i.age}</span>
</div>
))}
</div>
);
}
Try this one out:
import React, { useState } from "react";
export default function App() {
const data = [
{ age: 2, name: 'John' },
{ age: 32,name: 'Mark' },
{ age: 22,name: 'Dell' },
{ age: 14,name: 'Linda' },
{ age: 16,name: 'Jon' },
{ age: 18,name: 'Ron' }
];
const [myData, setMydata] = useState([]);
const searchData = e => {
const v = e.target.value;
const res = data.filter(i =>
i.name.toLowerCase().includes(v.toLowerCase())
);
setMydata(res);
};
function handleChange(value) {
const res = data.filter(i => i.age === Number(value.target.value));
console.log("This is the value and respos", value.target.value);
setMydata(res);
}
return (
<div>
<select defaultValue="" style={{ width: 120 }} onChange={handleChange}>
<option value="2">2</option>
<option value="32">32</option>
</select>
<input onChange={searchData} />
<h1>Data</h1>
{myData.map((i, k) => (
<div key={k}>
{i.name} is <span>{i.age}</span>
</div>
))}
</div>
);
}
Here is the codesandbox demo: Demo
I have this cascading dropdown in class component form and it is working fine but I want to convert it to functional one with React Hooks. It is a Country, State, City dropdown. When a user select country, the states under it will load. When they select a state, the cities under it will load. I have changed the constructor part to start using state as seen below. Also, the componentDidMount has been converted to useEffect. The handlers have also been converted.
But the render part below where the form is displayed is giving me tough time. When I tried, I was getting the error that it is not properly arranged. How can I convert the part inside the container div from using this. to be in sync with the rest of the function. I know I'll have to do away with this. but I don't know how to go about it? The sample function is posted below.
function LocationDropdown() {
const [state, setState] = useState({
countries : [],
states : [],
lgas : [],
selectedCountry : '--Choose Country--',
selectedState : '--Choose State--'
});
useEffect(() => {
setState(prevState => ({
...prevState,
countries : [
{ name: 'Nigeria', value: 'nigeria',
states: [ {name: 'Abia', value: 'abia',
lgas: [
{name: "Aba", value: 'aba'},
{name: "Oru", value: 'oru'},
]}, {name: 'Adamawa', value: 'adamawa',
lgas: [
{name: 'Demsa', value: 'demsa'},
{name: 'Fufure', value: 'fufure'},
]},
},
]
}));
}, [])
changeCountry(event) {
this.setState({selectedCountry: event.target.value});
this.setState({states : this.state.countries.find(cntry => cntry.name === event.target.value).states});
}
changeState(event) {
this.setState({selectedState: event.target.value});
const stats = this.state.countries.find(cntry => cntry.name === this.state.selectedCountry).states;
this.setState({lgas : stats.find(stats => stats.name === event.target.value).lgas});
}
render() {
return (
<div id="container">
<h2>Cascading or Dependent Dropdown using React</h2>
<div>
<Label>Country</Label>
<Select placeholder="Country" value={this.state.selectedCountry} onChange={this.changeCountry}>
<option>--Choose Country--</option>
{this.state.countries.map((e, key) => {
return <option key={key}>{e.name}</option>;
})}
</Select>
</div>
<div>
<Label>State</Label>
<Select placeholder="State" value={this.state.selectedState} onChange={this.changeState}>
<option>--Choose State--</option>
{this.state.states.map((e, key) => {
return <option key={key}>{e.name}</option>;
})}
</Select>
</div>
<div>
<Label>LGA</Label>
<Select placeholder="LGA" value={this.state.selectedLga}>
<option>--Choose LGA--</option>
{this.state.lgas.map((e, key) => {
return <option key={key}>{e.name}</option>;
})}
</Select>
</div>
</div>
)
}
}
export default LocationDropdown;
I changed your code. I renamed state and setState, so it's not confusing with actual States. Also I changed Select and Label to normal html element so i can test.
import React, { useEffect, useState } from "react";
function LocationDropdown() {
const [myData, setMyData] = useState({
countries: [],
states: [],
lgas: [],
selectedCountry: "--Choose Country--",
selectedState: "--Choose State--"
});
useEffect(() => {
setMyData(prevState => ({
...prevState,
countries: [
{
name: "Nigeria",
value: "nigeria",
states: [
{
name: "Abia",
value: "abia",
lgas: [
{ name: "Aba", value: "aba" },
{ name: "Oru", value: "oru" }
]
},
{
name: "Adamawa",
value: "adamawa",
lgas: [
{ name: "Demsa", value: "demsa" },
{ name: "Fufure", value: "fufure" }
]
}
]
}
]
}));
}, []);
const mergeAndUpdateMyData = newData => {
setMyData({ ...myData, ...newData });
};
const changeCountry = event => {
mergeAndUpdateMyData({
selectedCountry: event.target.value,
states: myData.countries.find(cntry => cntry.name === event.target.value)
.states
});
};
const changeState = event => {
const stats = myData.countries.find(
cntry => cntry.name === myData.selectedCountry
).states;
mergeAndUpdateMyData({
selectedState: event.target.value,
lgas: stats.find(stats => stats.name === event.target.value).lgas
});
};
return (
<div id="container">
<h2>Cascading or Dependent Dropdown using React</h2>
<div>
<label>Country</label>
<select
placeholder="Country"
value={myData.selectedCountry}
onChange={changeCountry}
>
<option>--Choose Country--</option>
{myData.countries.map((country, key) => {
return (
<option value={country.name} key={key}>
{country.name}
</option>
);
})}
</select>
</div>
<div>
<label>State</label>
<select
placeholder="State"
value={myData.selectedState}
onChange={changeState}
>
<option>--Choose State--</option>
{myData.states.map((state, key) => {
return (
<option value={state.name} key={key}>
{state.name}
</option>
);
})}
</select>
</div>
<div>
<label>LGA</label>
<select placeholder="LGA" value={myData.selectedLga}>
<option>--Choose LGA--</option>
{myData.lgas.map((lga, key) => {
return (
<option value={lga.name} key={key}>
{lga.name}
</option>
);
})}
</select>
</div>
</div>
);
}
export default LocationDropdown;
EDITED: Please note that in React, when you set a state, the statement is async. This means when you call setMyData, the value of myData is not updated immediately. So you cannot call mergeAndUpdateMyData multiple times in a row.
By the way, you can use multiple useState in one function components. For example:
const [countries, setCountries] = useState();
const [lgas, setLags] = useState();
...
The code to get tables depends on how many items are selected in the left window in my image below but don't know What I need to write in the onClick function of the button to move the first selected item to the first table GMV1 and the second selected item to GMV2 and so on.
What I want to do is first check the status of contractLineItemSelectionChecked. If the status is true, check the selected items have different contract ids and push the different contract id objects to different places.
[
{
cNo: "CDH0000403",
contractLineItemSelectionChecked: true,
companyCode: "1360",
profitCenter: "DHA1",
approverId: "C7937"
},
{
cNo: "CDH0000404",
contractLineItemSelectionChecked: false,
companyCode: "1360",
profitCenter: "DHA1",
approverId: "C7937"
},
{
cNo: "CDH0000405",
contractLineItemSelectionChecked: true,
companyCode: "1360",
profitCenter: "DHA1",
approverId: "C7937"
}
];
<Field
name="transactionSelected"
component="select"
className="custom-select my-1 mr-sm-2"
id="moveContractLineItems"
onChange={e => {
setFieldValue("transactionSelected", "GMV+ /contract");
document.getElementById("creategoodsMovementsContractsTransaction").click();
getIndex();
}}
>
<option key="select" value="">
Select
</option>
<option key="GMV+ /contract" value="GMV+ /contract">
GMV+ /contract
</option>
<option key="PMT+/contract" value="PMT+/contract">
PMT+/Contract
</option>
</Field>;
<FieldArray name={`transactions["goodsMovements"].transactionItems`}>
{({ push }) => (
<input
type="button"
hidden
id="creategoodsMovementsContractsTransaction"
onClick={() => {
let myCounter = 0;
checkedCheckboxes.forEach(v =>
v.contractLineItemSelectionChecked ? myCounter++ : v
);
for (var i = 0; i < myCounter; i++) {
push({
name:
"GMV" +
(typeof values.transactions.goodsMovements.transactionItems !==
"undefined" &&
values.transactions.goodsMovements.transactionItems.length + 1),
transactionId: "",
transactionType: "1",
requestedPostingDate: null,
companyCode: "",
profitCenter: "",
attachments: [],
comments: "",
transactionLineItems: []
});
}
}}
/>
)}
</FieldArray>;
<button
type="button"
className="btn btn-primary my-1"
onClick={() => moveItems()}
>
Move
</button>;
Maybe the following will give you an idea how to do this:
const Item = React.memo(
//use React.memo to create pure component
function Item({ item, onChange, checked }) {
console.log('rendering:', item.id)
// can you gues why prop={new reference} is not a problem here
// this won't re render if props didn't
// change because it's a pure component
return (
<div>
<div>{item.name}</div>
<input
type="checkbox"
checked={checked}
onChange={() => onChange(item.id, !checked)}
/>
</div>
)
}
)
const SelectedItem = React.memo(
//use React.memo to create pure component
function SelectedItem({ item }) {
console.log('rendering selected:', item.id)
return (
<div>
<div>{item.name}</div>
</div>
)
}
)
class Parent extends React.PureComponent {
state = {
data: [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' }
],
checked: {}
}
toggle = (_id, add) => {
const newChecked = add
? {
...this.state.checked,
[_id]: true
}
: // cannot use Object.fromEntries because SO babel is too old
Object.entries(this.state.checked)
.filter(([key]) => Number(key) !== _id)
.reduce((result, [key, value]) => {
result[key] = value
return result
}, {})
this.setState({ checked: newChecked })
}
render() {
return (
<div>
<div>
<h4>items</h4>
{this.state.data.map(item => (
<Item
item={item}
onChange={this.toggle}
checked={Boolean(this.state.checked[item.id])}
key={item.id}
/>
))}
</div>
<div>
<h4>Selected items</h4>
{this.state.data
.filter(item => this.state.checked[item.id])
.map(item => (
<SelectedItem item={item} key={item.id} />
))}
</div>
</div>
)
}
}
//render app
ReactDOM.render(<Parent />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>