When using <AsyncTypeahead/> I can't use onSearch and onInputChange at the same time - react-bootstrap-typeahead

So as I said in the title, I can't use onSearch and onInputChange at the same time. If I try to, onSearch gets ignored. Can you tell me why or is there an alternative to onInputChange? Im using AsyncTypeahead in a Form, so I need the values when I click a button.
const [from, setFrom] = useState('');
const onSearchStart = (query) => {
setTypeAhead({
...typeAhead,
startIsLoading: true
})
setFrom(query)
getStations(query).then(r => {
setTypeAhead({
...typeAhead,
startIsLoading: false,
startOptions: r || []
})
})
}
return(
<AsyncTypeahead
id={"from"}
isLoading={typeAhead.startIsLoading}
options={typeAhead.startOptions}
onInputChange={setFrom}
onSearch={onSearchStart}
/>
)
This isn't the whole code, but it shows everything that is causing the error

As a workaround you can use useRef to not trigger the re-render. Here is the code:
const fromRef = useRef('');
const onSearchStart = (query) => {
...
}
return(
<AsyncTypeahead
...
onInputChange={(text) => {
fromRef.current = text;
}}
onSearch={onSearchStart}
/>
)

Related

Redux toolkit slice. Pure functions inside field reducers

Sup ya. Just wanna specify can we use it? I think we can just wanna be sure 100%
https://codesandbox.io/s/redux-toolkit-state-new-array-4sswi?file=/src/redux/slices/slice.js:111-425
const someSlice = createSlice({
name: "someSlice",
initialState: {
users: [],
status: ""
},
reducers: {
actionSuccess: (state, { payload }) => {
state.users = payload.users;
},
actionFailure: (state) => {
// can we use here function?
statusHandler(state)
}
}
});
A reducer should not trigger any side effects. That is, it should not do anything other than change the contents of the state. So you should not call a statusHandler which would trigger external effects.
If your statusHandler function does nothing but update the state, that does seem to work in my testing and I'm not aware of any reason why it shouldn't be okay.
Redux Toolkit uses Immer behind the scenes to deal with immutable updates, so this question basically boils down to whether these two functions, updatedInline and updatedExternally, are equivalent. They are, as far as I can tell.
const {produce} = immer;
const initialState = {
status: ''
};
const updatedInline = produce( initialState, draft => {
draft.status = 'failed';
})
const mutateStatus = (state) => {
state.status = 'failed';
}
const updatedExternally = produce( initialState, mutateStatus )
console.log("Updated Inline: \n", updatedInline);
console.log("Updated Externally: \n", updatedExternally);
<script src="https://cdn.jsdelivr.net/npm/immer#8.0.1/dist/immer.umd.production.min.js"></script>

Open param doesn't work (InlineDatePicker) - material ui

import { MuiPickersUtilsProvider, InlineDatePicker } from 'material-ui-pickers'
<InlineDatePicker
shouldDisableDate={day => isSameDay(day, new Date())}
onlyCalendar
open={isOpen}
minDate={addYears(new Date(), -18)}
value={begin}
renderDay={renderDay}
onClose={() => {
onChange([begin, end].sort())
if (onClose) onClose()
}}
onClear={() => {
setBegin(undefined)
setEnd(undefined)
setHover(undefined)
onChange([])
}}
ref={picker}
labelFunc={(date, invalid) =>
labelFunc
? labelFunc([begin, end].sort(), invalid)
: date && begin && end
? `${formatDate(begin)} - ${formatDate(end)}`
: emptyLabel || ''
}
{...props}
/>
I try to open date picker by using open parameter , but it doesn't work, what do I do wrong ? Version is : "material-ui-pickers": "^2.2.4". Also I pass this param to prevent showing input text field TextFieldComponent={() => null}
const picker = useRef()
const onClick = e => {
return picker.current.open(e)
}
.....
<InlinePicker>
ref={picker}
</InlinePicker /
This will be working

ReactDataGrid row selection does not work

I am trying to build data table using react and react-data-grid version "^7.0.0-canary.16",
The render method looks like this:
render() {
return (
<div className={"component"}>
<ReactDataGrid width={600} height={400}
rowKey="id"
columns={this.state.columns}
rows={this.state.rows}
onRowClick={this.onRowClick}
rowSelection={{
showCheckbox: true,
enableShiftSelect: true,
onRowsSelected: this.onRowsSelected,
onRowsDeselected: this.onRowsDeselected,
selectBy: {
indexes: this.state.selectedIndexes
}
}}
/>
</div>
)
}
So following the documentation on page https://adazzle.github.io/react-data-grid/docs/examples/row-selection
it should display checkbox in first column and when I select the checkbox it should call method this.onRowsSelected.
Alas, no checkbox is shown and no matter how I click the this.onRowsSelected method is never called.
On the other hand the method this.onRowClick is called, whenever I click somewhere in the table.
Does anyone have experience with this?
It seems to be showing the checkboxes with "react-data-grid": "6.1.0"
Although, I'm having issue with the checkboxes when we filter the data. The rowIdx changes and we lose context of that was previously selected. We want to make BE calls on selected Data. I tried changing it to use the row.id but no luck. It messes up the selection.
Here is a hook for managing the selection
import {useState} from 'react';
export const useRowSelection = () => {
const [selectedIndexes, setSelectedIndexes] = useState([]);
const onRowsSelected = rows => {
setSelectedIndexes(prevState => {
return prevState.concat(rows.map(r => r.rowIdx));
});
};
const onRowsDeselected = rows => {
let rowIndexes = rows.map(r => r.rowIdx);
setSelectedIndexes(prevState => {
return prevState.filter(i => rowIndexes.indexOf(i) === -1);
});
};
return {
selectedIndexes,
onRowsDeselected,
onRowsSelected,
};
};
Pass them to the RDG
const {selectedIndexes, onRowsDeselected, onRowsSelected} = useRowSelection();
const rowSelectionProps = enableRowSelection
? {
showCheckbox: true,
enableShiftSelect: true,
onRowsSelected: onRowsSelected,
onRowsDeselected: onRowsDeselected,
selectBy: {
indexes: selectedIndexes,
},
}
: undefined;
<ReactDataGrid
columns={columnDefinition}
getValidFilterValues={getFilterValues}
rowGetter={i => filteredData[i]}
rowsCount={filteredData.length}
onAddFilter={filter => handleOnAddFilter(filter)}
onClearFilters={() => handleOnCleanFilters()}
toolbar={toolbar}
contextMenu={contextMenu}
RowsContainer={ContextMenuTrigger}
rowSelection={rowSelectionProps}
rowKey="id"
/>

Autocomplete - How can I set a default value?

Does anyone know how to add a default value to the Autocomplete component?
The component have a dataSource, and I'd like to load the page with a specific item already selected(e.g. fill the text field with the selected item's text and it's value already set).
Does anyone knows how? Big thanks for any help <3
You can achieve this by setting the appropriate state in componentDidMount, for example:
componentDidMount() {
// load your items into your autocomplete
// set your default selected item
this.setState({ allItems: [itemYouWantToSet], selectedItem: item.name, selectedItemId: item.id }
}
render() {
return (
<Autocomplete
value={this.state.selectedItem}
items={this.state.allItems}
getItemValue={(item) => item.name}
onSelect={(value, item) => {
this.setState({ selectedItem: value, selectedItemId: value, allItems: [item] });
}}
/>
)
}
Then your item is correctly selected from the list of available options when it loads.
I tried all the above solutions and nothing worked. Perhaps the API has changed since then.
I finally figured out a solution. It's not so elegant, but in principle it makes sense. In my case the options are objects. I just had to set the "value" prop using the exact item from my options array. This way componentDidMount and getOptionSelected aren't needed.
Autocomplete is wrapped inside another component in our case. This is the main code:
class CTAutoComplete extends React.PureComponent {
getSelectedItem(){
const item = this.props.options.find((opt)=>{
if (opt.value == this.props.selectedValue)
return opt;
})
return item || {};
}
render() {
return (
<Autocomplete
id={this.props.id}
className={this.props.className}
style={{ width: '100%' }}
options={this.props.options}
getOptionLabel={this.props.getOptionLabel}
renderInput={params => (
<TextField {...params} label={this.props.label} variant="outlined" />
)}
onChange={this.props.onChange}
value={this.getSelectedItem()}
/>
);
}
}
IMPORTANT: When setting "value", you have to make sure to put the null case " || {} ", otherwise React complains you are changing from an uncontrolled to controlled component.
you can provide the defaultValue prop for AutoComplete.
<Autocomplete
multiple
id="tags-outlined"
options={this.state.categories}
getOptionLabel={(option) => option.category_name}
onChange={this.handleAutocomplete}
defaultValue={'yourDefaultStringValue'} //put your default value here. It should be an object of the categories array.
filterSelectedOptions
renderInput={(params) => (
<TextField
fullWidth
{...params}
variant="outlined"
label="Add Categories"
placeholder="Category"
required
/>
}
/>
This approach works for me (using hooks):
First of all define the options you need in a variable:
const genderOptions = [{ label: 'M' }, { label: 'V' }];
Second you can define a hook to store the selected value (for example store it in session storage for when the page refreshes, or use useState directly):
const age = useSessionStorage('age', '');
Next you can define your Autocomplete as follows (notice the default values in value and getOptionLabel, if you omit those you'll get those controlled to uncontrolled warnings):
<Autocomplete
id="id"
options={ageOptions}
getOptionLabel={option => option.label || ""}
value={ageOptions.find(v => v.label === age[0]) || {}} // since we have objects in our options array, this needs to be a object as well
onInputChange={(_, v) => age[1](v)}
renderInput={params => (
<TextField {...params} label="Leeftijd" variant="outlined" />
)}
/>
It is tricky specially in case of you are using along with filter option which load API on every filter. I was able to load initial value by setting up within state and onInputChange option.
Below is code that work for me or click below link for full working demo.
https://codesandbox.io/s/smoosh-brook-xgpkq?fontsize=14&hidenavigation=1&theme=dark
import React, { useState, useEffect } from "react";
import TextField from "#material-ui/core/TextField";
import Typography from "#material-ui/core/Typography";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function CreateEditStrategy({ match }) {
const [user, setUser] = useState({
_id: "32778",
name: "Magic User's Club!"
});
const [filter, setFilter] = useState("");
const [users, setUsers] = useState([]);
const [openAutoComplete, setOpenAutoComplete] = React.useState(false);
useEffect(() => {
(async () => {
//Will not filter anything for testing purpose
const response = await fetch(
`https://api.tvmaze.com/search/shows?q=${filter}`
);
const shows = await response.json();
setUsers(
shows.map((a, i) => {
return { _id: a.show.id, name: a.show.name };
})
);
})();
}, [filter]);
return (
<div>
<Typography variant="h6">Autocomplete</Typography>
<Autocomplete
open={openAutoComplete}
onOpen={() => setOpenAutoComplete(true)}
value={user}
inputValue={filter}
onClose={() => setOpenAutoComplete(false)}
onChange={(event, user) => {
if (user) setUser(user);
else setUser({ _id: "", name: "" });
}}
onInputChange={(event, newInputValue) => setFilter(newInputValue)}
getOptionSelected={(option, value) => option.name === value.name}
getOptionLabel={(option) => option.name}
options={users}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
InputProps={{
...params.InputProps
}}
/>
)}
/>
</div>
);
}
Call your component like this
<SelectCountryAutosuggest searchText="My Default Value" />
Make sure you apply the default value to state on class load
class SelectCountryAutosuggest extends React.Component {
state = {
value: this.props.searchText, //apply default value instead of ''
suggestions: [],
};
...
}
The api docs suggest the best approach in the current version (June 2022) is to use value and isOptionEqualToValue.
So for example, if I have a list of users and am choosing which user this thing is assigned to:
const [assignedTo, setAssignedTo] = useState(initialOption);
return (<Autocomplete
options={users.map((i) => ({
label: i.name,
value: i._id,
}))}
isOptionEqualToValue={(o, v) => o.value === v.id}
value={assignedTo}
onChange={(evt, val) => setAssignedTo(val)}
renderInput={(params) => (
<TextField {...params} label="Assigned To" />
)}
/>);
We can setup initial value through value property of Autocomplete component
<Autocomplete
fullWidth={true}
label={'Location'}
margin={'noraml'}
multiple={false}
name={'location'}
getOptionSelected={useCallback((option, value) => option.value === value.value)}
value={formValues.location === '' ? {label: ''} : {label: formValues.location}}
options={location}
ref={locationRef}
onChange={useCallback((e, v) => handleInputChange(e, v))}
/>
You can use the searchText property.
<AutoComplete searchText="example" ... />
Try this...
componentWillReceiveProps(nextProps) {
let value = nextProps.value
if (value) {
this.setState({
value: value
})
}
}
onUpdateInput worked for me - for anyone looking through all this as I was
Have you tried setting the searchText prop dynamically? You can pass the value you want to set to the Autocomplete component as the searchText prop. Something like,
<Autocomplete
searchText={this.state.input.name} // usually value={this.state.input.name}
/>
By default, it'll have the initial value set into the TextField of the Autocomplete component but when the user makes any modifications, it calls up the Autocomplete options depending on the dataSource prop.

How to set focus to a materialUI TextField?

How can I set the focus on a material-ui TextField component?
componentDidMount() {
ReactDom.findDomNode(this.refs.myControl).focus()
}
I have tried above code, but it does not work :(
For React 16.8.6, you should use the inputRef property of TextField to set focus. I tried ref property but it doesn't work.
<TextField
inputRef={input => input && input.focus()}
/>
Material-ui doc says:
inputRef: Use this property to pass a ref callback to the native input component.
You can use the autoFocus attribute.
<TextField value="some value" autoFocus />
autoFocus was also not working for me, perhaps since this is a component that's not mounted when the top-level component loads. I had to do something a lot more convoluted to get it to work:
function AutoFocusTextField(props) {
const inputRef = React.useRef();
React.useEffect(() => {
const timeout = setTimeout(() => {
inputRef.current.focus();
}, 100);
return () => {
clearTimeout(timeout);
};
}, []);
return <TextField inputRef={inputRef} {...props} />;
}
Note that for some reason it does not work without the setTimeout. For more info see https://github.com/callemall/material-ui/issues/1594.
If you are using material-ui TextField and react functional component, you can pass inputRef in your TextField component. The trick here is the if condition if(input != null).
<TextField
variant="filled"
inputRef={(input) => {
if(input != null) {
input.focus();
}
}}
/>
Here is an working example for you. CodeSandBox- Material-ui-TextFieldFocus
add this propery to your TextField component :
inputRef={(input) => input?.focus()}
This will focus the component every time it renders. Other solutions I tried only focus the element an initial time.
const inputRef = React.useRef<HTMLInputElement>();
useEffect(() => {
inputRef.current?.focus();
}, [inputRef.current]);
const setTextInputRef = (element: HTMLInputElement) => {
inputRef.current = element;
};
return (
<TextField
inputRef={setTextInputRef}
/>
const handleClick = () => {
inputRef.current.firstChild.focus();
inputRef.current.firstChild.placeholder = '';
}
<InputBase
value={value}
ref={inputRef}
placeholder="search" />
<Button onClick={handleClick}>Click</Button>
This code is actually good, but has a drawback, on every render it's going to create a new function. It easily can be solved using useCallback
<TextField
inputRef={input => input && input.focus()}
/>
Should be
const callbackRef = useCallback((inputElement) => {
if (inputElement) {
inputElement.focus();
}
}, []);
...
<TextField
inputRef={callbackRef}
/>
useRef hook simple example:
const focusMe_Ref = useRef(null); // 1. create
useEffect(() => {
focusMe_Ref.current.focus(); // 2. startup
}, []);
...
<TextField
inputRef={focusMe_Ref} // 3. will focused
...
/>
I am using this solution, works for text fields inspired by https://gist.github.com/carpben/de968e377cbac0ffbdefe1ab56237573
const useFocus = (): [any, () => void] => {
const htmlElRef: MutableRefObject<any> = useRef<HTMLDivElement>();
const setFocus = (): void => {
if (!htmlElRef || !htmlElRef.current) return
const div = htmlElRef.current as HTMLDivElement
if (!div) return
const input = div.querySelector("input")
if (input) input.focus()
}
return [htmlElRef, setFocus];
};
export function MyComp() {
const [ref, setFocus] = useFocus()
// use setFocus() to focus the input field
return <Input ref={ref} />
}
I had a similar problem where the input field didn't regain focus after I've modified its contents with external controls (an emoji picker).
I ended up with this brute workaround hook:
const useEnforceFocusTextField = (textFieldRef: React.RefObject<HTMLInputElement>) => {
const [enforcedRerender, setEnforcedRerender] = useState(false);
React.useEffect(() => {
textFieldRef.current?.focus();
const timeout = setTimeout(() => {
textFieldRef.current?.focus();
}, 100);
return () => clearTimeout(timeout);
}, [enforcedRerender]);
return () => setEnforcedRerender((n) => !n);
};
From the outside, you call utilize this hook in the following manner:
const textFieldRef = useRef<HTMLInputElement>(null);
...
// enforceFocus() can be called in any callback to set the focus to the textfield
const enforceFocus = useEnforceFocusTextField(textFieldRef);
...
return <TextField inputRef={textFieldRef} ... />
For a material ui TextField you need to input the props for autoFocus in a inputProps object like this.
<TextField inputProps={{ autoFocus: true }} />
AlienKevin is correct ( pass a ref callback to "TextField.inputProps" ), but you can also save the element reference on your "this" object, so that you can set focus later. Here is an example in Coffeescript:
TextField
inputProps:
ref: (el)=>
if el?
#input_element = el
Button
onClick:=>
#input_element.focus()