How to reset a react-select when the options is changed, this happen because im using chaining select, so my second select options will change based on my first select, what im trying to do is reset back the select to "please select" when my second option already picked before, im using react-select with react-hook-form
import React, { useState, useEffect } from 'react';
import { default as ReactSelect } from 'react-select';
import { FormGroup, Label } from 'reactstrap';
import { useFormContext, Controller } from 'react-hook-form';
import { ErrorMessage } from '#hookform/error-message';
export default function Select(props) {
const {
label,
isMulti,
note,
// isDisabled,
// withDefaultValue,
options,
isClearable,
name,
placeholder = 'Pilihan'
} = props;
const rhfContext = useFormContext(); // retrieve all hook methods
const { control, errors } = rhfContext || {};
const [elOptions, setElOptions] = useState([]);
useEffect(() => {
setElOptions(options);
}, [options]);
return (
<FormGroup>
{label && <Label htmlFor={name || ''}>{label}</Label>}
<Controller
as={ReactSelect}
name={name}
control={control}
options={elOptions}
placeholder={placeholder}
styles={customStyles}
{...(isMulti ? { isMulti: true } : {})}
{...(isClearable ? { isClearable: true } : {})}
classNamePrefix="react-select-pw"
className="react-select-container"
/>
{note && <span>{note}</span>}
<ErrorMessage
name={name}
errors={errors}
render={() => {
return <p className="err-msg">pilih salah satu</p>;
}}
/>
</FormGroup>
);
}
Basically you need to handle the onChange of your react-select
const funcComponent = () => {
const [firstOptions, setFirstOptions] = useState({});
const [secondOptions, setSecondOptions] = useState({});
useEffect(() => {
//Here dispatch your defined actions to load first select options
setFirstOptions(response-data)
})
const handleFirstOptions = selectedVal => {
//Here dispatch your defined action to load second select options
setSecondOptions(response-data)
}
const handleSecondOptions = selectedVal => {
//Your action to perform
}
return (
<Label>First Option Field</Label>
<Select
options={firstOptions}
onChange={handleFirstOptions}
/>
Label>Second Option Field</Label>
<Select
options={secondOptions}
onChange={handleSecondOptions}
/>
)}
Related
In this simple "demo" in CodeSandbox: https://codesandbox.io/s/cool-fast-fi426k?file=/src/App.tsx
you can see that checked actually changes, based on an external condition (inputted text length), but this does not "physically"change the Switch
import "./styles.css";
import * as React from "react";
import { Switch } from "antd";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
let [checked, setChecked] = React.useState(false);
const onSubmit = (data: string) => {
console.log("data.text: ", data.text);
let length = data.text.length;
console.log("data.text.length: ", length);
if (length > 5) {
console.log("'checked' variable has to be set as TRUE");
setChecked((checked) => true);
} else {
console.log("'checked' variable has to be set as FALSE");
setChecked((checked) => false);
}
};
const AntdOnChange = (checked) => {
console.log(`switch to ${checked}`);
};
React.useEffect(() => {
AntdOnChange(checked);
}, [checked]);
return (
<div className="App">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="text">Text</label>
<input id="text" placeholder="text" {...register("text")} />
</div>
<button type="submit">Submit</button>
</form>
<Switch
checkedChildren="ON"
unCheckedChildren="OFF"
defaultChecked
onChange={AntdOnChange}
/>
</div>
);
}
How to pass the correctly changed value of checked variable to the Switch state ?
You can achieve this by doing three things
Remove the useEffect that updates the checked state. If you pass the state into the switch component with that, it will cause multiple renders which will cause an error
Do this in your switch component. I have added the checked prop
<Switch
checkedChildren="ON"
unCheckedChildren="OFF"
defaultChecked
checked={checked}
onChange={AntdOnChange}
/>
In your AntdOnChange function, do this. This function will work independently of whatever is added in the input
const AntdOnChange = (checked) => {
setChecked((checked) => !checked);
};
Below is the snapshot of my search filter code of table component(react-table). I'm trying to cover test cases for unreachable code using jest. Is their anyway to cover test cases for onAsyncDebounceChange method?
import { React, useMemo, useState } from "react";
import { useAsyncDebounce } from "react-table";
import { Label, Input } from "reactstrap";
import { isValidSearchInput } from "./helper"
// Component for Global Filter
export function GlobalFilter({ globalFilter, setGlobalFilter }) {
const [value, setValue] = useState(globalFilter);
const onChange = value => {
if (isValidSearchInput(value)) {
setValue(value);
onAsyncDebounceChange(value);
}
};
const onAsyncDebounceChange = useAsyncDebounce(value => {
setGlobalFilter (value);
}, 500);
return (
<div>
<Label>Search Table: </Label>
<Input
value={value || ""}
onChange={(e) => {
onChange(e.target.value);
}}
placeholder=" Enter value "
/>
</div>
);
}
Getting the error in browser Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()
My code:
import { yupResolver } from '#hookform/resolvers/yup'
import { useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { contactSchema } from 'schemas/schemas'
import { InputFloatLabel } from './components/Inputs/InputFloatLabel'
type TypeFormInput = {
name: string
email: string
textarea: string
}
export const Register = () => {
const [isLoading, setIsLoading] = useState(false)
const {
register,
handleSubmit,
formState: { errors },
} = useForm<TypeFormInput>({ resolver: yupResolver(contactSchema) })
const onSubmit: SubmitHandler<TypeFormInput> = async ({ name, email }) => {
console.log('🚀 ~ file: Register.tsx ~ line 25 ~ email', email)
console.log('🚀 ~ file: Register.tsx ~ line 25 ~ name', name)
}
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<InputFloatLabel
type="text"
placeholder="Name"
{...register('name')}
/>
<button type="submit">{isLoading ? 'Loading' : 'Send Mail'}</button>
</div>
</form>
</div>
)
}
And the Input comp:
import { useState } from 'react'
type typeInput = {
placeholder: string
type?: string
}
export const InputFloatLabel: React.FC<typeInput> = ({ type, placeholder, ...props }) => {
const [isActive, setIsActive] = useState(false)
const handleTextChange = (text: string) => {
if (text !== '') setIsActive(true)
else setIsActive(false)
}
return (
<div>
<input
{...props}
id={placeholder}
type={placeholder ? placeholder : 'text'}
onChange={(e) => handleTextChange(e.target.value)}
/>
<label htmlFor={placeholder}>
{placeholder ? placeholder : 'Placeholder'}
</label>
</div>
)
}
I don't have this issue with ChakraUI that I've built but now just doing plain input as a separate component getting that issue.
I have tried some suggestions from here, but still can't fix it: https://github.com/react-hook-form/react-hook-form/issues/85
So the issue is that I think that the {...register("name"}} line actually includes a ref property. You could console.log that out to verify; this is what I found to be true when using {...field} with the ControlledComponent. A very quick fix to get rid of the console error is to just, after the line with the spread, to add a ref={null} to override this ref that is being passed in from the library.
You forgot to forward the ref in your InputFloatLabel. See https://reactjs.org/docs/forwarding-refs.html
In your case it would look like this:
export const InputFloatLabel: React.FC<typeInput> =
// Use React.forwardRef
React.forwardRef(({type, placeholder, ...props}, ref) => {
const [isActive, setIsActive] = useState(false)
const handleTextChange = (text: string) => {
if (text !== '') setIsActive(true)
else setIsActive(false)
}
return (
<div>
<input
ref={ref /* Pass ref */}
{...props}
id={placeholder}
type={placeholder ? placeholder : 'text'}
onChange={(e) => handleTextChange(e.target.value)}
/>
<label htmlFor={placeholder}>
{placeholder ? placeholder : 'Placeholder'}
</label>
</div>
)
})
In https://react-hook-form.com/faqs, scroll to "How to share ref usage?" may help?
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const firstNameRef = useRef(null);
const onSubmit = data => console.log(data);
const { ref, ...rest } = register('firstName');
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...rest} name="firstName" ref={(e) => {
ref(e)
firstNameRef.current = e // you can still assign to ref
}} />
<button>Submit</button>
</form>
);
}
the field element and register function pass a ref to the element. If you define a custom React Component and try to use it within a controller or with register, funky things can happen. I found using React.forwardRef() solves the problem when using Custom Components.
CustomSwitchComponent.tsx
import React from 'react';
import {FormControlLabel, Switch, SxProps, Theme} from "#mui/material";
const TradingStrategyEditFormInstructionsInputSwitch:
// Note this type allows us to do React.forwardRef(Component)
React.ForwardRefRenderFunction<HTMLButtonElement, {
label: string,
checked: boolean,
onBlur: React.FocusEventHandler<HTMLButtonElement>
}> = ({label, ...rest}) => {
// Spreading ...rest here will pass the ref :)
return <FormControlLabel control={<Switch {...rest} />}
labelPlacement={"top"}
label={label}
/>;
};
// *Huzzah!*
export default React.forwardRef(TradingStrategyEditFormInstructionsInputSwitch);
CustomSwitchController.tsx
<Controller
control={formCtx.control}
name={fieldPath}
key={type + side + key}
render={({field}) => {
return <TradingStrategyEditFormInstructionsInputField
{...field}
label={key}
checked={field.value}
onBlur={() => {
field.onBlur()
handleOnBlur(key)
}}
/>
}}/>
Idk if we're allowed to link YT Videos, but techsith has a great video on using forwardRef with useRef that should clear things up.
https://www.youtube.com/watch?v=ScT4ElKd6eo
Your input component does not export ref as props since it is a functional component.
React.useEffect(() => {
register('name', { required: true });
}, []);
<InputFloatLabel
type="text"
placeholder="Name"
name="name"
// Remove the register from here
/>
I've been customising a Material UI Autocomplete within a Controller from React Hook Form, as part of a much larger form, with some difficulty.
The dropdown lists suggestions drawn from the database (props.items, represented here as objects) and if the suggestion is not there, there's the option to add a new one in a separate form with a button from the dropdown. This 'secondComponent' is opened with conditional rendering.
As it gets passed to the second form, the data is stored in state (heldData) and then passed back into the form via React Hook Form's reset, here as reset(heldData).
This updates the value of the form perfectly, as I have an onChange event that sets the value according to what was passed in. React Hook Form handles that logic with the reset and gives the full object to the onChange.
However, I also want to set the InputValue so that the TextField is populated.
In order to create a dynamic button when there are no options ('Add ....(input)... as a guest'), I store what is typed into state as 'texts'. I thought that I could then use the OnChange event to use the same state to update the inputValue, as below. However, when I setTexts from the onChange, the change isn't reflected in the inputValue.
Perhaps this is because the useState is async and so it doesn't update the state, before something else prevents it altogether. If so, it's much simpler than the other code that I have included, but wasn't certain. I have excluded most of the form (over 500 lines of code) but have tried to keep any parts that may be appropriate. I hope that I have not deleted anything that would be relevant, but can update if necessary.
Apologies. This is my first question on Stack Overflow and I'm quite new to React (and coding) and the code's probably a mess. Thank you
**Form**
import React, { useState, useEffect} from "react";
import AutoCompleteSuggestion from "../general/form/AutoCompleteSuggestion";
import SecondComponent from './SecondComponent'
import { useForm } from "react-hook-form";
const items = {
id: 2,
name: "Mr Anderson"
}
const items2 = {
id: 4,
name: "Mr Frog"
}
const defaultValues = {
guest: 'null',
contact: 'null',
}
const AddBooking = () => {
const { handleSubmit, register, control, reset, getValues} = useForm({
defaultValues: defaultValues,
});
const [secondComponent, setSecondComponent] = useState(false);
const [heldData, setHeldData] = useState(null)
const openSecondComponent = (name) => {
setSecondComponent(true)
const data = getValues();
setHeldData(data);
}
useEffect(() => {
!secondComponent.open?
reset(heldData):''
}, [heldData]);
const onSubmit = (data) => {
console.log(data)
};
return (
<>
{!secondComponent.open &&
<form onSubmit={handleSubmit(onSubmit)}
<AutoCompleteSuggestion
control={control}
name="guest"
selection="id"
label="name"
items={items}
openSecondComponent={openSecondComponent}
/>
<AutoCompleteSuggestion
control={control}
name="contact"
selection="id"
label="name"
items={items2}
openSecondComponent={openSecondComponent}
/>
</form>
};
{secondComponent.open?
<SecondComponent/>: ''
};
</>
);
};
And this is the customised AutoComplete:
**AutoComplete**
import React, { useState } from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete, from "#material-ui/lab/Autocomplete";
import parse from "autosuggest-highlight/parse";
import match from "autosuggest-highlight/match";
import { Controller } from "react-hook-form";
import Button from "#material-ui/core/Button";
const AutoCompleteSuggestion = (props) => {
const [texts, setTexts] = useState('');
return (
<>
<Controller
name={props.name}
control={props.control}
render={({ onChange }) => (
<Autocomplete
options={props.items}
inputValue={texts} //NOT GETTING UPDATED BY STATE
debug={true}
getOptionLabel={(value) => value[props.label]}
noOptionsText = {
<Button onClick={()=> props.opensSecondComponent()}>
Add {texts} as a {props.implementation}
</Button>}
onChange={(e, data) => {
if (data==null){
onChange(null)
} else {
onChange(data[props.selection]); //THIS ONCHANGE WORKS
setTexts(data[props.label]) //THIS DOESN'T UPDATE STATE
}}
renderInput={(params) => (
<TextField
{...params}
onChange = { e=> setTexts(e.target.value)}
/>
)}
renderOption={(option, { inputValue }) => {
const matches = match(option[props.label1, inputValue);
const parts = parse(option[props.label], matches);
return (
<div>
{parts.map((part, index) => (
<span
key={index}
style={{ fontWeight: part.highlight ? 700 : 400 }}
>
{part.text}
</span>
))}
</div>
);
}}
/>
)}
/>
</>
);
};
export default AutoCompleteSuggestion;
I am using rsuitejs for components, and in a form I am creating I have a custom slider called MotivationSlider which isn't updating the data in the form it's in when the value is changed. The custom Slider looks like this:
import React, { useState } from 'react';
import { Slider } from 'rsuite';
const MotivationSlider = () => {
const [motivation, setMotivation] = useState(2);
return (
<Slider
min={0}
max={4}
value={motivation}
graduated
progress
className="custom-slider"
onChange={v => setMotivation(v)}
renderMark={mark => {
if ([0, 4].includes(mark)) {
return <span>{mark === 0 ? 'Not Very' : 'Highly!'}</span>;
}
return null;
}}
/>
);
};
export default MotivationSlider;
It is wrapped in a Custom Field which looks like this:
// CustomField.js:
import React from 'react';
import { FormGroup, ControlLabel, FormControl, HelpBlock } from 'rsuite';
const CustomField = ({ name, message, label, accepter, error, ...props }) => {
return (
<FormGroup className={error ? 'has-error' : ''}>
<ControlLabel>{label} </ControlLabel>
<FormControl
name={name}
accepter={accepter}
errorMessage={error}
{...props}
/>
<HelpBlock>{message}</HelpBlock>
</FormGroup>
);
};
export default CustomField;
// FormPage.js:
<CustomField
accepter={MotivationSlider}
name="motivation"
/>
When I change the slider value, the form data does not change, however if I use the CustomField with a normal Slider like this, the form value does change.
<CustomField
accepter={Slider}
name="motivation"
min={0}
max={4}
/>
What am I doing wrong here?
Form custom controls need to implement the attributes onChange, value, and defaultValue.
import React, { useState } from "react";
import { Slider } from "rsuite";
const MotivationSlider = React.forwardRef((props, ref) => {
const { value: valueProp, defalutValue, onChange } = props;
const [motivation, setMotivation] = useState(defalutValue);
const value = typeof valueProp !== "undefined" ? valueProp : motivation;
return (
<Slider
ref={ref}
min={0}
max={4}
value={value}
graduated
progress
className="custom-slider"
onChange={(v) => {
onChange(v);
setMotivation(v);
}}
renderMark={(mark) => {
if ([0, 4].includes(mark)) {
return <span>{mark === 0 ? "Not Very" : "Highly!"}</span>;
}
return null;
}}
/>
);
});
export default MotivationSlider;