#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
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
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
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)
}
}
@jhollingworth Have you looked into how to move the server side rendering (with marty-express) to redux?