Search code examples
javascriptreactjsreduxredux-orm

Use reducer method on redux-orm schema instance


I am using redux-orm to create models for normalization and denormalization. I find that when I create a schema, I get the error:

Uncaught Error: Schema has been renamed to ORM. Please import ORM instead of Schema from Redux-ORM

I have used both the 0.90-rc1 which is the version installed by default as well as `0.8.4

when running this code:

import { Schema } from 'redux-orm'
import Todo from './Todo'
import Tag from './Tag'
import User from './User'

const schema = new Schema()
schema.register(Todo, Tag, User)

export default schema

However I find that the documentation and code for schemas within redux-orm is still in existance.

If I switch from

import { Schema } from 'redux-orm'

to

import { ORM as Schema } from 'redux-orm'

the code works but I get an error specifying that reducer method is not defined here:

import { schema } from './models' 

console.log(schema)

const rootReducer = combineReducers({
  orm: schema.reducer(),
  selectedUserId: selectedUserIdReducer
})

Most of the code is based of the primer here

My Models look like this:

ValidatingModel.js

import { PropTypes } from 'react'
import { Model } from 'redux-orm'
import propTypesMixin from 'redux-orm-proptypes'

const ValidatingModel = propTypesMixin(Model)

export default ValidatingModel

Todo.js

import { fk, many } from 'redux-orm'
import { PropTypes } from 'react'
import ValidatingModel from './ValidatingModel'
import User from './User'
import Tag from './Tag'
import { CREATE_TODO, MARK_DONE, DELETE_TODO, ADD_TAG_TO_TODO, REMOVE_TAG_FROM_TODO } from '../actionTypes'
export default class Todo extends ValidatingModel {
  static reducer (state, action, Todo, session) {
    const { payload, type } = action
    switch (type) {
      case CREATE_TODO:
        const tagIds = payload.tags.split(',').map(str => str.trim())
        const props = Object.assign({}, payload, { tags: tagIds })
        Todo.create(props)
        break
      case MARK_DONE:
        Todo.withId(payload).set({ done: true })
        break
      case DELETE_TODO:
        Todo.withId(payload).delete()
        break
      case ADD_TAG_TO_TODO:
        Todo.withId(payload.todo).tags.add(payload.tag)
        break
      case REMOVE_TAG_FROM_TODO:
        Todo.withId(payload.todo).tags.remove(payload.tag)
        break
    }
  }
}

Todo.modelName = 'Todo'

Todo.propTypes = {
  id: PropTypes.number,
  text: PropTypes.string.isRequired,
  done: PropTypes.bool.isRequired,
  user: PropTypes.oneOf([PropTypes.instanceOf(User), PropTypes.number]),
  tags: PropTypes.arrayOf(PropTypes.oneOf([
    PropTypes.number,
    PropTypes.instanceOf(Tag)
  ]))
}

Todo.defaultProps = {
  done: false
}

Todo.fields = {
  user: fk('User', 'todos'),
  tags: many('Tag', 'todos')
}

Tag.js

import ValidatingModel from './ValidatingModel'
import { PropTypes } from 'react'
import { CREATE_TODO, ADD_TAG_TO_TODO } from '../actionTypes'

export default class Tag extends ValidatingModel {
  static reducer (state, action, Tag, session) {
    const { payload, type } = action
    switch (type) {
      case CREATE_TODO:
        const tags = payload.tags.split(',')
        const trimmed = tags.map(name => name.trim())
        trimmed.forEach(name => Tag.create(name))
        break
      case ADD_TAG_TO_TODO:
        if (!Tag.filter({ name: payload.tag }).exists()) {
          Tag.create({ name: payload.tag })
        }
        break
    }
  }
}

Tag.modelName = 'Tag'

Tag.backend = {
  idAttribute: 'name'
}

Tag.propTypes = {
  name: PropTypes.string
}

This is the User model, I have added a no-op reducer after seeing @squiroid original answer

import ValidatingModel from './ValidatingModel'
import { PropTypes } from 'react'

export default class User extends ValidatingModel {
  static reducer (state, action, User, session) {
    return state
  }
}

User.modelName = 'User'

User.propTypes = {
  id: PropTypes.number,
  name: PropTypes.string
}

Solution

  • First, I needed to use ORM instead of the Schema. So I changed the import for Schema to:

    import { ORM as Schema } from 'redux-orm'
    

    Secondly, I had to change the signature of the reducer to:

    static reducer (action, SessionSpecificModel, session)
    

    the redux-orm docs have been updated, the second argument is not the state of the reducer but a session specific model.

    from the signature in the documentation:

    static reducer (state, action, Model, session)
    

    Thirdly, I had to change the code in the todo to use toRefArray or toModelArray in order to call map on a list of Todo and Tag instances:

    return orm.Todo.filter({ user: userId }).toModelArray().map(todo => {
      const obj = Object.assign({}, todo.ref)
      obj.tags = todo.tags.toRefArray().map(tag => tag.name)
      return obj
    })
    

    Fourth, I had to resolve the Model class from session instance.

    I am still finding issues with creating a Todo where:

    session.Todo.create(props)
    

    throws the error:

    Invalid prop tags[0] supplied to Todo.create.

    when called with the props:

    {
     "text":"Test my data",
     "tags":[
       "urgent",
       "personal"
      ],
      "user":0
    }
    

    The validations seem to be interfering with creating the model. When creating the tags, specify the PropTypes to be:

    const { string, number, arrayOf oneOfType, instanceOf } = PropTypes
    Todo.propTypes = {
       text: string,
       user: oneOfType([
          number,
          instanceOf(User)
       ]),
       tags: oneOfType([
          arrayOf(string),
          arrayOf(
            instanceOf(Tag)
          )
       ])
    }
    

    In redux-orm when bootstrapping or creating models, you can either provide the id or the instance of the model to the related property. Thus, it would be nessecary to ensure that both are accepted by the Model.