I use below code which is to render a multi selection react-select component. But what I found is that when I type any characters and leave the focus before select any items, the characters are not persisted.
import React from 'react';
import Select from 'react-select';
import makeAnimated from 'react-select/animated';
import { colourOptions } from '../data';
const animatedComponents = makeAnimated();
export default function AnimatedMulti() {
return (
<Select
closeMenuOnSelect={false}
components={animatedComponents}
defaultValue={[colourOptions[4], colourOptions[5]]}
isMulti
options={colourOptions}
/>
);
}
As an example of that, see below screenshot. I typed f and move the focus on other component. Then the f will be removed from the select component. Is there a way to persist the f when it loses focus?
You can achieve this by combining inputValue, value, onInputChange and onChange props.
Something like this
<Select
value={this.state.value}
onChange={value => this.setState({ value })}
inputValue={this.state.inputValue}
onInputChange={inputValue => this.setState({ inputValue })}
/>
You can read more about this in the docs
Related
I am building a custom Dropdown component which uses React-Select under the hood. I'd like to add support for refs. I have declared a ref in the app using useRef and am passing this into the component like this:
import Dropdown from "./Dropdown";
import { useRef } from "react";
const App = () => {
const dropdownRef = useRef<HTMLSelectElement>(null);
return (
<div>
<Dropdown id="hello" ref={dropdownRef} />
</div>
);
};
export default App;
In the Dropdown component I'm using ForwardRef like this:
import { forwardRef } from "react";
import Select from "react-select";
export interface DropdownProps {
id: string;
}
const Dropdown = forwardRef(({ id }: DropdownProps, ref) => (
<Select id={id} ref={ref} />
));
export default Dropdown;
However I am getting a Typescript error:
Type 'MutableRefObject' is not assignable to type 'Ref<Select<unknown, false, GroupBase>> | undefined'.
I have tried swapping out the React Select for another component and I get a similar issue so I think this is a general React/Typescript issue rather than anything specific to React Select.
Code Sandbox
Any help would be much appreciated.
I'm slightly surprised, because normally when you specify the hook's initial value as null the type inference is clever enough.
I don't know react-select very well but have you tried passing a string as an initial value, and deleting the <HTMLSelectElement> from your app component? Let the package's TS definitions do the work...
You should defind forwardRef type, it's a generic function:
const Dropdown = forwardRef<HTMLSelectElement,DropdownProps>(( { id }: DropdownProps, ref) => (
<select id={id} ref={ref}>
<option>1</option>
<option>2</option>
</select>
));
You should specify the props to your forwardRef so it knows what types you are trying to forward, as such:
import { forwardRef } from "react";
export interface DropdownProps {
id: string;
}
const Dropdown = forwardRef<HTMLSelectElement, DropdownProps>(({ id }, ref) => (
<div>
<select id={id} ref={ref}>
<option>1</option>
<option>2</option>
</select>
</div>
));
export default Dropdown;
The first template argument will be helpful to determine the type of ref you are trying to forward, the second will define the arguments your component will receive.
A good cheatsheet here for reference.
I have a problem regarding MUI's MenuItem when combined with Select and rendering it in a separate component.
Here's the codesandbox
Basically, I have something like this:
import { Select } from "#material-ui/core";
import CustomMenuItem from "./CustomMenuItem";
import React from "react";
export default function App() {
const userIds = [1, 2, 3];
return (
<Select
id="user"
name="User"
onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
alert(event.target.value as number);
}}
>
{userIds.map((userId) => (
<CustomMenuItem key={userId} userId={userId} />
))}
</Select>
);
}
And this is the custom item:
import { MenuItem, Typography } from "#material-ui/core";
import React from "react";
interface CustomMenuItemProps {
userId: number;
}
const CustomMenuItem = React.forwardRef<HTMLLIElement, CustomMenuItemProps>(
(props: CustomMenuItemProps, ref) => {
const { userId, ...rest } = props;
return (
<MenuItem value={userId} {...rest} ref={ref}>
<Typography>{userId}</Typography>
</MenuItem>
);
}
);
export default CustomMenuItem;
At first, I've done this without any refs, but this gave me an error in the console (Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?), so after googling a while, I found out that I have to pass this ref. I also pass the ...rest of the props, as I understand that the MenuItem needs them.
Expected behavior: when I click on the MenuItem, it gets selected in the Select component.
Actual behavior: nothing happens.
The thing is, I made the CustomMenuItem to make it reusable. But before that, I had a simple function like: renderItem which I used both in Select.renderValue and in userIds.map and it had the same code as CustomMenuItem - it returned the same JSX tree. And it worked then, but it doesn't work now, for some reason. So if I would do:
<Select
id="user"
name="User"
onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
alert(event.target.value as number);
}}
>
{userIds.map((userId) => (
<MenuItem key={userId} value={userId}>
<Typography>{userId}</Typography>
</MenuItem>
))}
</Select>
it simply works :(
Am I missing something here?
There are a few implementation details of Select that get in the way of trying to customize MenuItem in this way.
Select uses the value prop of its immediate children. The immediate children of the Select in your case are CustomMenuItem elements which only have a userId prop -- not a value prop; so Select finds undefined as the new value when you click on one of your custom menu items.
You can fix this aspect by duplicating your userId prop as a value prop:
import { Select } from "#material-ui/core";
import CustomMenuItem from "./CustomMenuItem";
import React from "react";
export default function App() {
const userIds = [1, 2, 3];
const [value, setValue] = React.useState(1);
console.log("value", value);
return (
<Select
id="user"
name="User"
value={value}
onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
setValue(event.target.value as number);
}}
>
{userIds.map((userId) => (
<CustomMenuItem key={userId} value={userId} userId={userId} />
))}
</Select>
);
}
This then successfully changes the value of the Select if you look at the console logs. The new value is not successfully displayed due to a separate problem I'll explain later.
You may think "then I can just use the value prop instead of the userId prop rather than having both", but the value prop won't actually reach your custom component. Select uses React.cloneElement to change the value prop to undefined and instead puts it in data-value to avoid a value prop being specified in the final html (which wouldn't be a valid attribute for the html element that gets rendered).
In my sandbox above, you'll notice that when you select a value, the new value is not successfully displayed as the selected value. This is because Select uses the children prop of the selected child as the display value unless you specify the renderValue prop. The children prop of the CustomMenuItem element is undefined.
You can fix this by either using the renderValue prop on the Select or by specifying the userId yet again as a child:
import { Select } from "#material-ui/core";
import CustomMenuItem from "./CustomMenuItem";
import React from "react";
export default function App() {
const userIds = [1, 2, 3];
const [value, setValue] = React.useState(1);
console.log("value", value);
return (
<Select
id="user"
name="User"
value={value}
onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
setValue(event.target.value as number);
}}
>
{userIds.map((userId) => (
<CustomMenuItem key={userId} value={userId} userId={userId}>
{userId}
</CustomMenuItem>
))}
</Select>
);
}
This works, but also removes all of the value the custom menu item component was trying to provide. I think the simplest way to achieve this (while still working well with the Material-UI Select design) is to put the reusable code in a function for rendering the menu items rather than making a custom menu item component:
import { Select } from "#material-ui/core";
import React from "react";
import { MenuItem, Typography } from "#material-ui/core";
const renderMenuItem = (value: number) => {
return (
<MenuItem key={value} value={value}>
<Typography>{value}</Typography>
</MenuItem>
);
};
export default function App() {
const userIds = [1, 2, 3];
const [value, setValue] = React.useState(1);
console.log("value", value);
return (
<Select
id="user"
name="User"
value={value}
onChange={(event: React.ChangeEvent<{ value: unknown }>) => {
setValue(event.target.value as number);
}}
>
{userIds.map(renderMenuItem)}
</Select>
);
}
I created a DateTimePicker component for my react-admin project:
import { DateTimePicker, DateTimePickerProps, MuiPickersUtilsProvider } from "#material-ui/pickers";
import { FC } from "react";
import { FieldTitle, InputProps, useInput } from "react-admin";
import MomentUtils from "#date-io/moment";
import moment from "moment";
import "moment/locale/fr";
interface DateTimeInputProps extends InputProps<DateTimePickerProps> {
label: string;
}
export const DateTimeInput: FC<DateTimeInputProps> = ({ source, label, resource, options }) => {
const {
input: { value, onChange },
isRequired,
} = useInput({ source });
return (
<MuiPickersUtilsProvider
libInstance={moment}
utils={MomentUtils}
locale='fr'
>
<DateTimePicker
label={<FieldTitle
label={label}
source={source}
resource={resource}
isRequired={isRequired}
/>}
value={value || null}
onChange={onChange}
format="llll"
ampm={false}
{...options}
/>
</MuiPickersUtilsProvider>
);
}
It works great, however the design does not follow the other classic inputs:
What is the simplest way to keep the same design across custom material-ui components?
There are 3 variants to choose from that are applied to MUI input elements: outlined, filled and standard. You need to provide DateTimePicker with inputVariant prop set to filled to get a "greyish" look. Plus you need to pass a className prop so that react-admin can pass parent controled classses (this should fix width issues).
<DateTimePicker {...props} inputVariant="filled" className={props.className} />
You can see the look of outlined, filled and standard clicking link below:
https://mui.com/components/text-fields/#basic-textfield
I'm using the material ui library, and in the onBlur input event I'm adding 2 events, one that is coming by props and another is declared in the same component.
onBlur={(props.onBlur, validation)}
but he only recognizes me for the second event that happened to him.
It is not recognizing me the two events that are in onBlur.
import React, { useState } from "react";
import FormControl from "#material-ui/core/FormControl";
import FormHelperText from "#material-ui/core/FormHelperText";
import Input from "#material-ui/core/Input";
import InputLabel from "#material-ui/core/InputLabel";
export default function CustomInputExample(props) {
const [validationValue, setValidation] = useState(false);
const validation = () => {
if (props.value === "") setValidation(true);
else setValidation(false);
};
return (
<FormControl>
<InputLabel htmlFor={props.name}>{props.labelText}</InputLabel>
<Input
name={props.name}
aria-describedby="component-helper-text"
type="text"
onChange={props.onChange}
onBlur={(props.onBlur, validation)}
value={props.value}
/>
{props.required !== undefined && validationValue ? (
<FormHelperText id={props.name}>Required field</FormHelperText>
) : null}
</FormControl>
);
}
I need to use those two events in onBlur.
You can call 2 function using an anonymous function,
onBlur={() => {
props.onBlur();
validation();
}}
I am trying to integrate Redux-form v6 into my project, however no matter how closely I try to replicate the example code, I cannot get a working redux-form.
Everything seems to be connected properly, however the handleSubmit function does not capture any of the values from my form fields.
Any guidance on this issue would be greatly appreciated. My code is below.
Starting with the reducer, nothing seems to be the matter here.
import { reducer as formReducer } from 'redux-form';
export default combineReducers({
form: formReducer
});
Then, I use a container component to connect the form component to redux form, which decorates the form component nicely with all the Redux-form function.
CreateCompany.js
import CreateCompany from '../components/create_company';
import { reduxForm } from 'redux-form';
export default reduxForm({
form: 'company-submission'
})(CreateCompany);
The actual form then looks like this:
CreateCompany.jsx
<form onSubmit={ handleSubmit(values => {console.log("values",values)})}>
<div>
<label htmlFor="group">Group Name (Required)</label>
<Field name="group" component={FormInput} type="text"/>
</div>
<div>
<Field
name="group-type"
component={DropDownSelect}
selectOptions={this.state.groupTypes}
id="group-type"
/>
<label>Group Type</label>
</div>
<button type="submit">Log In</button>
</form>
The text input stateless functions supplied to the Field component.
FormInput.js
(Note: I had to include {...input.value} in the input tag to be able to type into the field. In the example code, only {...input} is used.)
import React from 'react';
const FormInput = ({ id, type, className, input }) => {
className = className || "";
id = id || "";
return (
<input id={id} {...input.value} type={type} className={className}/>
)
}
export default FormInput;
DropDownSelect.js
import React from 'react';
const DropDownSelect = ({ input, selectOptions, id }) => {
const renderSelectOptions = (selectOption) => (
<option key={selectOption} value={selectOption}>{selectOption}</option>
)
return (
<select id={id} {...input}>
{selectOptions.map(renderSelectOptions)}
</select>
);
}
export default DropDownSelect;
Any idea what I am doing wrong?
handleSubmit should be defined outside of you CreateCompany component and passed to it via props. Check out examples