14

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

Nicolas Kern
  • 279
  • 2
  • 3
  • 11

3 Answers3

28

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);
Gildas Garcia
  • 6,966
  • 3
  • 15
  • 29
  • Perfect answer, thank you. This is indeed the second case I currently face. Do you have any idea of how I can programmatically change a value in a function as well? – Nicolas Kern Aug 03 '18 at 13:30
  • Use redux form action creators – Gildas Garcia Aug 04 '18 at 10:27
  • Thanks for the info, although I'm having big troubles understanding how to adapt mapDispatchToProps (following this doc https://github.com/reduxjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) to React Admin. If you have a very short example, that would be perfect. Thanks a lot ;) – Nicolas Kern Aug 06 '18 at 08:06
  • 1
    This is outdated, react-admin does not use redux-form, it use react-final-form. I am trying with a `FormDataConsumer` to display twice a `DateField`, except one format the date time to a particular timezone and is `disabled`. I always get the `formData.someDate` from the previous form, I need to select twice the value to get this field updated. How can I access values within the form in order to format my field correctly each time the value change ? – Dimitri Kopriwa Mar 23 '20 at 17:37
3

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
Nicolas Kern
  • 279
  • 2
  • 3
  • 11
0

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>
   )
}