I'm trying to render a modal on the click of a button. I'm rendering the Modal perfectly fine. And I have two dropdowns in the modal which can get duplicated when we click on the add more button in the modal. When I click on the add more button the dropdowns get duplicated.
The Problem here is:
1. When ever we choose the first option in the first dropdown and go on to select the second option in the second dropdown the second dropdown is not showing any value for us. And the value of the first dropdown is getting updated with it's second value.
2. When we add the other set of dropdowns the problem 1 is there and with that above problem the values of the first dropdown are getting in sync with all of the first dropdowns.
You can have a look at the problem in the CodeSandbox that I've attached below:
https://codesandbox.io/s/winter-wildflower-oz7do?file=/src/App.js
When ever we choose the first option in the first dropdown and go on to select the second option in the second dropdown the second dropdown is not showing any value for us. And the value of the first dropdown is getting updated with it's second value.
This is what you telling the code to do with calling the function
const handleChange = (event) => {
setReview(event.target.value);
};
In the moment you are changing the option, the review value gets updated; thus, the value in your first dropdown is changing, not the option value from your second dropdown.
When we add the other set of dropdowns the problem 1 is there and with that above problem the values of the first dropdown are getting in sync with all of the first dropdowns.
This is, what you tell the system to do, too. Because you are just updating a string state that you declared with const [review, setReview] = useState("");
As you noticed, the problem is two-faced here.
The approach would be that you 1) ensure you are updating the right value (review or option) and 2) that you save the updated values within the arrays you already created and that you populate with another object once you click on the "+ add more" button, meaning those to arrays:
const [reviewInput, setReviewInput] = useState([{ review: "" }]);
const [reviewOption, setReviewOption] = useState([{ option: "" }]);
What means, in order to update the review input array, you can do the following:
const handleChangeReviewInput = (event, index) => {
setReviewInput([
...reviewInput.map((item, key) =>
key === index ? { review: event.target.value } : item
)
]);
};
The function is checking for the position in the array you try to change. If found: update the input with the given value.
In your dropdown rendering:
<Select
labelId="demo-simple-select-standard-label"
id="demo-simple-select-standard"
value={reviewInput.review} // taking the review string of your item in the array
onChange={(e) =>
handleChangeReviewInput(e, index) // passing the index (position in the array)
}
label="Review"
>...</Select>
And similar you can do with your review options:
const handleChangeOption = (event, index) => {
setReviewOption([
...reviewOption.map((item, key) =>
key === index ? { option: event.target.value } : item
)
]);
};
and
<Select
labelId="demo-simple-select-standard-label"
id="demo-simple-select-standard"
value={item.option} // taking option string from your item in the array
onChange={(e) => handleChangeOption(e, index)} // passing index
label="Option"
>...</Select>
See the working version:
https://codesandbox.io/s/jovial-greider-9uthk?file=/src/App.js
In general you might want to consider to have one array for both, your review input and your review option; and then have one function to update either the review input or the option.
Related
I've seen typescript solution with universal handle change function, but in my case it works bad. Need help to understand why this happens.
When I add new product - it adds correctly, but when I try to clear Input manually - first rerender it also changes already added content.
Algorithm to see this mistake: fill inputs, add submit and remove one letter from title input - you'll see that it affects already added title in another state, but only on first rerender
Sandbox: https://codesandbox.io/s/restless-dream-do7bqh?file=/src/App.tsx
The below code-sample may be a solution to achieve the desired objective:
In the handleChange method:
setProduct(prev => ({ ...prev, [name]: value }));
Explanation
The prev value of product is taken as-is
Then, using the ... spread-operator all of prev's props are spread
This is now followed with [name] which is a new prop being added with value as the corresponding value
Any existing prop will be replaced.
Suppose name variable has the value 'title' and value is 'Testing 123', then the product object becomes:
product = {
id: previousValueOfId,
price: previousValueOfPrice,
title: 'Testing 123'
}
Here, previousValueOfId and previousValueOfPrice are supposed to be the values.
Why OP's code did not get the desired result
The code is:
setProduct((prev) => {
(prev as any)[name] = value;
const newValue = { ...prev };
return newValue;
});
This takes the prev product, and changes it's [name] prop.
When prev is changed, it may impact UI elements which are rendered using product.
(NOTE: I am still very new to ReactJS & if this is incorrect kindly update appropriately).
I have a state variable that looks rather complex.. like this:
[
{
objName: nameOne,
objData: [
{
colName: [ colData ]
},
{
colName: [ colData]
}
]
},
...
]
For the first dropdown box, the data displayed is all the objNames. And that works fine. But the second should display all the colNames based on which ever objName was selected. My problem is that the size of this is dynamic.
I basically just want to take what ever was selected and query the state variable like a dictionary and display all the column names. Is this possible?
I already have the code built out for the <select>s. But I just dont know how to handle this. Any ideas?
The trick is to save the current selection (in the first dropdown) in the state of the component. I think this answer is useful here: https://stackoverflow.com/a/28868135/1326264
Then you can use this saved selection to populate the second dropdown.
You will need to filter based on the first dropdown's value that is selected. So your second dropdown's object should have a value which matches something from the first dropdown's value. For example, lets you have a list of Countries and a list of cities.
const country = ['USA','UK', 'India']
const cities = [{name:'Los Angeles', country:'USA'},{name:'California', country:'USA'},{name:'London', country:'UK'}, {name:'Delhi', country:'India'}]
Then you can update your cities state variable whenever the country state variable changes by putting in a useEffect
useEffect(() => {
setCities(cities.filter(city => city.country === country))
},[country])
I am facing strange behavior with useState. In the sandbox https://codesandbox.io/s/elastic-ellis-gjre7 I have 3 input fields, that change value if you click on them. The problem is with history state, that I log in console.
Expected behavior is, that as soon as you click on 1 of the boxes, it saves the state of all 3 boxes in history array, when we click another, it adds to history array new placements.
When clicking 1 box, it works correctly - "History 1" shows placement, and "History 2" and "History 3" are undefined.
But when we click 2nd box, it starts to act strange - It rewrites previously written "History 1", so now the content for "History 1" and "History 2" are the same. Strange, because all we do is concatenate new value to history array, but it overwrites the previous values.
Can anyone explain, why does it act in such a way?
On line 16, newValue[event.value.id] is getting mutated. Meaning, the same value is being updated and referenced in each element of history.
To reach the expected behaviour, the solution is to reference different values. This can be achieved by way of cloning.
See this fork of your sandbox for a practical example.
// Mutation.
newValue[event.target.id].value = "X";
// Cloning.
const targetId = event.target.id
newValue[targetId] = {...newValue[targetId], value: "X"};
You should write it as the following:
setHistory((prevHistory) => prevHistory.concat({ ...placements }));
Explanation:
I believe what you are doing is you're merging the previous state with the current one, from what I understand you want to contact the arrays -correct me if wrong -
You have several things wrong.
You are using item.id as event.target.id, but you are updating base on placements' array index, which you will eventually end up with unforseen results.
You are updating history base on placements state, but when you are updating the history, the new placements might not be ready yet.
Here we will update your placements base on your item id instead of index. Then we update the history at the same time with the new placements.
const handleClick = (event) => {
// .map is commonly used in this type of situation to update the correct object in the array with new value.
const newplacement = placements.map(item => item.id === parseInt(event.target.id) ? { ...item, value: 'X'} : item )
setPlacements(newplacement);
setHistory((prevHistory) => [...prevHistory, newplacement]);
};
my sandbox
Is there a way to pre-select a value or values for the filter checkbox?
I am currently saving the last filter so if a user filters, leaves the page, and then comes back the grid is still filtered. The issue I'm having is the filter drop down checkboxes does not reflect the filtered rows.
Here is the code I am using to set the saved filter:
if ($scope.onFilterChanged) {
this.gridOptions.onFilterModified = function () {
$scope.onFilterChanged({filter: ctrl.gridOptions.api.getFilterModel()});
}
}
if ($scope.currentFilter && $scope.onFilterChanged) {
this.gridOptions.api.setFilterModel($scope.currentFilter);
} else {
this.gridOptions.api.setFilterModel(null);
}
setFilterModel works great if I'm not leaving the page and coming back. But I'm not sure why it updates the rows and not the drop down options on page load..Is there a way to get the filtered rows and the check boxes to match on page load?
Yes its possible via filter API
You need to get filter instance first
let filterInstance = this.gridOptions.api.getFilterInstance('columnNameHere');
Then you can decide what should be in the filter
filterInstance.selectNothing();
filterInstance.selectValue("value one");
filterInstance.selectValue("value two");
...or...
let model = ["value one", "value two"];
filterInstance.setModel(model);
And on the last step - just inform the grid about changes
this.gridOptions.api.onFilterChanged();
I'm trying to use the react-bootstrap-typeahead control but I'm struck trying to make it do what I want. I've actually got 2 of these on my page, one of which is doing a true async lookup, and one which I almost want to behave like a combobox.
What I'd like to be able to do is to select an item, then click the dropdown to change my mind and choose another item. However if you try this, when you expand the list again it's automatically filtered to just the item you have selected.
For example if I use the basic example on the demo page, and select Alabama, clicking the input now only displays Alabama and none of the other choices. I'd like this to ideally return me to the full list (is this possible?).
Yes, it's possible. The filterBy prop can take a custom function, allowing you to filter results however you want. So building off the example you linked to, you'd want to do something along these lines:
<Typeahead
filterBy={(option, text) => {
if (this.state.selected.length) {
// Display all the options if there's a selection.
return true;
}
// Otherwise filter on some criteria.
return option.name.toLowerCase().indexOf(text.toLowerCase()) !== -1;
}}
labelKey="name"
onChange={(selected) => this.setState({selected})}
options={options}
placeholder="Choose a state..."
selected={this.state.selected}
/>
Update
As of v3.0.0, the filterBy callback passes props instead of text:
(option, props) => {
if (props.selected.length) {
// Display all the options if there's a selection.
return true;
}
// Otherwise filter on some criteria.
return option.name.toLowerCase().indexOf(props.text.toLowerCase()) !== -1;
}