1
votes

I'm using Apollo Client with React, I've outlined my Query/Mutation usage in the MWE below.

I have a Query which fetches a user's appointments:

const GET_USER_APPOINTMENTS = gql`
  query getUserAppointments {
    getUserAppointments {
      id
      appointmentStart
      appointmentEnd
      appointmentType
      status
    }
  }
`
// omitted code for brevity...

<Query query={GET_USER_APPOINTMENTS} fetchPolicy='network-only'>
  {({ loading, error, data }) => {
    if (loading) return <div>Loading...</div>
    if (error) return `Error ${error}`

    const appointments = data.getUserAppointments

    return <BookingsBlock appointments={appointments} />
  }}
</Query>

The BookingsBlock is represented by this MWE:

export default class BookingsBlock extends Component {
  constructor (props) {
    super(props)
    let pastAppointments = []
    let futureAppointments = []
    if (props.appointments) {
      // assign past and future appointments
      props.appointments.forEach(appt => {
        if (moment(appt.appointmentStart) < moment()) {
          pastAppointments.push(appt)
        } else {
          futureAppointments.push(appt)
        }
      })
    }
    this.state = { pastAppointments, futureAppointments }
  }

  render () {
    let pastAppointmentsBlock
    let futureAppointmentsBlock

    const EmptyBlock = (
      <EmptyBookingBlock>
        <h3>You have no appointments!</h3>
      </EmptyBookingBlock>
    )

    if (this.state.pastAppointments.length) {
      pastAppointmentsBlock = (
        <BookingBlock>
          {this.state.pastAppointments.map(appt => {
            return <Appointment key={appt.id} appointmentData={appt} />
          })}
        </BookingBlock>
      )
    } else {
      pastAppointmentsBlock = EmptyBlock
    }

    if (this.state.futureAppointments.length) {
      futureAppointmentsBlock = (
        <BookingBlock>
          {this.state.futureAppointments.map(appt => {
            return <Appointment key={appt.id} appointmentData={appt} />
          })}
        </BookingBlock>
      )
    } else {
      futureAppointmentsBlock = EmptyBlock
    }

    return (
      <div>
        <h2>Your bookings</h2>
        {futureAppointmentsBlock}
        {pastAppointmentsBlock}
      </div>
    )
  }
}

From the above, BookingBlock and EmptyBookingBlock are simply styled components without any logic.

The Appointment component is as in this following MWE:

const Appointment = props => {
  const { id, appointmentStart, appointmentEnd, status } = props.appointmentData
  return (
    <AppointmentBlock>
        <p>
          <Description>Start: </Description>
          <Value> {moment(appointmentStart).format('HH:mm')} </Value>
        </p>
        <p>
          <Description>End: </Description>
          <Value> {moment(appointmentEnd).format('HH:mm')} </Value>
        </p>
        <p>
          <Description>Status: </Description>
          <Value>
            {status === 'Confirmed' ? (
              <PositiveValue>Confirmed</PositiveValue>
            ) : (
              <NegativeValue>{status}</NegativeValue>
            )}
          </Value>
        </p>
        <CancelAppointment
          id={id}
          appointmentStart={appointmentStart}
          status={status}
        />
      </div>
    </AppointmentBlock>
  )
}

Again, AppointmentBlock, Description and Value are simply styled components without logic. CancelAppointment is a component represented by the following MWE, which cancels the appointment via a Mutation:

const CANCEL_APPOINTMENT = gql`
  mutation cancelAppointment($id: Int!) {
    cancelAppointment(id: $id) {
      id
      appointmentStart
      appointmentEnd
      appointmentType
      status
    }
  }
`

// code omitted for brevity...

class CancelAppointment extends Component {
  constructor (props) {
    super(props)
    const hoursUntilAppt = moment(this.props.appointmentStart).diff(
      moment(),
      'hours'
    )
    const cancellable =
      this.props.appointmentType === 'A'
        ? hoursUntilAppt > 72
        : hoursUntilAppt > 48

    this.state = {
      cancelled: this.props.status === 'Cancelled',
      cancellable,
      firstClick: false
    }
  }

  cancelAppointment = async (e, cancelAppointment) => {
    e.preventDefault()
    await cancelAppointment({
      variables: { id: this.props.id }
    })
  }

  render () {
    if (!this.state.cancelled) {
      if (!this.state.cancellable) {
        return (
          <CancelAppointmentButtonInactive>
            Cancellation period elapsed
          </CancelAppointmentButtonInactive>
        )
      }
      if (!this.state.firstClick) {
        return (
          <CancelAppointmentButtonActive
            onClick={() => {
              this.setState({ firstClick: true })
            }}
          >
            Cancel appointment
          </CancelAppointmentButtonActive>
        )
      } else if (this.state.firstClick) {
        return (
          <Mutation mutation={CANCEL_APPOINTMENT}>
            {cancelAppointment => {
              return (
                <CancelAppointmentButtonActive
                  onClick={e => this.cancelAppointment(e, cancelAppointment)}
                >
                  Are you sure?
                </CancelAppointmentButtonActive>
              )
            }}
          </Mutation>
        )
      }
    } else {
      return (
        <CancelAppointmentButtonInactive>
          Appointment cancelled
        </CancelAppointmentButtonInactive>
      )
    }
  }
}

Once more CancelAppointmentButtonInactive and CancelAppointmentButtonActive are button styled components.

The mutation functions as expected and cancels the appointment on the database. After the mutation function is called the Apollo cache is updated in browser memory to reflect the appointment being cancelled. I have tested this using the Apollo dev tools.

However this change in appointment status is not reflected in the UI, in particular the status displayed in AppointmentAppointmentBlock does not update to cancelled. The CancelAppointment button also does not receive an update to its this.props.status as one would expected when the appointment is updated in the cache via the completed mutation.

I initially thought this may be down to query/mutation object return object differences, but even after unifying what fields are returned the UI does not update.

The data flow of the appointment data is Query 'GET_USER_APPOINTMENTS'BookingBlockAppointmentCancelAppointment.

2

2 Answers

1
votes

Your BookingsBlock component is copying first props into own state, therefore change in the props, caused by the mutation, is not affecting the rendered state. Simply getting rid of the state in BookingsBlock will help - it's not needed in there anyway, as you can easily calculate both pastAppointments and futureAppointments in the render method.

0
votes

First you have to change the fetch policy in GET_USER_APPOINTMENTS. cache-first is default . network-only always fetches form server it does not look in cache first.

Second .. after mutation you have to update the cache.

check this link from off [https://www.apollographql.com/docs/react/essentials/mutations.html#update][1]

hope this will help. stay blessed :)