Reference problem with external UI library in React Hook Form - react-select

I'm using the ReactSelect component, however, I created a component called Select and ReactSelect is in there:
interface IProps extends ISelectProps {
tabIndex: string;
required: boolean;
isClearable: boolean;
isDisabled: boolean;
placeholder: string;
options: {
label: string;
value: string;
}[];
}
const Select = ({ ...props }: IProps) => <ReactSelect {...props} />;
export default Select
;
The error is as follows:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of `Controller`.
at Select
Here's the code:
<form onSubmit={handleSubmit(onSubmit)} noValidate autoComplete='off'>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
{API.map(({ id, order, htmlControl }) => (
<Controller
key={id}
name={id}
control={control}
render={({ field, fieldState: { error } }) => (
<>
{htmlControl.type === 'PARAMETER_TYPE_SELECT' && (
<Select
{...field}
isClearable={true}
error={!!error}
tabIndex={order.toString()}
required={htmlControl.isRequired}
isDisabled={htmlControl.isDisabled}
placeholder={htmlControl.placeholder}
options={htmlControl.options}
/>
)}
{htmlControl.type !== 'PARAMETER_TYPE_SELECT' && (
<input
required={htmlControl.isRequired}
disabled={htmlControl.isDisabled}
placeholder={htmlControl.placeholder}
/>
)}
</>
)}
/>
))}
</div>
<input type='submit' />
</form>
I'm following exactly like the official documentation and I can't understand why it can't get the reference.

As you read the warning, it says you are attempting to assign ref attribute to a functional component which is not correct, you should not use the ref attribute on function components because they don’t have instances. To solve your problem you can define your Select component by using React.forwardRef like this:
const Select = React.forwardRef(({ ...props }, ref) => (
<ReactSelect {...props} ref={ref} />
));

Related

How to update input defaultValue in React child component when props change?

I can pass props to the child component, but the input value doesn't change, when I update props (e.g. I send down "second" instead of "first", I can console.log "second" but the input value remains "first".) What could be the problem?
Code example:
// in parent component
const ParentComp = () => {
const [showEdit, setShowEdit] = useState(false);
const [currentElement, setCurrentElement] = useState('');
const myList = [{ label: 'first'}, {label: 'second'}]
const editElement = (el) => {
setShowEdit(true);
setCurrentElement(el);
}
return (
<div>
{myList.map((el, i) => (
<span key={i} onClick={() => editElement(el)}>
{el.label}
</span>
))}
{showEdit && (
<ChildComponent elData={currentElement} />
)}
</div>
)}
// in child component
const ChildComponent = ({ elData }) => {
const [testInput, setTestInput] = useState(elData.label)
return (
<input
onChange={(e) => setTestInput(e.target?.value)}
defaultValue={elData.label}
/>
)
}
I found a working solution but I'm not quite sure how it works.
// old code
<input
onChange={(e) => setTestInput(e.target?.value)}
defaultValue={elData.label}
/>
// new code
<input
onChange={(e) => setTestInput(e.target?.value)}
value={testInput || ''}
/>
Can anyone explain the difference?

Why query not updated value but get initial value in Function with ExtReact

I have form fn
code here
#update code. I initialization
query = {
field: '',
email: '',
}
not initialization url
export function HomePage() {
const [query, setQuery] = useState({
field: '',
email: '',
});
const handleChaneValue = (value) => {
// not received data on change
console.log('query', query);
setQuery({
...query,
[value.sender.name]: value.newValue
})
}
console.log('query2', query);
query2 received data on change
console.log('query2', query);
#Update code: I add
return (
<div>
<Container
padding={10}
platformConfig={{
desktop: {
maxWidth: 400,
}
}}
>
<FormPanel
padding={10}
shadow
defaults={{
errorTarget: 'under'
}}
margin="0 0 20 0"
title="Using Validators"
>
<Textfield
required
label="Required Field"
requiredMessage="This field is required."
errorTarget="under"
name="field"
onChange={handleChaneValue}
/>
<EmailField
label="Email"
validators="email"
errorTarget="under"
name="email"
onChange={(e) => handleChaneValue(e)}
/>
<Urlfield
label="URL"
validators={{
type: 'url',
message: 'Website url example http://'
}}
errorTarget="under"
name="url"
onChange={handleChaneValue}
/>
<Container layout="center">
<Button
ui="confirm"
text="BTN submit"
handler={handleClick}
style={{border: '1px solid black' }}/>
</Container>
</FormPanel>
</Container>
</div>
)
}
export default HomePage;
When I change value in TextField. Query is
query2: {field: 'abc'}
But i change value in Email field. Query is not give old value "{filed: 'abc'}" throught I use ES6 three dot.
query2 : {email: 'xyz'}
and Query in funciton always initialization
query: {}
image change value
#Update image: when I change value Url. fn handleChangeValue get initialization query
query: {
field: '',
email: '',
}
does not value query updated.
here issues were you did not initialize the value of textfield and emailfield I updated code pls check now
export function HomePage() {
const [query, setQuery] = useState({
field:'',
email:''
});
const handleChaneValue = (value) => {
// not received data on change
console.log("query", query);
setQuery({
...query,
[value.sender.name]: value.newValue,
});
};
console.log("query2", query);
return (
<div>
<Container
padding={10}
platformConfig={{
desktop: {
maxWidth: 400,
},
}}
>
<FormPanel
padding={10}
shadow
defaults={{
errorTarget: "under",
}}
margin="0 0 20 0"
title="Using Validators"
>
<Textfield
required
label="Required Field"
requiredMessage="This field is required."
errorTarget="under"
name="field"
onChange={handleChaneValue}
value={query.field}
/>
<EmailField
label="Email"
validators="email"
errorTarget="under"
name="email"
value={query.email}
onChange={handleChaneValue}
/>
<Container layout="center">
<Button
ui="confirm"
text="BTN submit"
handler={handleClick}
style={{ border: "1px solid black" }}
/>
</Container>
</FormPanel>
</Container>
</div>
);
}
export default HomePage;
I found the solution to the problem. I using framework ExtReact so when run function, states re-initialized initil value. To solve this problem you can useRef and refer here.
enter link description here

Resetting a formik form in react-native on navigation

I am trying to create a React Native app that can create and edit entities.
For example, users.
I have a formik form that takes first and last name, as well as an email address.
If a user params is passed, then the form should take on those values.
However, it only works the first time. After that, the form keeps the first values.
How can I force the form to be reevaluated?
function FormScreen({ navigation, route }) {
const formikRef = React.createRef();
const initialValues = { firstName: "", lastName: "", email: "" };
if (route.params && route.params.user) {
if (route.params.user.firstName) {
initialValues.firstName = route.params.user.firstName;
}
if (route.params.user.lastName) {
initialValues.lastName = route.params.user.lastName;
}
if (route.params.user.email) {
initialValues.email = route.params.user.email;
}
}
const unsubscribeBlur = navigation.addListener("blur", (e) => {
console.log("form blur");
if (formikRef.current) {
console.log("form reset");
formikRef.current?.resetForm();
}
});
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Formik
innerRef={formikRef}
initialValues={initialValues}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, "Must be 15 characters or less")
.required("Required"),
lastName: Yup.string()
.max(20, "Must be 20 characters or less")
.required("Required"),
email: Yup.string()
.email("Invalid email address")
.required("Required"),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" />
<ErrorMessage name="firstName" />
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" />
<ErrorMessage name="lastName" />
<label htmlFor="email">Email Address</label>
<Field name="email" type="email" />
<ErrorMessage name="email" />
<button type="submit">Submit</button>
</Form>
</Formik>
</View>
);
}
export default FormScreen;
I have code in there to reset the form on blur but the reset doesn't seem to do anything.
You can find the complete code in a snack.
https://snack.expo.io/#hackzilla/create-and-edit-with-formik
Because you are using a tab navigator. The home and form screen will not reMount when you toggle between them.So the formik will not load the new initialValues.In your way,you can reset the form using route params everytime the form screen is focused.But a better way is using a stack navigator,every time create a form using a new instance.
useFocusEffect(
React.useCallback(() => {
if (formikRef.current) {
console.log("form reset");
formikRef.current?.setValues(initialValues);
}
})
);
We can reset the form after submit, so that it will clear all the fields. Please try by replacing your submit method with below mentioned code.
onSubmit={(values, { setSubmitting, resetForm }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
// reset the fields after submit
resetForm();
}, 400);
}}

Custom React-Admin Component

I am trying to create a custom component for edit/create in react admin. The problem I am facing is that the new updated value is not being taken, what my component is doing is adding and removing values from ChipArray with the values coming from a dropdown select
render() {
return (
<React.Fragment>
<Paper>
{this.state.scopes.map((item, key) => {
return (
<Chip
key={key}
label={item.description}
onDelete={(e) => this.handleDelete(e, item)}
/>
)
})}
</Paper>
<div>
<button onClick={(e) => this.displayChoices(e)}>
{this.state.show ? <span>Hide Scopes Select List</span> : <span>Show Scopes Select List</span>}
</button>
<div/>
{this.state.show ? <Field name="scope" component="Select" placeholder="Spaces"
onChange={(e) => this.handleAdd(e)}>
{this.state.data.map((option, index) =>
<option key={index} value={JSON.stringify(option)}>
{option.description}
</option>
)}
</Field> : ''}
</div>
</React.Fragment>
)
}
}
I manged to remove and add to the ChipArray but when I save the channges were never taken, here is the delete and add functions :
handleAdd = (e) => {
e.preventDefault();
const value = JSON.parse(e.target.value);
let selectedScopes = this.state.scopes;
if (selectedScopes.filter(s => (s.id === value.id)).length === 0) {
selectedScopes.push(value);
this.setState({scopes: [...selectedScopes]});
}
};
handleDelete = (e, value) => {
e.preventDefault();
const selectedScopes = this.state.scopes;
this.setState((prevState, props) => {
return {scopes: prevState.scopes.filter(scope => scope !== value)};
})
};
I know I am supposed to use <Field/> from "react-final-form" but I am not sure how or where.
Really sorry if this question was asked before but I looked online and wasn't so lucky and I am stuck on this.
Thanks

Editing a Material UI row within React + Redux

I have a material UI table in a react project, and I want a user to click on the pencil/Edit Icon to edit the table row.
This is a loaded question, but would this require an additional piece of Material UI? what would the logic look like to make this happen?? I have most of the code set up on the backend, but don't know how to write this code in the component???
Thanks for looking!
//STYLE VARIABLE BOR MATERIAL BUTTON
const style = {
margin: 12
//
};
const mapStateToProps = (state) => ({
user: state.user,
reduxState: state.getExpense
});
class ExpenseTable extends Component {
constructor(props) {
super(props);
this.state = {
getExpense: []
};
}
isSelected = (index) => {
return this.state.selected.indexOf(index) !== -1;
};
handleRowSelection = (selectedRows) => {
this.setState({
selected: selectedRows,
});
};
//on page load, DISPATCH GET_EXPENSE is
//SENT TO expenseSaga which then
//goes to getExpenseReducer and appends EXPENSE_DATA to the
//DOM
componentDidMount() {
const { id } = this.props.match.params;
this.props.dispatch({ type: USER_ACTIONS.FETCH_USER });
this.props.dispatch({ type: 'GET_EXPENSE' });
}
componentDidUpdate() {
if (!this.props.user.isLoading && this.props.user.userName ===
null) {
this.props.history.push('home');
}
}
logout = () => {
this.props.dispatch(triggerLogout());
// this.props.history.push('home');
};
//SETS STATE FOR ALL INPUTS
handleChange = (name) => {
return (event) => {
this.setState({
[name]: event.target.value
});
};
};
//SUBMIT BUTTON- TRIGGERS DISPATCH TO EXPENSE SAGA TO ADD DATA
handleClick = () => {
console.log('add expense', this.state);
this.props.dispatch({
type: 'ADD_EXPENSE',
payload: this.state
});
};
//TRASH ICON-TRIGGERS DISPATCH TO EXPENSE SAGA DELETE
handleClickRemove = (id) => {
console.log('delete expense', this.state);
this.props.dispatch({
type: 'DELETE_EXPENSE',
payload: id
});
};
handleClickEdit = (row) => {
console.log('edit expense', this.state)
this.props.dispatch({
type: 'EDIT_EXPENSE',
payload: row
})
}
render() {
console.log('HEY-oooo expense render', this.state);
let content = null;
if (this.props.user.userName) {
//MAP OVER REDUX STATE.
const tableRows = this.props.reduxState.map((row) => {
//.MAP SEPARATES DATA INTO INDIVIDUAL ITEMS.
const { id, item_description, purchase_date,
item_price, item_link } = row;
return (
<TableRow selectable={false}>
{/* TABLE ROWS */}
<TableRowColumn>{item_description}
</TableRowColumn>
<TableRowColumn>{purchase_date}
</TableRowColumn>
<TableRowColumn>${item_price}</TableRowColumn>
<TableRowColumn><a href={item_link}>{item_link}
</a></TableRowColumn>
<TableRowColumn><EditIcon class="grow:hover"
onClick={() => {this.handleClickEdit(row)}} />
</TableRowColumn>
<TableRowColumn><TrashIcon onClick={() =>
{this.handleClickRemove(id);
}}/>
</TableRowColumn>
</TableRow>
// END TABLE ROWS
);
});
<div>
{/* FORM FOR ADDING EXPENSES(DATA) */}
<form id="expenseForm">
<h3>
Add a new <br />
expense
</h3>
<input
type="text"
id="fname"
name="fname"
placeholder="Item description"
onChange=
{this.handleChange('item_description')}
/>
<br />
<br />
<input
type="text"
id="lname"
name="lname"
placeholder="Item price"
onChange={this.handleChange('item_price')}
/>
<br />
<input
type="text"
id="lname"
name="lname"
placeholder="Item link"
onChange={this.handleChange('item_link')}
/>
<br />
{/* END FORM */}
<RaisedButton
id="expSubmit"
label="Submit Expense"
primary={true}
style={style}
onClick={this.handleClick}
/>
{/* TABLE TOTAL KEEPS CURRENT TOTAL OF PRICE COLOUMN */}
<h1>Total:</h1>
<br />
<h3>$748.93</h3>
</form>
{/* TABLE HEADERS */}
<Table>
<TableHeader>
<TableRow>
<TableHeaderColumn>Item
description</TableHeaderColumn>
<TableHeaderColumn>Purchase
Date</TableHeaderColumn>
<TableHeaderColumn>Item
Price</TableHeaderColumn>
<TableHeaderColumn>Item
Link</TableHeaderColumn>
<TableHeaderColumn>Edit
entry</TableHeaderColumn>
<TableHeaderColumn>Delete
entry</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody>
{tableRows}
</TableBody>
</Table>
</div>
);
}
return (
<div>
<Nav />
{content}
</div>
);
}
}
// this allows us to use <App /> in index.js
export default connect(mapStateToProps)(ExpenseTable);
The default material-ui table doesn't have the feature of in-line editing. You will have to write your own wrapper to achieve this which can be time consuming. To overcome the time issue we can use some good existing libraries.
Here's one powerful library from DevExtreme for the same which i used, but go through their licensing before you take the final decision to use.
https://devexpress.github.io/devextreme-reactive/react/grid/demos/featured/controlled-mode/