I am trying to get query parameters from my url and use those values to set the selected option value on load. At the moment I have access to two values that match, which are the option text and the query parameter value.
My original thought is to add a variable within my loop that is set by an if statement that checks if the loop value matches the query props value, but I'm being given an error message about an unexpected token. What is possibly wrong with this setup and there is a better way to set up this evaluation and set "select" to the proper option on page load?
Here is the full error message ({selected} is where the error happens):
ERROR in ./public/components/app/activity-feed/search-form.js
Module build failed: SyntaxError: Unexpected token, expected ... (45:102)
43 | selected = 'selected';
44 | }
> 45 | return ( <option value={category.categoryHashId} {selected}>
Here is my react code where this.props.category is an array of ids/text for each category that is possible to query and this.props.categoryQuery is the text value that is currently a query parameter:
<select name="category" className="form-control filter-category">
{ this.props.category.map( (category) => { var selected; if(category.categoryName == this.props.categoryQuery){ selected = 'selected'; } return (
<option value={category.categoryHashId} {selected}>{category.categoryName}</option> ) } ) }
</select>
For example, this.props.categoryQuery is Blog and out of the 8 this.props.category.categoryName array values, Blog matches and a "selected" string is added
You cannot write a prop for an element the way you are doing: <tag {someValue}/>.
Instead, pass a value property to the select element and a defaultValue to the option elements:
const {
category,
categoryQuery
} = this.props;
const selectedCategory = category.find((val) => {
return val.categoryName === categoryQuery;
})
return (
<select
name="category"
className="form-control filter-category"
value={selectedCategory.categoryHashId}
>
{category.map((cat) => {
return (
<option
key={cat.categoryHashId}
defaultValue={cat.categoryHashId}
>
{cat.categoryName}
</option>
);
})}
</select>
);
Sidenote: You should not use the categoryName for identification/comparison/selection if you have a unique categoryHashId. When the user selects the category, use this instead of the name. You might want to review a bit your data structure for more efficiency.
I wrote this example to match your need, but if you use a hashId inside your categoryQuery you can skip the .find() step.
Related
I mapping over data which displays different select option dropdowns. I need my onChange function to be on the select tag to capture the selected option, but the select tag is before the function is initialized so I'm not sure how to go about passing the function into it.
<select onChange={onChange}> <--- onChange function cant be passed here
{options.map((attribute) => {
function onChange(event) {
const newSelected = { ...selected };
newSelected[attribute.title] = event.target.value
}
return (
<option value={attribute.index}>{attribute.value}</option>
)
})}
</select>
Define your onChange so that it uses the information on the event object it receives to find the right entry in the list, something like this:
<select onChange={event => {
const attribute = options[event.currentTarget.value];
const newSelected = {
...selected,
[attribute.title]: event.currentTarget.value,
};
// ...
}}>
{options.map((attribute, index) => {
return (
<option key={attribute.value} value={index}>{attribute.value}</option>
)
})}
</select>
It's hard to be sure the details of that are right without knowing the structure of options or what you want newSelected to be, but I think I've mostly inferred it from the question's code. Note that I changed attribute.index to index in the map call, and that I added a key (you need key on any array you render), assuming that attribute.value is unique.
That said, if attribute.value is unique, I think I'd use it instead of index or attribute.index:
<select onChange={event => {
const attribute = options.find(({value}) => value == event.currentTarget.value);
const newSelected = {
...selected,
[attribute.title]: attribute.index,
};
// ...
}}>
{options.map(({value}) => {
return (
<option key={value} value={value}>{value}</option>
)
})}
</select>
I think I sort of see why you're doing it the way you're doing it, so let me try to address how to handle it differently:
First of all, you're doing it because the onChange function needs access to the attribute.title value. So for you, you're thinking the easiest way to get that value is to define the function in the .map function, where you have access to each individual attribute as it's coming through.
You can't, though. As I said in my comment: It conceptually doesn't make sense. <select onChange={onChange}> implies a singular onChange function, but you're defining a new onChange function in every iteration of .map.
So what you need to do is define onChange before your render function, and then inside that you have to find the matching attribute inside the options array, so that you can then use attribute.title as you like.
[edit] TJ Crowder gives a specific code example of how to do that.
I implemented a multiple select dropdown from react-bootstrap documentation.
It does not let me do multiple select and only gets the last clicked option. I have state variable set to array. What else am I missing? App is created with create-react-app.
I have state set to array inside the class constructor. Binding of event handler also done in the constructor.
Next, I'm showing my event handler followed by form group with onChange and value set to state. (note I have a drop-down above this which is working fine.)
I then pass this value to a few classes before it's parsed to JSON. The last pastes are those classes. I have removed other parameters so easier to read, any ideas, feel free to ask for more info.
this.state = {
codeCoverage: [],
}
this.handleCodeCoverageChange = this.handleCodeCoverageChange.bind(this);
//Event handlers below
handleCodeCoverageChange(event){
this.setState({
codeCoverage: event.target.value
})
}
<Form.Group>
<Form.Label>Please choose your desired code coverage software(s)</Form.Label>
<Form.Control as="select" value={this.state.codeCoverage} onChange={this.handleCodeCoverageChange} multiple>
<option value="">--Please choose an option--</option>
<option value="cobertura">Cobertura</option>
<option value="sonarcube">Sonarcube</option>
</Form.Control>
</Form.Group>
var configurator = new Configurator(this.state.codeCoverage)
class Configurator
{
constructor(
code_coverage)
{
this.pipeline = new Pipeline(code_coverage)
}
}
class Pipeline
{
constructor(code_coverage)
{
this.analysisAndSecurity = new AnalysisAndSecurity(code_coverage)
}
class AnalysisAndSecurity{
parameter
constructor(code_coverage)
{
this.code_coverage = code_coverage
}
}
In your handleChange function you assign state.codeCoverage the value of the selected element instead of adding it to the array of selected element. This is why when you select another element it deletes the old value. I would recommend logging e.target.value and this.state.codeCoverage to better understand. As for the solution:
Since you are using multiple select it expects an array as value instead of a single value. So you need to change two things in your handleChange method.
First you need to add your element to existing values and not replace them.
You need to handle when a selected element is clicked again and needs to become unselected.
You can do both these tasks as shown below:
handleChange = e => {
const { codeCoverage } = this.state;
// Find the value selected the codeCoverage array
const index = codeCoverage.indexOf(e.target.value);
// If the value is not found then add it to the array
if (index === -1) codeCoverage.push(e.target.value);
// If value found then remove the value to unselect
else codeCoverage.splice(index, 1);
// Set the state so that the component reloads with the new value
this.setState({ codeCoverage: [...codeCoverage] });
};
TL;DR: calling this.setState to update an array avoid the change of a < select > label with the selected < option >
Explanations:
I am building an array of options like that (dd is for drop down):
let ddUsers = (this.state.users.map(user => <option key={uuid.v4()} value={user.key}>{user.name}</option>))
Then here is my select:
<select onChange={this.handleFilter}>{ddUsers}</select>
This works perfectly: it appears first with the first option and is able to display the others.
Here is my onChange function: handleFilter
handleFilter = (event) => {
let selectedUsers;
console.log(event.target.value, this.state.DEFAULT_KEY)
if (event.target.value === this.state.DEFAULT_KEY) {
selectedUsers = [...this.state.users];
} else {
selectedUsers = (this.state.users.filter(user => user.key === event.target.value));
}
// this.setState({selected: selectedUsers}); <-- The problem comes from this line (looks like atleast)
}
Now with the commented last line, the label of the select correctly update to the selected options. But when I uncomment this line, the function is called, this.state.selected is updated BUT the select is blocked on the 1st option.
Is that clear? What am I missing?
In fact: just calling setState in the function avoid the correct change
This is because you are setting the key of option to dynamic value which gets updated every time. Try to use a know and unique one as the key. Code such as below
<option key={user.key} value={user.key}>
This ensures, you have unique key and also is predictable.
I am displaying dynamic dropdown, where I want to keep one specific element or first element selected. But I could not find way.
<select onChange = {this.handleSelectChange}>
<option>any-select</option>
{
this.state.data2.map(function(data) {
return <option value={data.id}>{data.name}</option>
})
}
</select>
you can use below mentioned code
this.state.data2.map(function(data,index) {
return index==0?<option value={data.id} selected="selected">{data.name}</option>:<option value={data.id}>{data.name}</option>
})
Although you can add selected="selected" on option, but I think the better way is using value of select.
You can change the value of select in onChange handler.
If you want select the first no-value option, just set value to a false value such as null
<select onChange = {this.handleSelectChange} value={/*null or selected option's data.id*/}>
<option>any-select</option>
{
this.state.data2.map(function(data) {
return <option value={data.id}>{data.name}</option>
})
}
</select>
If you want to select any other option, just set value to the option's value
I created a nested category model. In order to select a category for a product, you see a dropdown with the main categories. When you select one, a new dropdown is added to the right with the child categories to the one you selected. This repeats until you reach the last level. When this happens $scope.product.category_id is set. I want to invalidate the whole set of fields when $scope.product.category_id is null.
I've been using angular-bootstrap-show-errors for validation and I tried to mix it with ui-utils to achieve this one, using a custom function: validate_category().
Here's the HTML I used:
<span ng-repeat="parent_id in category_dropdown_parents" show-errors="{ skipFormGroupCheck: true }">
<select class="form-control" name="category_id"
ng-init="selected_category[$index] = init_select($index);"
ng-model="selected_category[$index]"
ng-options="category.name for category in (categories | filter : { parent_id: parent_id } : true ) track by category.id "
ng-change="select_category(selected_category[$index], $index)"
ui-validate="'validate_category()'" // added this to work with ui-utils
>
</select>
<span ng-if="$index+1 != category_dropdown_parents.length">/</span>
</span>
And this is my simple validation function:
$scope.validate_category = function() {
return ( $scope.product.category_id !== null
&& $scope.product.category_id !== undefined);
}
But this is not working. Ideas?
EDIT: I just realized, that the problem with this is that the validation function on ui-validate is executed after the ng-change function, so it's never able to check the $scope.product.category_id update.
Your select is using
ng-model="selected_category[$index]"
but the validation function is using
$scope.product.category_id
Shouldn't it be using
ui-validate="'validate_category($index)'"
and
$scope.validate_category = function($index) {
return($scope.selected_category[$index] !== null
&& $scope.selected_category[$index] !== undefined);
}
This is not the ideal answer but it's the best I could get. Shame on me, this was too simple:
<select class="form-control" name="category_id"
ng-init="selected_category[$index] = init_select($index);"
ng-model="selected_category[$index]"
ng-options="category.name for category in (categories | filter : { parent_id: parent_id } : true ) track by category.id "
ng-change="select_category(selected_category[$index], $index)"
required // !!!
>
</select>
That's it, just added the required attribute. The problem with this is that since the I'm not validating product.category_id but just validating all the dropdowns to be not empty. I guess I
'll have to rely on the code on select_category().