Setting defaultValues on the fly for Select - reactjs

I am trying to follow this sample:
https://react-select.com/home
I have defined two following arrays:
const [selectedAccountTypes, setSelectedAccountTypes] = useState([]);
const [allAccountTypes, setAllAccountTypes] = useState([]);
I set the values in following function when a user click a button:
const onAccountChanged = async (val) => {
console.log(val);
setSearchedAccount(val);
console.log('onAccountChanged');
console.log(searchedAccountValue);
const accounts = await getAccountTypeLookupList();
var options = accounts.data.map((o) => ({
label: o.name,
value: o.id,
number: o.accountNumber
}))
console.log(options);
setAllAccountTypes(options);
setSelectedAccountTypes(options);
console.log(options);
console.log(setSelectedAccountTypes);
return options;
};
And my component:
<Select
placeholder='First select account above'
isMulti
cacheOptions
defaultValue ={selectedAccountTypes}
options={allAccountTypes}/>
I would expect all to be selected in this scenario nut nothing is default selected. Am i missing something?

Set the value to selectedAccountTypes, then onChange call the setSelectedAccountTypes to update it.

The issue was the timing. At time I set the selected values the control has not finished rendering the control. I ended up making a useEffect on the array, and in that I set default selected.

Related

Good practice in useState hook

I am pretty new to JavaScript and react. I have a scenario in which I have a lot of checkboxes, about 60. I am getting data from server and then updating the state of my checkboxes. The problem is on older phone it is taking time to check or uncheck the checkbox.
const [data, setData] = React.useState({
checkBox1: true,
checbox2:false //all the way upto 60.....
checkbox60:false
});
In checkBox component class onPress I am toggling the state
onPress={()=> setData(prevState=>({...prevState, [props.checkBoxName]: props.instance === 'n' ?
'y' : 'n'}))}
I understand why this is taking time, is there any better way to manage this case ?
First you would have to create a checked state and define how many checkboxes you need.
const [checked, setChecked] = useState(false)
const numberOfCheckboxes = 60
Second you should create a function that sets the value for checked.
const onChangeHandler = (val) => {
setChecked(val);
}
Third this is what the checkbox input should look like. You would populate it using numberOfCheckboxes.
{Array.from(Array(numberOfCheckboxes), (e, i) => {
return (
<input
name={String(i)}
checked={checked === String(i)}
onChange={() => onChangeHandler(String(i))}
type="checkbox"
)
/>
})}

How Can I Setup `react-select` to work correctly with server-side data by using AsyncSelect?

I would like to setup a component react-select to work server-side data and do server-side filtering, but it doesn't work for a plethora of reasons.
Can you explain it and also show working code?
react-select has several examples in the documentation including an entire section dedicated to AsyncSelect which include inline code examples with codesandbox links.
It's worth noting that there are three unique props specific to the AsyncSelect
loadOptions
defaultOptions
cacheOptions
The primary difference between AsyncSelect and Select is that a Select is reliant on an options prop (an array of options) whereas the AsyncSelect is instead reliant on a loadOptions prop (an async function which provides a callback to set the options from an api).
Often api autocomplete lookups filter results on the server so the callback on the loadOptions does not make assumptions on filtering the results returned which is why they may need to be filtered client-side prior to passing them to the AsyncSelect state.
Here is a simple code example.
import React from 'react';
import AsyncSelect from 'react-select/async';
const filterOptions = (options, inputValue) => {
const candidate = inputValue.toLowerCase();
return options.filter(({ label }) => label.toLowerCase().includes(candidate);
};
const loadOptions = (inputValue, callback) => {
const url = `www.your-api.com/?inputValue=${inputValue}`;
fetch(url).then(resp => {
const toSelectOption = ({ id, name }) => ({ label: name, value: id });
// map server data to options
const asyncOptions = resp.results.map(toSelectOption);
// Filter options if needed
const filtered = filterOptions(asyncOptions, inputValue);
// Call callback with mapped and filtered options
callback(filtered);
})
};
const AsyncLookup = props => (
<AsyncSelect
cacheOptions
loadOptions={loadOptions}
defaultOptions
{...props}
/>
);
export default AsyncLookup
Let's start by me expressing the opinion that react-select seems great, but not very clearly documented. Personally I didn't fall in love with the documentation for the following reasons:
No search
All the props and put on a single page. If I do CTRL+F on something everything lights up. Pretty useless
Most descriptions are minimal and not describing the important edge cases, some are even missing
There are some examples, but not nearly enough to show the different varieties, so you have to do guesswork
And so I will try to help a bit with this article, by giving steps by steps, code and problems + solutions.
Step 1: Simplest form react-select:
const [options, setOptions] = useState([
{ id: 'b72a1060-a472-4355-87d4-4c82a257b8b8', name: 'illy' },
{ id: 'c166c9c8-a245-48f8-abf0-0fa8e8b934d2', name: 'Whiskas' },
{ id: 'cb612d76-a59e-4fba-8085-c9682ba2818c', name: 'KitKat' },
]);
<Select
defaultValue={options[0]}
isClearable
options={options}
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
/>
It generally works, but you will notice that if I type the letter d which doesn't match any of the choices anywhere, choices stay, instead of showing "no options" as it should.
I will ignore this issue, since it is minor and seems unfixable.
So far so good, we can live with that small issue.
Step 2: Convert static data to server data
Our goal is now to simply swap the static data with server loaded data. Meh, how difficult could it be?
We will first need to swap <Select/> for <AsyncSelect/>. Now how do we load data?
So looking at the documentation there are multiple ways of loading data:
defaultOptions: The default set of options to show before the user starts searching. When set to true, the results for loadOptions('') will be autoloaded.
and
loadOptions: Function that returns a promise, which is the set of options to be used once the promise resolves.
Reading it carefully you understand defaultOptions needs to be a boolean value true and loadOptions should have a function returning the choices:
<AsyncSelect
defaultValue={options[0]}
isClearable
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
defaultOptions
loadOptions={loadData}
/>
Looks great, we have remote data loaded. But we want to preset our default value now. We have to match it by Id, rather than choosing the first one. Here comes our first problem:
PROBLEM: You can't set the defaultValue in the very beginning, because you have no data to match it against. And if you try to set the defaultValue after component has loaded, then it doesn't work.
To solve that, we need to load data in advance, match the initial value we have, and once we have both of those, we can initialize the component. A bit ugly but that's the only way I could figure it out given the limitations:
const [data, setData] = useState(null);
const [initialObject, setInitialObject] = useState(null);
const getInitial = async () => {
// make your request, once you receive data:
// Set initial object
const init= res.data.find((item)=>item.id=ourInitialId);
setInitialObject(init);
// Set data so component initializes
setData(res.data);
};
useEffect(() => {
getInitial();
}, []);
return (
<>
{data!== null && initialObject !== null ? (
<AsyncSelect
isClearable
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
defaultValue={initialObject}
defaultOptions={options}
// loadOptions={loadData} // we don't need this anymore
/>
) : null}
</>
)
Since we are loading the data ourselves, we don't need loadOptions so we will take it out. So far so good.
Step 3: Make filter with server-side filtering call
So now we need a callback that we can use for getting data. Let's look back at the documentation:
onChange: (no description, from section "StateManager Props")
onInputChange: Same behaviour as for Select
So we listen to documentation and go back to "Select Props" section to find:
onInputChange: Handle change events on the input`
Insightful...NOT.
We see a function types definition that seems to have some clues:
I figured, that string must by my text/query. And apparently it drops in the type of change. Off we go --
const [data, setData] = useState(null);
const [initialObject, setInitialObject] = useState(null);
const getInitial = async () => {
// make your request, once you receive data:
// Set initial object
const init= res.data.find((item)=>item.id=ourInitialId);
setInitialObject(init);
// Set data so component initializes
setData(res.data);
};
useEffect(() => {
getInitial();
}, []);
const loadData = async (query) => {
// fetch your data, using `query`
return res.data;
};
return (
<>
{data!== null && initialObject !== null ? (
<AsyncSelect
isClearable
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
defaultValue={initialObject}
defaultOptions={options}
onInputChange={loadData} // +
/>
) : null}
</>
)
Data gets fetched with the right query, but options don't update as per our server data results. We can't update the defaultOptions since it is only used during initialization, so the only way to go would be to bring back loadOptions. But once we do, we have 2 calls on every keystroke. Blak. By countless hours and miracle of painstaking experimentation, we now figure out that:
USEFUL REVELATION: loadOptions actually fires on inputChange, so we don't actually need onInputChange.
<AsyncSelect
isClearable
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
defaultValue={initialObject}
defaultOptions={options}
// onInputChange={loadData} // remove that
loadOptions={loadData} // add back that
/>
Things look good. Even our d search has automagically been fixed somehow:
Step 4: Update formik or whatever form value you have
To do that we need something that fires on select:
onChange: (no explanation or description)
Insightful...NOT. We have a pretty and colorful definition again to our rescue and we pick up some clues:
So we see the first param (which we don't know what it is can be object, array of array, null, or undefined. And then we have the types of actions. So with some guessing we figure out, it must be passing the selected object:
We will pass setFieldValue function as a prop to the component:
onChange={(selectedItem) => {
setFieldValue(fieldName, selectedItem?.id); // fieldName is also passed as a prop
}}
NOTE: careful, if you clear the select it will pass null for selectedItem and your JS will explode for looking for .id of undefined. Either use optional chaining or as in my case set it conditionally to '' (empty string so formik works).
Step 5: Final code:
And so we are all set with a fully functional reusable Autocomplete dropdown select server-fetching async filtering, clearable thingy.
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/async';
export default function AutocompleteComponent({
fieldName,
initialValue,
setFieldValue,
getOptionLabel,
queryField,
}) {
const [options, setOptions] = useState(null);
const [initialObject, setInitialObject] = useState(null);
// this function only finds the item from all the data that has the same id
// that comes from the parent component (my case - formik initial)
const findByValue = (fullData, specificValue) => {
return fullData.find((e) => e.id === specificValue);
};
const loadData = async (query) => {
// load your data using query HERE
return res.data;
};
const getInitial = async () => {
// load your data using query HERE
const fetchedData = res.data;
// match by id your initial value
const initialItem = findByValue(fetchedData, initialValue);
// Set both initialItem and data options so component is initialized
setInitialObject(initialItem);
setOptions(fetchedData);
}
};
// Hit this once in the beginning
useEffect(() => {
getInitial();
}, []);
return (
<>
{options !== null && initialObject !== null ? (
<AsyncSelect
isClearable
getOptionLabel={getOptionLabel}
getOptionValue={(option) => option.id}
defaultValue={initialObject}
defaultOptions={options}
loadOptions={loadData}
onChange={(selectedItem) => {
const val = (selectedItem === null?'':selectedItem?.id);
setFieldValue(fieldName, val)
}}
/>
) : null}
</>
);
}
AutocompleteComponent.propTypes = {
fieldName: PropTypes.string.isRequired,
initialValue: PropTypes.string,
setFieldValue: PropTypes.func.isRequired,
getOptionLabel: PropTypes.func.isRequired,
queryField: PropTypes.string.isRequired,
};
AutocompleteComponent.defaultProps = {
initialValue: '',
};
I hope this saves you some time.

Selecting created option on menu close/select blur using creatable component

Is there some way to instruct react-select to select an option on menu close or select blur, but only if it is the one created (not from default list)?
Context:
I have a list of e-mail addresses and want to allow user to select from the list or type new e-mail address and then hit Submit button. I do the select part with react-select's Creatable component and it works.
import CreatableSelect from 'react-select/creatable';
<CreatableSelect
options={options}
isMulti={true}
isSearchable={true}
name={'emailAddresses'}
hideSelectedOptions={true}
isValidNewOption={(inputValue) => validateEmail(inputValue)}
/>
But what happens to my users is that they type new e-mail address, do not understand they need to click the newly created option in dropdown menu and directly hit the Submit button of the form. Thus the menu closes because select's focus is stolen and form is submitted with no e-mail address selected.
I look for a way how can I select the created option before the menu is closed and the typed option disappears.
You can keep track of the inputValue and add the inputValue as a new option when the onMenuClose and onBlur callbacks are triggered.
Keep in mind that both onBlur and onMenuClose will fire if you click anywhere outside of the select area. onMenuClose can also fire alone without onBlur if you press Esc key so you will need to write additional logic to handle that extra edge case.
function MySelect() {
const [value, setValue] = React.useState([]);
const [inputValue, setInputValue] = React.useState("");
const isInputPreviouslyBlurred = React.useRef(false);
const createOptionFromInputValue = () => {
if (!inputValue) return;
setValue((v) => {
return [...(v ? v : []), { label: inputValue, value: inputValue }];
});
};
const onInputBlur = () => {
isInputPreviouslyBlurred.current = true;
createOptionFromInputValue();
};
const onMenuClose = () => {
if (!isInputPreviouslyBlurred.current) {
createOptionFromInputValue();
}
else {} // option's already been created from the input blur event. Skip.
isInputPreviouslyBlurred.current = false;
};
return (
<CreatableSelect
isMulti
value={value}
onChange={setValue}
inputValue={inputValue}
onInputChange={setInputValue}
options={options}
onMenuClose={onMenuClose}
onBlur={onInputBlur}
/>
);
}
Live Demo

useState() bug - state value different from initial value

I have a component that uses useState() to handle the state of its floating label, like this:
const FloatingLabelInput = props => {
const {
value = ''
} = props
const [floatingLabel, toggleFloatingLabel] = useState(value !== '')
I have a series of those components and you'd expect initialFloatingLabel and floatingLabel to always be the same, but they're not for some of them! I can see by logging the values:
const initialFloatingLabel = value !== ''
console.log(initialFloatingLabel) // false
const [floatingLabel, toggleFloatingLabel] = useState(initialFloatingLabel)
console.log(floatingLabel) // true???
And it's a consistent result. How is that possible?
How come value can be different from initialValue in the following example? Is it a sort of race condition?
const [value, setValue] = useState(initialValue)
More details here
UPDATE
This (as suggested) fixes the problem:
useEffect(() => setFloatingLabel(initialFloatingLabel), [initialFloatingLabel])
...but it creates another one: if I focus on a field, type something and then delete it until the value is an empty string, it will "unfloat" the label, like this (the label should be floating):
I didn't intend to update the floatingLabel state according to the input value at all times; the value of initialFloatingLabel was only meant to dictate the initial value of the toggle, and I'd toggle it on handleBlur and handleChange events, like this:
const handleFocus = e => {
toggleFloatingLabel(true)
}
const handleBlur = e => {
if (value === '') {
toggleFloatingLabel(false)
}
}
Is this approach wrong?
UPDATE
I keep finding new solutions to this but there's always a persisting problem and I'd say it's an issue with Formik - it seems to initially render all my input component from its render props function before the values are entirely computed from Formik's initialValues.
For example:
I added another local state which I update on the handleFocus and handleBlur:
const [isFocused, setFocused] = useState(false)
so I can then do this to prevent unfloating the label when the input is empty but focused:
useEffect(() => {
const shouldFloat = value !== '' && !isFocused
setFloatLabel(shouldFloat)
}, [value])
I'd still do this to prevent pre-populated fields from having an animation on the label from non-floating to floating (I'm using react-spring for that):
const [floatLabel, setFloatLabel] = useState(value !== '')
But I'd still get an animation on the label (from "floating" to "non-floating") on those specific fields I pointed out in the beginning of this thread, which aren't pre-populated.
Following the suggestion from the comments, I ditched the floatingLabel local state entirely and just kept the isFocused local state. That's great, I don't really need that, and I can only have this for the label animation:
const animatedProps = useSpring({
transform: isFocused || value !== '' ? 'translate3d(0,-13px,0) scale(0.66)' : 'translate3d(0,0px,0) scale(1)',
config: {
tension: 350,
},
})
The code looks cleaner now but I still have the an animation on the label when there shouldn't be (for those same specific values I mentioned at the start), because value !== '' equals to true for some obscure reason at a first render and then to false again.
Am I doing something wrong with Formik when setting the initial values for the fields?
You have the use useEffect to update your state when initialFloatingLabel change.
const initialFloatingLabel = value !== ''
const [floatingLabel, setFloatingLabel] = useState(initialFloatingLabel)
// calling the callback when initialFloatingLabel change
useEffect(() => setFloatingLabel(initialFloatingLabel), [initialFloatingLabel])
...
Your problem look like prop drilling issue. Perhaps you should store floatingLabel in a context.
// floatingLabelContext.js
import { createContext } from 'react'
export default createContext({})
// top three component
...
import { Provider as FloatingLabelProvider } from '../foo/bar/floatingLabelContext'
const Container = () => {
const [floatingLabel, setFloatingLabel] = useState(false)
return (
<FloatingLabelProvider value={{ setFloatingLabel, floatingLabel }}>
<SomeChild />
</FloatingLabel>
)
}
// FloatingLabelInput.js
import FloatingLabelContext from '../foo/bar/floatingLabelContext'
const FloatingLabelInput = () => {
const { setFloatingLabel, floatingLabel } = useContext(FloatingLabelContext)
...
}
This way you just have to use the context to change or read the floatingLabel value where you want in your components three.

How can i define a default value for react-select v1

I had a react-select rendering a list of emails, and i need to keep the selected emails as a default option when the email is selected and saved, but the defaultValues are not working. How can i do that?
Here is my select component:
const [selectedOption, setSelectedOption] = useState("")
const makeEmailOption = item => ({
value: item.id,
label: item.ccEmail,
id: item.id,
chipLabel: item.ccEmail,
rest: item,
selected: item.selected
})
const makeEmailOptions = items => items.map(makeEmailOption)
const handleChange = (value) => {
setSelectedOption(value)
props.emails(value)
}
return (
<div>
<Select
multi={true}
name={props.name}
options={makeEmailOptions(props.ccemailfilter)}
onChange={handleChange}
value={selectedOption}
/>
</div>
)
I receive everything as props and work with that to make the options. How can i do that to make the default value if a field selected is true?
You almost have it, but in this case, you are setting the value to the selectedOption instead of setting the defaultValue. Also, you are changing the default value each time there is a change, which shouldn't be needed.
const defaultVal = {value: selectedOption, label: selectedOption};
return (
<div>
<Select
multi={true}
name={props.name}
options={makeEmailOptions(props.ccemailfilter)}
defaultValue={defaultVal}
/>
</div>
)
I came with the following solution, since my component use a function to set some variables to the select, i use a useEffect to call that with a filter right after the page render.
useEffect(() => {
handleChange(makeEmailOption(props.ccemailfilter.filter(x => x.selected)))
}, [])
const handleChange = (value) => {
setSelectedOption(value)
props.emails(value)
}
So, the handleChange are called on the onChange of the select and once after the page loads, to create a value to the select to use.

Resources