2

I am having server render on a demo react application of mine. Although it works if i refresh the page on a url to fetch the doctor like /doctor/:id if i am at /login and try to go to /doctor/123456 the doctor property is empty and (this.props.doctor.name.first) fails.

What is a good approach to use redux to fetch data on these cases?

Code is below

import { fetchDoctor } from '../../DoctorActions';
import { getDoctor } from '../../DoctorReducer';

class DoctorDetailPage extends Component {
    render() {
        return (
            <div>{this.props.doctor.name.first}</div>
        );
    }
}

DoctorDetailPage.need = [params => {
    return this.props.dispatch(fetchDoctor(params.id));
}];

function mapStateToProps(state, props) {
    return {
        doctor: getDoctor(state, props.params.id),
    };
}
DoctorDetailPage.propTypes = {
    doctor: PropTypes.shape({
        insurance: PropTypes.string,
        description: PropTypes.string,
        GUID: PropTypes.string,
        name: PropTypes.shape({
            first: PropTypes.string,
            last: PropTypes.string,
        })
    }),
    dispatch: PropTypes.func.isRequired,
};

export default connect(mapStateToProps)(DoctorDetailPage);

REDUCER

import { ADD_DOCTOR } from './DoctorActions';

// Initial State
const initialState = { list: [] };

const DoctorReducer = (state = initialState, action = {}) => {

    switch (action.type) {
        case ADD_DOCTOR:
            return {
                list: [action.doctor, ...state.list],
            };

        default:
            return state;
    }
};

export const getDoctor = (state, id) => {
  return state.doctors.list.filter(doctor => doctor._id === id)[0];
};

export default DoctorReducer;

ACTIONS

import callApi from '../../util/apiCaller';

// Export Constants
export const ADD_DOCTOR = 'ADD_DOCTOR';

// Export Actions
export function addDoctor(doctor) {
    return {
        type: ADD_DOCTOR,
        doctor,
    };
}

export function addDoctorRequest() {
    return () => {
        return true;
    };
}

export function fetchDoctor(id) {
    return (dispatch) => {
        return callApi(`doctors/${id}`)
            .then(res => dispatch(addDoctor(res)));
    };
}

LOG ERROR

TypeError: Cannot read property 'name' of undefined
Panagiotis Vrs
  • 824
  • 6
  • 16
  • What's the error that is shown in the console? – kbariotis Feb 28 '17 at 23:15
  • "TypeError: Cannot read property 'name' of undefined" because doctor is undefined. Fetch on client side fails although server side is working – Panagiotis Vrs Mar 01 '17 at 07:35
  • Can you switch to the Network tab and tell us if the API call is being made correctly on the client side? – kbariotis Mar 01 '17 at 08:08
  • no is not. It's is like is trying to render before resolving that object. Also the only way around it is to have a componentDidMount function and setState of doctor to empty. This way can show it empty and then replace it with the fetched data. But this is not the redux way to do it – Panagiotis Vrs Mar 01 '17 at 08:52

1 Answers1

1

What is a good approach to fetch data in general?

A user friendly approach would be to enter the page /doctor/123456 wihtout the need of having the doctor available, so user get's instant feedback that his action (navigate me to page x) worked. In onEnter method of react-router or in componentDidMount you should start an action fetchDoctor and in a meanwhile show the user a spinner or a message indicating that the data is being loaded.

render() {
    return (
        <div>
          { this.props.doctor && <div>{this.props.doctor.name.first}</div> }
          { ! this.props.doctor && <YourSpinnerComponent/> }
        </div>
    );
}

So the above render method shows something while data is being loaded and when data comes in it displays it without any errors.

What is a good approach to fetch data using redux?

The "good old" way to handle async operations is to use redux-thunk. You can read this great SO answer about dispatching asynchronous redux actions.

The latest trend is to use redux-saga. It is a library that aims to make side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) in React/Redux applications easier and better. More about redux-saga.

So in your case, you would create a Saga to handle the fetching.

More about redux-thunk vs redux-saga in this great SO answer.

Community
  • 1
  • 1
Deividas
  • 6,437
  • 2
  • 26
  • 27
  • 1
    Thanks for your answer that worked fine. Just to let other users know that the above example would work if you put the return () in a div like .. return (

    Hello!

    { this.props.doctor &&
    {this.props.doctor.name.first}
    }
    );
    – Panagiotis Vrs Mar 05 '17 at 20:06
  • 1
    @PanagiotisVrs thanks for the note. I have corrected the answer too. – Deividas Mar 06 '17 at 06:02