In a list of certain records where I'm populating in the dropdown of an input component. The default value is set on page loading but, I need to update it on click event of a button. Refer to the code below.
Home.js
const allList = [
{ id: "1", value: "Fruits" },
{ id: "2", value: "Vegetables" },
{ id: "3", value: "Grains" },
{ id: "4", value: "Milk" },
{ id: "5", value: "Meat" },
{ id: "6", value: "Fish" },
{ id: "7", value: "Pulses" }
];
const [itemList, setItemList] = useState(allList);
const [newValue, setNewValue] = useState(allList[0].value)
// I want to set the value on click of cancel button
const handleCancel = (e) => {
setNewValue(allList[2].value)
setPopup(false);
};
return (
<>
<DataList
defaultValue={newValue}
list="itemListOptions"
id="itemList"
placeholder="Search/select items"
data={itemList}
onSelectionChange={itemChanged}
></DataList>
{popup === true ? (
<Popup okbtnClick={handleOK} canclebtnclick={handleCancel} />
) : null}
</>
)
DataList.js
<input
defaultValue={props?.defaultValue ?? ""}
className="form-control cpselect"
id={props?.id ?? ""}
list={props?.list ?? ""}
placeholder={props?.placeholder ?? ""}
onChange={props?.onSelectionChange ?? ""}
/>
<datalist key={props.id} id={props?.list ?? ""}>
{props.data.map((d) => {
return <option key={d.id} id={d.id} value={d.value}></option>;
})}
</datalist>
My intention is to change the defaultValue inside the input field, on click of cancel button. Here it loads the first element and on click event should load third element. What is the best optimal solution?
Please refer to the Codesandbox link: https://codesandbox.io/s/clever-rumple-ig0wwj
What you want to do is to use value instead of defaultValue.
defaultValue property is configured in such a way that it reads the prop only once and then creates an inner state that handles changes.
Related
I am making a simple react application where there are dropdowns in which one dependent on another.
-> Here dropdown 1 has the value as type of game like Indoor and Outdoor.
-> Here dropdown 2 has the value as type of sport like Chess , Tennis and Football .
Requirement:
The following different use cases needs to be covered,
Scenarios:
-> User selects Indoor from dropdown 1, then in dropdown 2 only the value of Chess needs to be enabled and others needs to be
disabled.
-> User selects Outdoor from dropdown 1, then in dropdown 2 only the value of Tennis and Football needs to be enabled and option Chess
needs to be disabled.
Vice versa:
-> User selects Chess from dropdown 2, then in dropdown 1 only the value of Indoor needs to be enabled and others needs to be
disabled.
-> User selects Tennis or Football from dropdown 2, then in dropdown 1 only the value of Outdoor needs to be enabled and others needs to be disabled.
Here we provide option of allowClear so that user can reset their selection in any select box selection (the close icon) and do the above mentioned scenario in any way like selecting option from first dropdown or in second dropdown based on which the another dropdown make the option enable or disable.
Right now I have a data like this and open for modification to achieve the expected result.
const data = {
games: {
type: [
{ id: 1, value: "Indoor", sportId: [2] },
{ id: 2, value: "Outdoor", sportId: [1, 3] }
],
sport: [
{ id: 1, value: "Tennis", typeId: [2] },
{ id: 2, value: "Chess", typeId: [1] },
{ id: 3, value: "Football", typeId: [2] }
]
}
}
The property names may vary so I cannot rely on the hard coded/static name inside code like data.games.type or data.games.sport.
And hence I tried with dynamic approach like,
{Object.entries(data.games).map((item, index) => {
return (
<div className="wrapper" key={index}>
<h4> {item[0]} </h4>
<Select
defaultValue="selectType"
onChange={handleChange}
allowClear
>
<Option value="selectType"> Select {item[0]} </Option>
{item[1].map((option, j) => (
<Option key={j} value={option.value}>
{option.value}
</Option>
))}
</Select>
<br />
</div>
);
})}
Reactjs sandbox:
Note: The options needs to be disabled (only) and should not be removed from select box as user can clear any select box selection and
select value from any of the dropdown.
Pure Javascript Approach: (Ignore reset of dropdown in this JS example which handled in reactjs with help of clear icon (close icon))
Also here is the Pure JS (working) way of approach tried with hard coded select boxes with id for each element respectively and also with some repetition of code in each addEventListener,
const data = {
games: {
type: [
{ id: 1, value: "Indoor", sportId: [2] },
{ id: 2, value: "Outdoor", sportId: [1, 3] }
],
sport: [
{ id: 1, value: "Tennis", typeId: [2] },
{ id: 2, value: "Chess", typeId: [1] },
{ id: 3, value: "Football", typeId: [2] }
]
}
}
const typeSelect = document.getElementById('type')
const sportSelect = document.getElementById('sport')
const createSelect = (values, select) => {
values.forEach(t => {
let opt = document.createElement('option')
opt.value = t.id
opt.text = t.value
select.append(opt)
})
}
createSelect(data.games.type, typeSelect)
createSelect(data.games.sport, sportSelect)
typeSelect.addEventListener('change', (e) => {
const val = e.target.value
const type = data.games.type.find(t => t.id == val)
Array.from(sportSelect.querySelectorAll('option')).forEach(o => o.disabled = true)
type.sportId.forEach(sId =>
sportSelect.querySelector(`option[value="${sId}"]`).disabled = false)
})
sportSelect.addEventListener('change', (e) => {
const val = e.target.value
const sport = data.games.sport.find(s => s.id == val)
Array.from(typeSelect.querySelectorAll('option')).forEach(o => o.disabled = true)
sport.typeId.forEach(sId =>
typeSelect.querySelector(`option[value="${sport.typeId}"]`).disabled = false)
})
<select id="type"></select>
<select id="sport"></select>
Could you please kindly help me to achieve the result of disabling the respective options from respective select box based on the conditions mentioned in the above mentioned scenario's in pure reactjs way?
For the comment given by #Andy, there is a reset option available in the select I am using, with close icon, so using that user can clear the select box and select the other dropdown option. This option is provided under allowClear in the antd select . Kindly please see the select box that I have in the above codesandbox, it has clear icon in the last.
Here's what I have as a working solution with my understanding of your question. You want dynamic options that can easily validate against other dynamic options. It's about the best I could come up with that wasn't completely unmaintainable. It's about 98% dynamic but for the validation purposes some properties do need to be defined.
Example:
Setup the interfaces and types
interface IState { // <-- need to be known
type: number;
sport: number;
}
interface IOption {
id: number;
value: string;
valid: Record<keyof IState, number[]>;
}
type Valid = "sport" & "type"; // <-- this needs to be known
interface Data {
games: {
[key: string]: Array<Record<Valid, IOption[]>>;
};
}
Data
const data: Data = {
games: {
type: [
{ id: 1, value: "Indoor", valid: { sport: [2] } },
{ id: 2, value: "Outdoor", valid: { sport: [1, 3] } }
],
sport: [
{ id: 1, value: "Tennis", valid: { type: [2] } },
{ id: 2, value: "Chess", valid: { type: [1] } },
{ id: 3, value: "Football", valid: { type: [2] } }
],
}
};
Create component state to hold the selected option values. These should match the known selection types in the data. The idea here is that we are converting the select inputs to now be controlled inputs so we can validate options against selected state.
export default function App() {
const [state, setState] = React.useState<IState>({
type: -1,
sport: -1,
category: -1
});
const changeHandler = (key: keyof IState) => (value: number) => {
setState((state) => ({
...state,
[key]: value
}));
};
This is the meat of the addition. Validates options against currently selected state values according to the data configuration. Looks through each option's valid object and compares against current selected state. Returns if a current option is a valid selectable option or not.
const isValid = (key: keyof IState, option: IOption) => {
const { valid } = option;
return (Object.entries(valid) as [[keyof IState, number[]]]).every(
([validKey, validValues]) => {
const selectedValue = state[validKey];
if (!selectedValue || selectedValue === -1) return true;
return validValues.includes(state[validKey]);
}
);
};
return (
<>
<br />
{(Object.entries(data.games) as [[keyof IState, IOption[]]]).map(
([key, options]) => {
return (
<div className="wrapper" key={key}>
<h4>{key}</h4>
<Select
value={state[key] || -1}
onChange={changeHandler(key)}
allowClear
>
<Option disabled value={-1}>
Select {key}
</Option>
{options.map((option) => (
<Option
key={option.id}
value={option.id}
disabled={!isValid(key, option)} // if not valid, then disable
>
{option.value}
</Option>
))}
</Select>
<br />
</div>
);
}
)}
</>
);
}
https://codesandbox.io/s/react-typescript-forked-gt7gvy?file=/src/App.tsx
I added keeping state of chosen values in each select and conditional disabling of options in the select.
import "antd/dist/antd.min.css";
import { Select } from "antd";
import * as React from "react";
import "./styles.css";
const { Option } = Select;
const data = {
games: {
type: [
{ id: 1, value: "Indoor", sportId: [2] },
{ id: 2, value: "Outdoor", sportId: [1, 3] }
],
sport: [
{ id: 1, value: "Tennis", typeId: [2] },
{ id: 2, value: "Chess", typeId: [1] },
{ id: 3, value: "Football", typeId: [2] }
]
}
};
export default function App() {
const [category, setCategory] = React.useState(null);
const [sport, setSport] = React.useState(null);
const handleChange = (value: any, index: number) => {
console.log(value);
const valueToSet = value.startsWith("select") ? null : value;
if (index === 0) {
setCategory(valueToSet);
} else if (index === 1) {
setSport(valueToSet);
}
};
return (
<>
<br />
{Object.entries(data.games).map((item, index) => {
return (
<div className="wrapper" key={index}>
<h4> {item[0]} </h4>
<Select
defaultValue="selectType"
onChange={(value) => handleChange(value, index)}
allowClear
>
<Option value="selectType"> Select {item[0]} </Option>
{item[1].map((option, j) => (
<Option
key={j}
value={option.value}
disabled={
'typeId' in option && // index === 1 or just belong to sports
category &&
data.games.type.find((x) => x.value === category)?.id !==
option.typeId[0]
}
>
{option.value}
</Option>
))}
</Select>
<br />
</div>
);
})}
</>
);
}
I have a set of select menus and I am trying to change a value when I select an option using onChange={updateValue} event. When I first select an option, the value is not being updated in the select menu.
It only changes the second time I try to choose an option. Not sure what I am doing wrong.
Edit: I did some more research (OnChange event using React JS for drop down) and I believe I need the value of the select to be updated as well, using setState. I cant figure out how to do it without having a variable for each value and set the state again.
let selectMenus = [
{
id: 'id1',
name: 'name1',
label: 'label1',
value: '0',
options: [
{
text: 'All ages',
value: '0',
},
{
text: '35 - 37 yrs',
value: '1',
},
],
buttonLabel: 'Refresh',
},
{
id: 'id2',
name: 'name2',
label: 'label2',
value: '1',
options: [
{
text: 'All ages',
value: '0',
},
{
text: '45 - 50 yrs',
value: '1',
},
],
buttonLabel: 'Refresh',
},
];
const [url, setUrl] = useState('http://localhost:5000/selectDropdowns1');
const updateValue = () => {
setUrl('http://localhost:5000/selectDropdowns2');
};
<form>
{selectMenus.map((select) => (
<div key={select.id} className='select-container'>
<label htmlFor={select.id}>{select.label}</label>
<select id={select.id} name={select.name} value={select.value} onChange={updateValue}>
{select.options.map((option) => (
<option value={option.value} key={uuid()}>
{option.text}
</option>
))}
</select>
<button>{select.buttonLabel}</button>
</div>
))}
</form>;
The problem is that when you provide onChange prop to select component it become a controlled component.
For more information: React Docs - Forms #controlled components
When you dealing with controlled components you must provide a value to it and when onChange triggerd it should update that value to work properly. Since you did not provide the full code, I imagine you have an array of select menus and options attached to it.
So in this case every select component should have own onChange method and own value to work properly. To achive this we should create another component for only Select Options. Like this;
function SelectComponent({ optionList, onSelected }) {
const [value, setValue] = useState();
const updateValue = ({ target }) => {
setValue(target.value);
if (onSelected) onSelected(target.value);
};
return (
<>
<label htmlFor={optionList.id}>{optionList.label}</label>
<select
id={optionList.id}
name={optionList.name}
value={value}
onChange={updateValue}
>
{optionList.options.map((option) => (
<option value={option.value} key={uuid()}>
{option.text}
</option>
))}
</select>
<button>{optionList.buttonLabel}</button>
</>
);
}
This component accepts to props; optionList and onSelected
optionList is the list of options to render
onSelected is a method that we call when user select and option
On main component, we should change the select section with our select component with props optionList and onSelected
return (
<div>
{selectMenus.map((select) => (
<div key={select.id} className="select-container">
<SelectComponent optionList={select} onSelected={updateValue} />
</div>
))}
</div>
);
So overall code is like this:
import { useState } from "react";
import { v4 as uuid } from "uuid";
export default function App() {
const [url, setUrl] = useState();
const updateValue = (value) => {
setUrl(value);
};
const selectMenus = [
{
id: 1,
label: "Menu 1",
name: "menu1",
buttonLabel: "Menu 1",
options: [
{
text: "option 1",
value: "option1"
},
{
text: "option 2",
value: "option2"
},
{
text: "option 3",
value: "option3"
}
]
},
{
id: 2,
label: "Menu 2",
name: "menu2",
buttonLabel: "Menu 2",
options: [
{
text: "option 1",
value: "option1"
},
{
text: "option 2",
value: "option2"
},
{
text: "option 3",
value: "option3"
}
]
},
{
id: 3,
label: "Menu 3",
name: "menu3",
buttonLabel: "Menu 3",
options: [
{
text: "option 1",
value: "option1"
},
{
text: "option 2",
value: "option2"
},
{
text: "option 3",
value: "option3"
}
]
}
];
return (
<div className="App">
<h1>URL Value: {url}</h1>
{selectMenus.map((select) => (
<div key={select.id} className="select-container">
<SelectComponent optionList={select} onSelected={updateValue} />
</div>
))}
</div>
);
}
function SelectComponent({ optionList, onSelected }) {
const [value, setValue] = useState();
const updateValue = ({ target }) => {
setValue(target.value);
if (onSelected) onSelected(target.value);
};
return (
<>
<label htmlFor={optionList.id}>{optionList.label}</label>
<select
id={optionList.id}
name={optionList.name}
value={value}
onChange={updateValue}
>
{optionList.options.map((option) => (
<option value={option.value} key={uuid()}>
{option.text}
</option>
))}
</select>
<button>{optionList.buttonLabel}</button>
</>
);
}
Working example is overhere codesandbox
I need to render data using React vertical tabs, I have given code which I have tried and also the data coming from API. I am not getting how to loop inside <TabPanel>.
link for codesandbox https://codesandbox.io/s/jovial-darkness-qob1n?file=/src/tab.js
<Tabs
defaultTab="vertical-tab-one"
vertical
className="vertical-tabs"
>
<TabList>
{subProducts.map((subProduct, index) => (
<Tab>{subProduct.subProductName}</Tab>
))}
</TabList>
{subProducts.map((subProduct, index) => (
<TabPanel className="tab-pane fade mt-4 show ">
{subProduct.bookingDetails.map((attr, i) => (
<>
<table id="customers">
<tbody>
<tr>
<td>{attr.name}</td>
<td>{attr.value}</td>
</tr>
</tbody>
</table>
</>
))}
</TabPanel>
))}
</Tabs>
API output:
subProducts: [
{
bookingDetails: [
{
name: "Birthday Decoration",
value: "YES"
},
{
name: "Photographer",
value: "NO"
}
],
subProductName: "Celebration"
},
{
bookingDetails: [
{
name: "Decoration",
value: "YES"
},
{
name: "Video",
value: "NO"
}
],
subProductName: "FamilY"
}
]
In the sandbox you try to map over bookingDetails for each object in the subProducts array, but in the second object of subProducts you have a Details property, but not a bookingDetails property so bookingDetails will be undefined.
So you probably want to change Details to bookingDetails:
subProducts: [
{
bookingDetails: [
{
name: "Birthday Decoration",
value: "YES",
},
{
name: "Photographer",
value: "NO",
},
],
subProductName: "Celebration",
},
{
bookingDetails: [
{
name: "Decoration",
value: "YES",
},
{
name: "Video",
value: "NO",
},
],
subProductName: "FamilY",
},
];
If the API returns Details instead of bookingDetails as in your original question do it the other way around. Change it so it maps over Details instead:
So subProduct.Details.map instead of subProduct.bookingDetails.map.
The data not being displayed correctly on click is because each Tab component need to have a tabFor prop value that corresponds with a TabPanel's tabId prop value. Otherwise react-web-tabs doesn't know what content to show when.
For this example I've used the map's index and called toString on it before passing it to the props as the id needs to be a string. But a more natural id would be to have id fields in your data and use those.
Example Sandbox
Here in my array there are 5 values that I am getting in "Options" props , but I want to set default value Please Select ,that I have defined in state.
And trying to get in Value props . But its getting error . Label of undefined
Basically when I open my page in drop down "Please Select" and after that that array value should come .
Please suggest .
below select field I have created separate component .
<AndroidPicker
mode="dropdown"
iosIcon={<Icon name="chevron-down" type="MaterialCommunityIcons"/>}
placeholder=""
placeholderStyle={{ color: "#bfc6ea" }}
placeholderIconColor="#007aff"
style={{ width: '100%' }}
selectedValue={value}
onValueChange={(e) => onChange.bind(that, this.props, e)()}
>
{
options.map((item, indx) => {
let { label, value } = item;
return <AndroidPicker.Item key={indx} label={label} value={value}/>;
})
}
</AndroidPicker>
this.state={
religionValue:'Please Select'
}
religion: Array(5)
0: {name: "Hindu", code: "H", __typename: "TroubleTicketMasterCode"}
1: {name: "Christian", code: "C", __typename: "TroubleTicketMasterCode"}
2: {name: "ISLAM", code: "I", __typename: "TroubleTicketMasterCode"}
3: {name: "Others", code: "O", __typename: "TroubleTicketMasterCode"}
4: {name: "Not Disclosed", code: "ND", __typename: "TroubleTicketMasterCode"}
religionChanged = (key, val) => {
this.handlereligionChanged({ field: "religionValue" }, val);
};
handlereligionChanged = (props, e) => {
let tempObj = this.state.religionValue;
tempObj[props.field] = e;
this.setState({ religionValue: e });
};
let religionOptions = [];
religion.map(({ code: value, name: label }) => {
religionOptions.push({ value, label });
});
<SelectField
label="Religion"
value={this.state.religionValue}
options={religionOptions}
node="Religion"
onChange={this.religionChanged}
that={this}
setIcon={true}
/>
Use the SelectField attribute called defaultValue. Check out the docs at react-md site
The form has radio buttons which generate question from answers:[{}] it save based ononChage to the answers:[{}] as selected.
Demo: https://codesandbox.io/s/mm4qrv4o6j
Current status of the questions and answers are
const question = {
questions: {
questionID: 1,
title: "Your gender ?",
values: [{ id: "1", value: "Male" }, { id: "2", value: "Female" }]
}
};
this.state = {
answers: [
{
questionID: "1",
answerValues: "2"
}
]
};
Unfortunately not able to update the answer because the radio onChange doesn't allow to change
<div className="form-group">
<label htmlFor={name}>{label}</label>
{options.map(option => {
return (
<div className="form-check" key={option.id}>
<label className="radio-inline" htmlFor={`${name}-${option.id}`}>
<input
name={name}
id={`${name}-${option.id}`}
data-question-id={questionID}
type="radio"
onChange={onChange}
value={option.id}
checked={option.id == checked.questionID}
/>{" "}
{option.value}
</label>
</div>
);
})}
</div>
One thing to note this structure doesn't seem suitable for when you add more questions/answers (an array is probably more suitable):
answered: {
answers: {
questionID: "1",
answerValues: "1"
}
}
The first and main issues is that your root component that controls when the sub ones are going to re-render, is never re-rendered itself. To solve this you can use local state to update answers:
const question = {
questions: {
questionID: 1,
title: "Your gender ?",
values: [{ id: "1", value: "Male" }, { id: "2", value: "Female" }]
}
};
class App extends React.Component {
state = {
answered: {
answers: {
questionID: "1",
answerValues: "1"
}
},
}
updateAnswer = (newAnswer) => {
this.setState({
answered: {
answers: newAnswer,
},
});
}
render () {
return(
<div style={styles}>
<h2>Start editing to see some magic happen {"\u2728"}</h2>
<hr />
<FormQuestion question={question.questions} answered={this.state.answered.answers} updateAnswer={this.updateAnswer}/>
</div>
)
}
};
The final issue is that the checked ID is in a confusingly named "answerValues", not "questionID", so change to:
checked={option.id === checked.answerValues}
Working demo: https://codesandbox.io/s/62ovz1kql3
I would suggest you to refactor your solution and put inputs inside the form tag. Then simply remove onChange from the Radio and add it to the form. Then just run your code again.