How to synchronize Redux and Relay?
Asked Answered
L

1

6

The situation

I have an onboarding scenario where the user goes through a step-by-step onboarding. I want to manage the client side state of the user's progress with Redux. The synchronization between the server and the client is already implemented in Relay, but I still need a Redux store for client-side state management. As such, problems arise with synchronizing the Relay-/Redux-Store.

What I'm doing right now is to wrap my React component with Redux and then with Relay:

// OnboardProgressView.js
// ...      

// wrap React component with Redux 
const mapStateToProps = (state) => {
  return {
    onboardProgress: state.onboardProgress,
  }
}

const ReduxContainer = connect(
  mapStateToProps,
)(OnboardProgressView)

// this is only for convenience of access of the Relay data
const MappedOnboardProgressView = mapProps({
  params: (props) => props.params,
  user: (props) => props.viewer.user,
})(ReduxContainer)

// wrap Redux component with Relay
export default Relay.createContainer(MappedGettingStartedView, {
  fragments: {
    viewer: () => Relay.QL`
      fragment on Viewer {
        user {
          userId
          onboardProgressStep
        }
        # more stuff ...
      }
    `,
  },
})  

My progress

I have found ways to accomplish different operations as follows:

Initialization of the Redux store with server data

I am initializing the Redux state right after creating the store with an asynchronous raw Relay query. To make that possible I am also using the redux-thunk middleware. Redux initiates a request to Relay which queries the server. Visual representation (an arrow denotes data flow, the order of elements reflects the 'call order'): Redux <= Relay <= Server

// app.js
const store = createStore(reducer, applyMiddleware(thunk))
store.dispatch(fetchOnboardProgress())

// onboardProgress.js
export function fetchOnboardProgress () {
  return function (dispatch) {
    var query = Relay.createQuery(Relay.QL`
      query {
        viewer {
          user {
            id
            onboardProgress
          }
        }
      }`, {})

    return new Promise(function (resolve, reject) {
      Relay.Store.primeCache({query}, ({done, error}) => {
        if (done) {
          const data = Relay.Store.readQuery(query)[0]
          dispatch(update(data.user.onboardProgress, data.user.id))
          resolve()
        } else if (error) {
          reject(Error('Error when fetching onboardProgress'))
        }
      })
    })
  }
}

Updating data on server when dispatching a Redux action

Redux => Relay => Server

To have consistent state changes, when the user progresses through the onboarding process, I fire a Redux action that will also asynchronously do a Relay mutation. I am also using redux-thunk for this purpose.

function nextStep () {
  return function (dispatch, getState) {
    const currentStep = getState().onboardProgress.step
    const currentStepIndex = OnboardProgress.steps.indexOf(currentStep)
    const nextStep = OnboardProgress.steps[currentStepIndex + 1]
    const userId = getState().onboardProgress._userId

    return _updateReduxAndRelay(dispatch, nextStep, userId)
  }
}

function _updateReduxAndRelay (dispatch, step, userId) {
  return new Promise((resolve, reject) => {
    Relay.Store.commitUpdate(new UpdateUserMutation({
      userId: userId,
      onboardProgressStep: step,
    }), {
      onSuccess: () => {
        dispatch(update(step, userId))
        resolve()
      },
      onFailure: reject,
    })
  })
}

export function update (step, userId) {
  const payload = {onboardProgress: new OnboardProgress({step, userId})}
  return {type: UPDATE, payload}
}

Open Problems

I still haven't find an approach to the following situation:

Updating the Redux Store when the Relay Store updates

Changes to data on the server might have external sources, that are not triggered by a user action in our app. With Relay we can solve this with forceFetching or polling. A Relay query looks like this: Relay <= Server. I'd like to additionally have this data flow: Relay => Redux when external data changes.

Another possible reason for the need to update the Redux store with new data is when we want to synchronize data that is deeply nested in the Relay store, or part of a complex query.

For example, think of the count of comments to a blog post. When a user is posting a new comment, another component showing the comment count should update as well.

If we manage this information in Redux, we need a way to trigger a Redux action when a Relay query comes with new information. I am not aware of such a callback, or another solution to this situation.

My Questions

In this context, I have those questions:

  1. What can I improve in my existing approaches? Is there something I did that is highly dangerous/leads to inconsistencies? (see My Progress)
  2. How can I manage to sync the Redux store when for some reason the Relay store is being updated. I am looking for a React component life cycle method or a Relay callback where I can then send a Redux action to the Redux store. (see Open Problems)
Lentil answered 18/7, 2016 at 17:54 Comment(2)
Did you find a better solution? I am working on similar problem.Dipstick
Check out this solution. Its is similar to your solution : gist.github.com/fdecampredon/f337605e393a5b032b85Dipstick
W
2

RelayNetworkLayer is what you should use to sync the redux store with the relay one as it allows you to subscribe to everything that happens there. I'll update this post later if anything else comes to mind.

Waltz answered 19/7, 2016 at 22:22 Comment(1)
This sounds like a possible solution. Can you give a bit more information? I think I could use the default network layer and additionally send Redux actions to the Redux store in sendQueries/sendMutation.Lentil

© 2022 - 2024 — McMap. All rights reserved.