1

I have a profile page containing a redux-form ProfileForm for which I set some initialValues. In my page header, I have a react-router Link to the /profile route.

The first time the page loads, the form is initialized correctly. However, if I click on the Link element, the form empties itself. I would have expected the form to keep its values maintained by the redux-form state (or at least to be initialized to initialValues).

What am I doing wrong? Is there a workaround?

Note: I am using react 16, react-router 4 and redux-form 7. I am also using redux-thunk in my action generators when fetching data.

Code

Profile.js

The component Profile waits for the initialValues to be set before rendering the ProfileForm for the first time. It shows a Loading component until the data is fetched.

//...
import { 
    fetchData,
    submitData,
} from '../../actions';

class Profile extends Component{
    componentDidMount() {
        this.props.fetchData();
    }

    render(){
        if(!this.props.initialValues){
            return <Loading />
        }else{
            return <ProfileForm initialValues={this.props.initialValues} />
        }
    }
}


class ProfileForm extends Component{
    onSubmit(values){
        return this.props.submitData(values);
    }

    render(){
        const { handleSubmit } = this.props;
        return (
            <div>
                <Form 
                    onSubmit={handleSubmit(this.onSubmit.bind(this))}
                    className="container">
                    <Field
                        name="first_name"
                        type="text" 
                        title="First name" 
                        component={SingleInput} />

                    ...

                    <Button type="submit" 
                        Sumbit
                    </Button>
                </Form>
            </div>
        )
    }
}

// validate, warn, etc.
// ...

function mapStateToProps(state) {
    return { 
        initialValues: state.profile.data // set by the profile reducer upon fetching the data
    };
}

export default connect(mapStateToProps,{ fetchData })(Profile);

ProfileForm = reduxForm({
    form: 'ProfileForm',
    fields: ['first_name', ...],
    enableReinitialize: true,
    validate,
    warn,
})(
    connect(mapStateToProps, { submitData })(ProfileForm)
);

App.js

//src/components/App.js
render() {
    return (
        <div className="App">
            <Header />
            <Main />
        </div>
    );
}

Header.js

The Header component contains, among other things, a Link component pointing to /profile.

//src/components/header.js
render(){
    return (
        ...
        <Link className="nav-link" to="/profile">Profile</Link>
        ...
    )
}

Main.js

I have a profile page that is accessible thanks to React-Router v4 under /profile

//src/components/main.js
render(){
    return (
        <Switch>
            <Route path='/profile' component={Profile}/>
            ...
        </Switch>
    )
}

EDIT: actions generator and reducers

I am using axios to fetch and submit data and redux-thunk to dispatch callbacks once I receive the data.

Action generators

//src/actions/index.js
export function fetchData(){
    return (dispatch) => {
        axios.get(`${FETCH_URL}`)
            .then(response => {
                dispatch({
                    type: FETCH_DATA_SUCCESS,
                    payload: response.data
                });
            })
            .catch(error => {
                dispatch({
                    type: FETCH_DATA_FAILED,
                    payload: error
                })
            })
    }
}

export function submitData(values){
    return (dispatch) => {
        return axios.post(`${SUBMIT_URL}`,values)
                    .then(response => {
                        dispatch({ 
                            type: SUBMIT_DATA_SUCCESS,
                            payload: values,
                        });
                    })
                    .catch(error => {
                        dispatch({
                            type: SUBMIT_DATA_FAILED,
                            payload: error
                        });
                    })
    };
}

Reducers

//src/reducers/profile.js
export default function(state={}, action) {
    switch(action.type) {
        case FETCH_DATA_SUCCESS:
            return { ...state, profile: action.payload };
        case FETCH_DATA_FAILED:
            // Note that I never reach this
            return { ...state, profile: {} };
        case SUBMIT_DATA_SUCCESS:
            return { ...state, profile: action.payload };
        case SUBMIT_DATA_FAILED:
            return { ...state };
    }
    return state;
}
nbeuchat
  • 6,575
  • 5
  • 36
  • 50
  • have you tried wrapping your component in the withRouter HOC? – Sujit.Warrier Jan 27 '18 at 14:43
  • @Sujit.Warrier I just tried to add the withRouter HOC to my component but I still have the same issue. – nbeuchat Jan 27 '18 at 14:54
  • What does `BasicInformationForm` do? You only pass `initialValues` to the form. Also what is `this.props.submitData`? There doesn't seem to be an action-generator with that name, also where are you dispatching your action? Are you using redux-thunks and dispatch in the callback? – Moritz Roessler Jan 27 '18 at 15:04
  • @C5H8NNaO4 I had a couple of typos when preparing the question. `BasicInformationForm` is `ProfileForm`. `submitData` is an action generator which dispatch an action. I am indeed using `redux-thunk`. I am updating the question with more details on the actions and reducers. – nbeuchat Jan 27 '18 at 15:18
  • Are you sure `response.data` contains valid json? Don't you have to call `json()` on the promises result? `response.json ().then ((json) => dispatch ({payload: json}))` – Moritz Roessler Jan 28 '18 at 13:03
  • Also, are you using a `BrowserRouter` or a `HashRouter`? If you're using the former, you would need a backend server which can handle dynamic requests. – Moritz Roessler Jan 28 '18 at 13:05
  • @C5H8NNaO4: yes, the response is a valid json. The whole data loading part works fine. I have just checked in the `render` function of my form and I always have the correct `initialValues`. It just seems that the redux-form is not initialised when clicking a second time on the `Link` pointing to `/profile`. – nbeuchat Jan 29 '18 at 13:21
  • @C5H8NNaO4: regarding the router, I am using `BrowserRouter`. It works fine to navigate to other pages, the only issue arise when trying to go to the same page again. For now, I am using a dirty workaround to disable the `Link` when already on that path (see my answer here: https://stackoverflow.com/questions/35963070/react-router-how-to-disable-a-link-if-its-active/48482607#48482607). But I'm not very happy with fixing the symptoms. – nbeuchat Jan 29 '18 at 13:24
  • @nbeuchat If that issue arises when you're loading a new page. It might be because you're using a `BrowserRouter` together with a static server. You need to use a `HashRouter`. See my answer [here](https://stackoverflow.com/a/48477829/1487756) – Moritz Roessler Jan 29 '18 at 14:56
  • @nbeuchat It sounds odd though. Disabling the link does not seem to be the right way. – Moritz Roessler Jan 29 '18 at 14:59
  • @C5H8NNaO4 Thanks a lot, I will try to switch to `HashRouter` tonight. However, I am using AWS S3 and using `BrowserRouter` should work according to https://medium.com/@ervib/how-to-set-up-react-router-and-deploy-to-amazon-s3-d3dffa6ae43 . If I load directly a page, it works perfectly (even after clearing the cache) except for this question's issue. – nbeuchat Jan 29 '18 at 18:35
  • @nbeuchat The first comment regards the `HashRouter`. The article talks about a static server, and uses webpack. afaic this doesn't work with a `BrowserRouter`. I'm a bit puzzled because usually loading a page directly shouldn't be working either. – Moritz Roessler Jan 29 '18 at 19:19

1 Answers1

1

Your problem is that the form is unmounted/mounted for whatever reason, I glanced your code and my first thought is the Loading component. When it renders, the Form is not. What you can do is on the following: Hide the form instead of removing it (pass a isVisible or something to the form) or let redux-form keep the state for you when the form is unmounted. You can do this by setting the prop destroyOnUnmount=false to the reduxForm hoc.

Dennie de Lange
  • 2,776
  • 2
  • 18
  • 31
  • Setting the `destroyOnUnmount` to `false` did the trick, thanks! Now I do understand the rational behind it as well. – nbeuchat Feb 04 '18 at 12:23