Skip to content

Instantly share code, notes, and snippets.

@jhollingworth
Last active November 14, 2019 20:24
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jhollingworth/ed66357097451fb2a58f to your computer and use it in GitHub Desktop.
Save jhollingworth/ed66357097451fb2a58f to your computer and use it in GitHub Desktop.
Translating Marty to Redux

#Translating Marty to Redux

Some people have asked how I've translated a Marty app to Redux. Once you get your head around how Redux works I found concepts in Marty translated easily. Below is side-by-side comparison of the two libraries to help you along the way.

Also if you're looking for some devtools, check out redux-devtools.

##Constants

Marty

export default Marty.createConstants([
  'ADD_USER'
])

Redux

export const ADD_USER = 'ADD_USER'

##Action creators

Marty

class UserActionCreators extends Marty.ActionCreators {
  updateEmail(userId, email) {
    this.dispatch('UPDATE_EMAIL', userId, email)
  }
}

Redux

Redux docs Flux standard actions

export function updateEmail (userId, email) {
  return {
    type: 'UPDATE_EMAIL',
    payload: { userId, email }
  }
}

##Stores

Immutable.js isn't required for this but it makes life much easier, especially with redux

Marty

class UsersStore extends Marty.Store {
  constructor(options) {
    super(options)
    this.state = Immutable.Map()
    this.handlers = {
      addUser: 'RECEIVE_USER'
    }
  }
  addUser(user) {
    this.state = this.state.set(user.get('id'), user)
  }

  getUser(id) {
    return this.state.get(id)
  }
}

Redux

Redux docs

export default function (state = new Immutable.Map(), action) {
  switch (action.type) {
    case 'RECEIVE_USER':
      const {user} = action.payload

      return state.set(user.get('id'), user)
    default:
      return state
  }
}

##Render

Marty

import {Application, ApplicationContainer} from 'marty'

class Application extends Application {
    constructor(options) {
        super(options)

        this.register('userStore', require('./stores/userStore'))
    }
}


const app = new Application()

React.render((
    <ApplicationContainer app={app}>
        <User id={123} />
    </ApplicationContainer>
), document.body)

Redux

import {Provider} from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import users from './reducers/usersReducer'
import {createStore, combineReducers, applyMiddleware} from 'redux'

const reducers = { users }
const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore)
const store = createStoreWithMiddleware(combineReducers(reducers))

React.render((
  <Provider store={store}>
    {() => <Handler />}
  </Provider>
), document.getElementById('app'))

##Containers

Marty

class User extends React.Component {
  render() {
    return <div className='User'>{this.props.user}</div>
  }

  saveUser () {
    this.app.userActions.saveUser()
  }
}

export default Marty.createContainer(User, {
  listenTo: 'userStore',
  fetch: {
    user() {
      return this.app.userStore.getUser(this.props.id)
    }
  }
})

Redux

Redux docs

let userActions = {
  saveUser () {
    return { type: 'SAVE_USER' }
  }
}

function mapState (state, props) {
  return state.users.getUser(props.id)
}

@connect(mapState, userActions)
class User extends React.Component {
  render() {
    return <div className='User'>{this.props.user}</div>
  },
  saveUser () {
    this.props.saveUser()
  }
}

##Fetching state

Marty

var UserConstants = Marty.createConstants([
  'RECEIVE_USER',
  'USER_NOT_FOUND'
])

class UserAPI extends Marty.HttpStateSource {
  getUser(userId) {
    var url = 'http://jsonplaceholder.typicode.com/users/' + userId

    return this.get(url).then(function (res) {
      if (res.ok) {
        return res
      }

      throw res
    })
  }
}

class UserQueries extends Marty.Queries {
  getUser(userId) {
    this.dispatch(UserConstants.RECEIVE_USER_STARTING, userId)

    return this.app.userAPI.getUser(userId)
      .then(res => this.dispatch(UserConstants.RECEIVE_USER, userId, res.body)_
      .catch(err => this.dispatch(UserConstants.RECEIVE_USER_FAILED, userId, err)
  }
}

class UserStore extends Marty.Store {
  constructor(options) {
    super(options)
    this.state = {}
    this.handlers = { addUser: UserConstants.RECEIVE_USER }
  }
  getUser(userId) {
    return this.fetch({
      id: userId,
      locally () {
        return this.state[userId]
      },
      remotely () {
        return this.app.userQueries.getUser(userId)
      }
    })
  }
}

class User extends React.Component {
  render() {
    return (
      <div className='user'>
        {this.props.user.name}
      </div>
    )
  }
}

module.exports = Marty.createContainer(User, {
  listenTo: ['userStore'],
  fetch: {
    user() {
      return this.app.userStore.getUser(this.props.userId)
    }
  },
  pending (fetches) {
    return this.done(_.defaults(fetches, { user: DEFAULT_USER })
  },
  failed (errors) {
    return <ErrorPage errors={errors} />
  }
})

Redux

Redux docs

export function fetchUserIfNeeded (userId) {
  return (dispatch, getState) => {
    const user = getState().users.get(userId)

    if (!user) {
      axios
        .get(`http://jsonplaceholder.typicode.com/users/${userId}`)
        .then(res => dispatch('RECEIVE_USER', res.data))
    }
  }
}

function mapState (state, props) {
  return {
    user: state.users.get(props.userId)
  }
}

@connect(mapState, {fetchUserIfNeeded})
class User extends React.Component {
  render() {
    return (
      <div className='user'>
        {this.props.user.name}
      </div>
    )
  }

  componentWillMount () {
    const {fetchUserIfNeeded, userId} = this.props

    fetchUserIfNeeded(userId)
  }
}
@bishtawi
Copy link

@jhollingworth Have you looked into how to move the server side rendering (with marty-express) to redux?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment