initialValues not loading in redux-form - react-native

I'm probably using this wrong but I'm trying to get a redux form to load with initialValues which isn't working. I've got a container component which renders a form:
class ProductScreen extends Component {
render() {
return (
<ProductForm {...this.props} {...this.state} />
)
}
}
const mapStateToProps = (state) => {
return {
initialValues: {name: 'Ferrari'}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductScreen)
For the ProductForm component the render() method includes a Field which loads a simple component. Note that it's a single field of name being name. I'm initialising the reduxForm the usual way but when the screen loads the form field isn't being populated with the expected "Ferrari" value:
// importing functions from redux-form
import { Field, reduxForm } from 'redux-form/immutable'
// inside the render() function
<Field component={renderInput} name='name' ref='name' placeholder='Product name' />
const ProductForm = reduxForm({
form: 'createProduct',
enableReinitialize: true
})(ProductFormComponent)
const renderInput = ({input, ...inputProps}) => {
return (
<TextInput {...inputProps} {...input} />)
}
In the React native debugger if I console out the renderInput function I can see the 'Ferrari' value in inputProps.meta.initial but not in input.value

I think you are returning it at wrong place.
Try the following code snippet:
const ProductForm = reduxForm({
form: 'createProduct',
initialValues: {name: 'Ferrari'},
enableReinitialize: true
})(ProductFormComponent)

Related

React Hook Form with Children in React Native

I have a form with ~15 fields where each section is a unique child component. I want to know how to pass data between the parent form and child components(using control because this is react native)
Right now, I see the proper value for testResult in onSubmit logs but data is undefined for some reason. This means my parent form is somehow not picking up the value in the child.
Parent Form:
const Stepper = () => {
const form = useForm({ defaultValues: {
testResult: "",
}
});
const { control, handleSubmit, formState: { errors }, } = form;
const testResult = useWatch({ control, name: "testResult" });
const onSubmit = (data) => {
console.log("watched testResult value: ", testResult);
console.log("form submission data: ", data);
};
return (
<WaterStep form={form} />
<Button onSubmit={handleSubmit(onSubmit())} />
)
}
Child component:
const WaterStep = ({ form }) => {
const { control, formState: { errors }, } = form;
return (
<Controller
name="testResult"
control={control}
rules={{
maxLength: 3,
required: true,
}}
render={({ field: onBlue, onChange, value }) => (
<TextInput
keyboardType="number-pad"
maxLength={3}
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
/>
)}
Here I'm trying the first approach this answer suggests, but I've also tried the second with useFormContext() in child https://stackoverflow.com/a/70603480/8561357
Additionally, must we use control in React Native? The examples that use register appear simpler, but the official docs are limited for React Native and only show use of control
Update: From Abe's answer, you can see that I'm getting undefined because I'm calling onSubmit callback in my submit button. I mistakenly did this because I wasn't seeing any data getting logged when passing onSubmit properly like this handleSubmit(onSubmit). I still think my issue is that my child component's data isn't being tracked properly by the form in parent
The problem is most likely in this line:
<Button onSubmit={handleSubmit(onSubmit())} />
Since you're executing the onSubmit callback, you're not allowing react-hook-forms to pass in the data from the form. Try replacing it with the following
<Button onSubmit={handleSubmit(onSubmit)} />
For anyone still looking for guidance on using react-hook-form with child components, here's what I found out to work well:
Parent Component:
const Stepper = (props) => {
const { ...methods } = useForm({
defaultValues: {
testResult: "",
},
});
const onSubmit = (data) => {
console.log("form submission data: ", data);
};
const onError = (errors, e) => {
return console.log("form submission errors: ", errors);
};
return (
<FormProvider {...methods}>
<WaterStep
name="testResult"
rules={{
maxLength: 3,
required: true,
}}
/>
<Button onSubmit={handleSubmit(onSubmit)} />
)
}
Child:
import { useFormContext, useController } from "react-hook-form";
const WaterStep = (props) => {
const formContext = useFormContext();
const { formState } = formContext;
const { name, label, rules, defaultValue, ...inputProps } = props;
const { field } = useController({ name, rules, defaultValue });
if (!formContext || !name) {
const msg = !formContext
? "Test Input must be wrapped by the FormProvider"
: "Name must be defined";
console.error(msg);
return null;
}
return (
<View>
<Text>
Test Input
{formState.errors.testResult && <Text color="#F01313">*</Text>}
</Text>
<TextInput
style={{
...(formState.errors.phTestResult && {
borderColor: "#f009",
}),
}}
placeholder="Test Value"
keyboardType="number-pad"
maxLength={3}
onBlur={field.onBlur}
onChangeText={field.onChange}
value={field.value}
/>
</View>
);
};
Here's what we're doing:
Define useForm() in parent and de-structure all its methods
Wrap child in <FormProvider> component and pass useForm's methods to this provider
Make sure to define name and rules as props for your child component so it can pass these to useController()
In your child component, define useFormContext() and de-structure your props
Get access to the field methods like onChange, onBlur, value by creating a controller. Pass those de-structured props to useController()
You can go to an arbitrary level of nested child, just wrap parents in a <FormProvider> component and pass formContext as prop.
In Ancestor:
...
const { ...methods } = useForm({
defaultValues: {
testResult: "",
},
});
const onSubmit = (data) => {
console.log("form submission data: ", data);
};
...
<FormProvider {...methods}>
<ChildOne/>
</FormProvider>
In Parent:
const ChecklistSection = (props) => {
const formContext = useFormContext();
const { formState } = formContext;
return (
<FormProvider {...formContext}>
<WaterStep
name="testResult"
rules={{
maxLength: 3,
required: true,
}}
/>
</FormProvider>
)}
Thanks to https://echobind.com/post/react-hook-form-for-react-native (one of the only resources I found on using react-hook-form with nested components in react-native)
....
And a further evaluation of my blank submission data problem, if you missed it:
As Abe pointed out, the reason I didn't see data or errors being logged upon form submission was because onSubmit was not being called. This was because my custom submission button, which I didn't include in my original question for simplicity's sake, had a broken callback for a completion gesture. I thought I solved onSubmit not being called by passing it as a call onSubmit(), but I was going down the wrong track.

test content of a Text element in a stateful component

I am using react-native-testing-library. My component is quite simple:
import React, {Component} from 'react';
import {Text, View} from 'react-native';
import {information} from './core/information';
export default class Logo extends Component {
constructor() {
super();
this.state = {
name: ''
};
information()
.then((details) => {
this.setState({
name: details['name']
});
})
.catch((e) => {
console.log(e);
});
}
render() {
return (
<>
<View>
<Text>{this.state.name}</Text>
</View>
</>
);
}
}
I want to make sure contains the right content. I tried the following but it is failing:
import * as info from "./lib/information";
it('displays correct text', () => {
const spy = jest.spyOn(info, 'information')
const data = {'name':'name'}
spy.mockResolvedValue(Promise.resolve(data));
const {queryByText, debug} = render(<Logo />);
expect(queryByText(data.name)).not.toBeNull();
expect(spy).toHaveBeenCalled();
});
I can confirm the function information() was spied on correctly but still debug(Logo) shows the Text element with empty string.
If it's correctly spying you can try this. I encourage you to use the testID props for the components
render() {
return (
<>
<View>
<Text testID="logo-text">{this.state.name}</Text>
</View>
</>
);
}
import * as info from "./lib/information";
import { waitForElement, render } from "react-native-testing-library";
it('displays correct text', () => {
const spy = jest.spyOn(info, 'information')
const data = {'name':'name'}
//this is already resolving the value, no need for the promise
spy.mockResolvedValue(data);
const {getByTestId, debug} = render(<Logo />);
//You better wait for the spy being called first and then checking
expect(spy).toHaveBeenCalled();
//Spy function involves a state update, wait for it to be updated
await waitForElement(() => getByTestId("logo-text"));
expect(getByTestId("logo-text").props.children).toEqual(data.name);
});
Also, you should move your information call inside a componentDidMount

react-select (AsyncSelewhen typing) not deleting

I have a dropdown using AsyncSelect that when trying to clear it up it doesn't clear. As I have the setup right now I have to: display a previously selected value upon rendering (set by the state, which in turn gets its initial value from a prop), be able to update this value (onChange), be able to type into the input a string to search. Right now, after having a value pre-populated, when I click on the dropdown to type and search, my input doesn't get cleared and I don't see what I input, almost as if the input wasn't allowing edits.
Update: for example, when I click over the drop down I see the cursor but if I hit "delete" the value is not reset.
Any help will be appreciated it.
import React, { Component } from 'react';
import AsyncSelect from 'react-select/async';
class DropDown extends Component {
constructor(props) {
super(props);
this.state = {
selectedValue: null,
loaded: false,
};
this.onChange = this.onChange.bind(this);
this.loadOptions = this.loadOptions.bind(this);
}
componentDidMount() {
const { value } = this.props;
this.setState({ selectedValue: value, componentMounted: true });
}
onChange(value) {
const { updateParent } = this.props;
this.setState({ selectedValue: value });
updateParent(value);
}
// this returns a promise with my data
loadOptions() {
const { getDropDownOptions } = this.props;
return getDropDownOptions();
}
render() {
const { selectedValue, loaded } = this.state;
return (
loaded && (
<AsyncSelect
defaultOptions
isClearable
inputValue={selectedValue}
onChange={event => this.onChange(event)}
loadOptions={this.loadOptions}
{...{ ...this.props }}
/>
)
);
}
}
DropDown.defaultProps = {
isClearable: true,
};
export default DropDown;
You are passing the selected option which is of shape { value : '1', lable:'apple' } as the input value to the <AsyncSelect/>, there is no need to explicitly pass the input value into the component since it will be managed from inside Select component. Just change,
<AsyncSelect
...
inputValue={selectedValue}
...
/>
to
<AsyncSelect
...
value={selectedValue}
...
/>

React Admin - Get current value in a form

I am having big troubles getting the "updated" value of a record in an edit form. I always get the initial record values, even though I have an input linked to the right record source, which should update it.
Is there an alternative way to get the values of the SimpleForm ?
I have a simple edit form :
<Edit {...props}>
<SimpleForm>
<MyEditForm {...props} />
</SimpleForm>
</Edit>
MyEditForm is as follow:
class MyEditForm extends React.Component {
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(prevProps.record.surface, this.props.record.surface); // <-- here is my problem, both values always get the initial value I had when I fetched the resource from API
}
render() {
return (
<div>
<TextInput source="surface" />
<!-- other fields -->
</div>
);
}
}
I usually do it this way to get my updated component's data from other components, but in the very case of a react-admin form, I can't get it to work.
Thanks,
Nicolas
It really depends on what you want to do with those values. If you want to hide/show/modify inputs based on the value of another input, the FormDataConsumer is the preferred method:
For example:
import { FormDataConsumer } from 'react-admin';
const OrderEdit = (props) => (
<Edit {...props}>
<SimpleForm>
<SelectInput source="country" choices={countries} />
<FormDataConsumer>
{({ formData, ...rest }) =>
<SelectInput
source="city"
choices={getCitiesFor(formData.country)}
{...rest}
/>
}
</FormDataConsumer>
</SimpleForm>
</Edit>
);
You can find more examples in the Input documentation. Take a look at the Linking Two Inputs and Hiding Inputs Based On Other Inputs.
However, if you want to use the form values in methods of your MyEditForm component, you should use the reduxForm selectors. This is safer as it will work even if we change the key where the reduxForm state is in our store.
import { connect } from 'react-redux';
import { getFormValues } from 'redux-form';
const mapStateToProps = state => ({
recordLiveValues: getFormValues('record-form')(state)
});
export default connect(mapStateToProps)(MyForm);
I found a working solution :
import { connect } from 'react-redux';
const mapStateToProps = state => ({
recordLiveValues: state.form['record-form'].values
});
export default connect(mapStateToProps)(MyForm);
When mapping the form state to my component's properties, I'm able to find my values using :
recordLiveValues.surface
If you don't want to use redux or you use other global state like me (recoil, etc.)
You can create custom-child component inside FormDataConsumer here example from me
// create FormReceiver component
const FormReceiver = ({ formData, getForm }) => {
useEffect(() => {
getForm(formData)
}, [formData])
return null
}
// inside any admin form
const AdminForm = () => {
const formState = useRef({}) // useRef for good performance not rerender
const getForm = (form) => {
formState.current = form
}
// you can access form by using `formState.current`
return (
<SimpleForm>
<FormDataConsumer>
{({ formData, ...rest }) => (
<FormReceiver formData={formData} getForm={getForm} />
)}
</FormDataConsumer>
</SimpleForm>
)
}

React Native: Conditionally Render Components

I've searched this question and found a solution that said to conditionally render based on the state as follows:
render() {
const content = this.state.isReady ? <Home/> : <Splash/>;
return (
{content}
);
}
However, I keep getting an Invariant Violation: Objects are not valid a React child (found object with keys {content}.
your make typo, you returned Object, instead use between JSX elements:
const Ready = () => <div>Ready</div>
const NotReady = () => <div>NotReady</div>
class App extends Component {
constructor() {
super();
this.state = {
isReady: false
};
}
render() {
const content=this.state.isReady ? <Ready /> : <NotReady />
return (
<div>
{content}
</div>
);
}
}
Use simple if else instead of ternary expression because sometimes ternary operators "return" whatever's inside and can't execute blocks of code.
if (this.state.isReady) {
return <Home />
} else {
return <Splash />
}