Okay, complicated one. I'll keep this as simple as possible.
I'm building an application to create json queries which are then sent to a server.
The components for building the query are in a nested structure: QueryContainer > QueryGroup > QueryRow > a series of form components: QueryRowSelectField (a dropdown menu used to make a selection), QueryRowDateInput, QueryRowPackageTypeInput, QueryRowPackageSizeInput.
The idea is that you have 'groups' of queries each of which contain 'rows' so a query group might contain three rows, one which stipulates a user as 'Dave', another that states the requested packageType and another giving a data range.
Every time a group or a row is added or removed or changed it updates an object in context called 'query', this then, in turn, is used to populate the front end so that the user can see the query that they're building.
That all seems to work fine. I've ended up keeping most of the functionality in the 'root' QueryContainer component and passing it down to QueryGroup, QueryRow and the QueryRow components using props.
There is though an issue which I've run into and can't get past.
I can't work out how to retain the selected value in QueryRowSelectField.
The code for the select dropdown is as follows:
<select
value={selectFieldState}
name='field'
onChange={(e) => {
handleChange(e, changeFieldInRow, groupIndex, rowIndex);
}}
className='form-control form-control-sm'
>
{populateSelectField()}
</select>
This, in turn, calls a handle change function passing it the event, the function which alters the query in context based on the selection (changeFieldInRow), the index of the group in the query and the index of the two in the query. The last three have been passed down from QueryContainer.
handleChange is as follows:
const handleChange = (e, changeFieldInRow, groupIndex, rowIndex) => {
let selectedValue = e.currentTarget.value;
updateFieldsInGroup(groupIndex);
changeFieldInRow(selectedValue, groupIndex, rowIndex);
setSelectFieldState(selectedValue);
};
The select options are stored as an array of objects containing labels and values in local state. updateFieldsInGroup basically filters out the currently selected dropdown option so that it doesn't appear in the options when the next row is initiated.
Most importantly, the selected option is sent to selectFieldState which is then used to populate the value attribute in the select.
As stated above, the select dropdown doesn't retain its displayed value when it's changed. Everything else seems to work fine. If I remove the function call 'changeFieldInRow(selectedValue, groupIndex, rowIndex)' then the value is retained but the row components don't alter when a selection is made.
If I set the selectFieldState with a string in handleChange e.g. setSelectFieldState('user'); then it retains that string value but if I try to use the state value, despite that showing up in the query in the console, it just returns to default.
The problem therefore is something to do with changeFieldInRow. I suspect when it rerenders it knocks out the existing value and so nothign is retained but in that case it should knock out the hardcoded setSelectFieldState('user'); which it doesn't.
I'm stuck with this so if anyone can point me in the right direction then it would be much appreciated.
If the above isn't clear then files can be found at: https://github.com/stefemil1969/queryMaker
Current 'working' example can be found here: https://codesandbox.io/s/youthful-mendeleev-7wzs2?file=/public/index.html
3 things to point out:
Get the value of the select
by event.target.value
not event.currentTarget.value
Since you are using controlled select
component, everytime it changes, you need to reflect that change to your local state using the provided function from useState
:
const handleChange = (e, changeFieldInRow, groupIndex, rowIndex) => {
let selectedValue = e.target.value;
setSelectFieldState(selectedValue); <- reflect the change to local state
updateFieldsInGroup(groupIndex);
changeFieldInRow(selectedValue, groupIndex, rowIndex);
setSelectFieldState(selectedValue);
};
You need to include the selected fields to the fields that are going to be populated so that the select
component will show its label correctly
Here's the working demo: https://codesandbox.io/s/modern-architecture-oyf0e?file=/src/components/query/queryRow/queryRowComponents/QueryRowSelectField.js:1838-2129
I noticedthat you are using map
function not correctly, look up those warnings from the Codesandbox. In those cases, I think you would like to use forEach
instead.