{"id":18799612,"url":"https://github.com/l2silver/erschema-redux-immutable-tutorial","last_synced_at":"2025-07-29T12:12:06.775Z","repository":{"id":97819256,"uuid":"110923578","full_name":"l2silver/erschema-redux-immutable-tutorial","owner":"l2silver","description":null,"archived":false,"fork":false,"pushed_at":"2017-11-16T04:35:54.000Z","size":63,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-21T19:53:43.133Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/l2silver.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-16T04:35:03.000Z","updated_at":"2017-12-10T15:17:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"edcc8455-da99-4c46-98bd-2cc9585a288e","html_url":"https://github.com/l2silver/erschema-redux-immutable-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/l2silver/erschema-redux-immutable-tutorial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/l2silver%2Ferschema-redux-immutable-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/l2silver%2Ferschema-redux-immutable-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/l2silver%2Ferschema-redux-immutable-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/l2silver%2Ferschema-redux-immutable-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/l2silver","download_url":"https://codeload.github.com/l2silver/erschema-redux-immutable-tutorial/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/l2silver%2Ferschema-redux-immutable-tutorial/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267685021,"owners_count":24127704,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-29T02:00:12.549Z","response_time":2574,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T22:15:54.236Z","updated_at":"2025-07-29T12:12:06.748Z","avatar_url":"https://github.com/l2silver.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# erschema-redux-immutable Tutorial\n\nThis tutorial is a transformation tutorial, where we turn the well known react-redux project frontend-boilerplate by TJH into the exact same functioning app, but using [erschema-redux-immutable](https://github.com/l2silver/erschema-redux-immutable). The final completed tutorial is available on the completed branch\n\n``` git checkout completed```\n\n## Install erschema-redux-immutable\n\n```npm i -S erschema-redux-immutable```\n\n## Add erschemaReducer to store\n\n```\n// ./client/reducers/index.js\n+ import erschemaReducer from 'erschema-redux-immutable'\n\nexport default combineReducers({\n  routing,\n  todos,\n+  erschema: erschemaReducer({\n+    schema: {},\n+    pageSchema: {},\n+  }),\n})\n```\n\nRun app, checkout store using chrome extension or console log it. You should now see the erschema property in the store. The schema and page schema are empty objects to begin with, but we'll be replacing those with the respective schemas shortly. For more information about schemas please checkout https://github.com/l2silver/erschema\n\n## Add model\n\nWe're going to add two models to the project. The todo model, and the page home model\n\n```\n// ./client/models/ToDo/index.js\n\nimport { Record } from 'immutable';\n\nexport const properties = {\n  id: '',\n  text: '',\n  completed: false,\n}\n\nexport default class ToDo extends Record(properties) {}\n```\nWe use models to format the date in the entities section of the erschema portion of the state. The todo entities need id, text, and completed\n\n```\n// ./client/models/pages/Home/index.js\nimport { Record } from 'immutable';\n\nexport const properties = {\n}\n\nexport default class Home extends Record(properties) {}\n```\nWe also need to create a model for the home entity in the pages part of the entities section of the erschema portion in the store. Although we don't technically need this model for this application because we aren't declaring any properties, it's just to demonstrate that if there were properties this is how we would add them.\n\n## Add Schemas\n\nWe're going to create our [erschema schemas](https://github.com/l2silver/erschema) now. \n\n```\n// ./client/schemas/todo.js\n\nimport Model, {properties} from '../models/ToDo'\n\nexport default {\n  properties,\n  Model,\n}\n```\n```\n// ./client/schemas/index.js\n\nimport todos from './todo'\n\nexport default {\n  todos,\n}\n```\n\nWe're also going to create the page schema\n\n```\n// ./client/schemas/page/home.js\nimport {relationshipTypes} from 'erschema'\nimport Model, {properties} from '../../models/pages/Home'\n\nexport default {\n  properties,\n  Model,\n  relationships: [\n    {\n      entityName: 'todos',\n      type: relationshipTypes.MANY,\n    }\n  ]\n}\n```\n```\n// ./client/schemas/page/index.js\nimport home from './home'\n\nexport default {\n  home,\n}\n```\n\n## import schema into reducer\n\nWe're going to use these schemas to populate the entities and relationships section of the erschema portion of the store\n\n```\n// reducers/index.js\n...\n+ import schema from '../schemas'\n+ import pageSchema from '../schemas/page'\n\nexport default combineReducers({\n  routing,\n  todos,\n  erschema: erschemaReducer({\n+    schema,\n+    pageSchema,\n  }),\n})\n```\ncheckout how entities and relations now include more information about the home page and todos\n\n## Selectors\n\nSelectors are how we retrieve data from the store. They are functions that generally follow this structure:\n\n```(state, props)=\u003estate.erschema...```\n\nWe're going to add selectors for the page home and for the todo entities. We're also going to use the erschema-selectors library, although one can create selectors very easily from scratch that work as well.\n\n```\n// ./client/selectors/todos/index.js\n\nimport Selector from 'erschema-selectors'\nimport ToDo from '../../models/ToDo'\n\nexport default new Selector('erschema', 'todos', new ToDo())\n```\n```\n// ./client/selectors/pages/home/index.js\nimport Home from '../../../models/pages/Home'\nimport {PageSelector} from 'erschema-selectors'\n\nexport default new PageSelector('erschema', 'home', new Home())\n```\n## Use selectors in mapStateToProps function\n\nWe're going to switch out the simple mapStateToProps function with one that use the createStructuredSelector from the [reselect](https://github.com/reactjs/reselect) library. \n\n```\n// ./client/containers/App/index.js\n+ import { createSelector, createStructuredSelector } from 'reselect'\n+ import homeSelectors from '../../selectors/pages/home'\n+ import todoSelectors from '../../selectors/todos'\n\n...\nconst mapStateToProps = createStructuredSelector({\n  todos: createSelector([\n    todoSelectors.get(homeSelectors.getRelatedIds('todos')),\n  ], (listOfTodos)=\u003elistOfTodos.toArray()),\n})\n```\n\nThe erschema-selectors will return an immutable List by default, but since this app is already built for an array of todos, we'll do the same. For most apps, it's best to use the List as deep down as possible in the application.\n\n## Actions\n\nActions are used to dispatch changes to the store. We're going to use the library [erschema-actions](https://github.com/l2silver/erschema-actions) which provides us with a set of actions that add basic RESTFUL functionality\n\n\n```\n// ./client/actions/todos/index.js\n\nimport Actions from 'erschema-actions'\nimport schema from '../schemas'\n\nclass ToDoActions extends Actions {\n  constructor(){\n    super(schema, 'todos')\n  }\n}\n\nexport default new ToDoActions()\n```\n```\n// ./client/actions/pages/home/index.js\n\nimport { PageActions } from 'erschema-actions'\nimport schema from '../../../schemas'\nimport pageSchema from '../../../schemas/page'\n\nclass HomeActions extends PageActions {\n  constructor(){\n    super(schema, pageSchema, 'home')\n  }\n}\n\nexport default new HomeActions()\n```\n\n## Create get todos action\n\nThe original app starts with one todo, but unlike the original, we can't just create the store with an initial state that already includes the one todo. Instead we'll use a get action to pass an array of todos into the store.\n\n```\n// ./client/actions/pages/home/index.js\n\nclass HomeActions extends PageActions {\n  ...\n  getTodos = (todos) =\u003e dispatch =\u003e {\n    dispatch(this.entities.getRelated('todos', todos))\n  }\n}\n```\n\nWe'll need to add this action to the mapDispatchToProps function in App container\n\n```\n// ./client/containers/App/index.js\n+ import homeActions from '../../actions/pages/home'\n\n...\n\nfunction mapDispatchToProps(dispatch) {\n  return {\n    actions: \n      bindActionCreators({\n        getTodos: homeActions.getTodos,\n      }, dispatch),\n    }\n  }\n}\n```\n\nWe'll also need to call this action on componentWillMount\n\n```\n// ./client/containers/App/index.js\n...\nclass App extends Component {\n  componentWillMount(){\n    this.props.actions.getTodos([{\n      id: 1,\n      text: 'Use Redux',\n    }])\n  }\n  ...\n```\n\n## Add higher order reducers\n\nWhen you start the app, you should see the GET_TODOS action being called, but no change in state. This is because we have not setup all the higher order reducers that are required to properly run erschema-redux-immutable.\n\nWe are using:\n * [redux-batched-actions](https://github.com/tshelburne/redux-batched-actions)\n * [redux-retype-actions](https://github.com/l2silver/redux-retype-actions)\n * [redux-compose-hors](https://github.com/l2silver/redux-compose-hors)\n * [redux-thunk](https://github.com/gaearon/redux-thunk)\n\n```\n// ./client/store/index.js\n\n+ import thunk from 'redux-thunk'\n+ import { enableBatching } from 'redux-batched-actions';\n+ import { enableRetyping } from 'redux-retype-actions';\n+ import composeHors from 'redux-compose-hors';\n\n...\n\nconst createStoreWithMiddleware = applyMiddleware(\n   logger,\n+  thunk,\n)(create)\n\n+ const store = createStoreWithMiddleware(composeHors(rootReducer, enableBatching, enableRetyping), initialState)\n...\n```\n* redux-thunk isn't actually required for immutable, but it's good practice to use for more complex apps\n\nNow when you start the app, you should see the beginning todo\n\n## Add create action\n\n```\n// ./client/actions/todos.js\n\n...\n\n+ let nextId = 1;\n\n+ function getId(){\n+  return ++nextId;\n+ }\n\nclass ToDoActions extends Actions {\n  ...\n  create = (text)=\u003edispatch=\u003e{\n    dispatch(\n      this.entities.createRelatedPage({\n        id: getId(),\n        text,\n      }, 'home', 'todos')\n    )\n  }\n```\n\nSince we're using erschema-actions, all of the entity actions are stored in this.actions . We're using the createRelatedPage action because we are creating a todo, and then linking that todo to the home page. The createRelatedPage is really just two actions batched together, the this.entities.create action which creates the entity, and the this.relationships.link action which adds the entity's id to the designated relationship (in this case the relationship is named 'todos').\n\nWe also need to add the action to the mapDispatchToProps function\n\n```\n// ./client/containers/App/index.js\n+ import todoActions from '../../actions/todos'\n\n...\n\nfunction mapDispatchToProps(dispatch) {\n  return {\n    actions: \n      bindActionCreators({\n        getTodos: homeActions.getTodos,\n        addTodo: todoActions.create,\n        deleteTodo: todoActions.entities.remove,\n      }, dispatch),\n    }\n  }\n}\n```\nWe also added the deleteTodo action simply using the base remove action provided through entities.\n\n## Add complete\n\nWe're going to add the ability to toggle the todo either completed or incomplete. First we create the action:\n\n```\n// ./client/actions/todos.js\n\n...\nclass ToDoActions extends Actions {\n  ...\n  complete = (todo)=\u003edispatch=\u003e{\n    dispatch(\n      this.entities.update({\n        id: todo.id,\n        completed: !todo.completed,\n      })\n    )\n  }\n```\nAdd action to mapDispatchToProps\n```\n// ./client/containers/App/index.js\n\n...\n\nfunction mapDispatchToProps(dispatch) {\n  return {\n    actions: \n      bindActionCreators({\n        getTodos: homeActions.getTodos,\n        addTodo: todoActions.create,\n        deleteTodo: todoActions.entities.remove,\n        completeTodo: todoActions.complete,\n      }, dispatch),\n    }\n  }\n}\n```\n\nSince the original complete action only uses the id, we need to make one more change\n\n```\n// ./client/components/TodoItem/index.js\n\n...\n\nrender() {\n    const {todo, incompleteTodo, completeTodo, deleteTodo} = this.props\n\n    let element\n    if (this.state.editing) {\n      element = (\n        \u003cTodoTextInput text={todo.text}\n           editing={this.state.editing}\n           onSave={(text) =\u003e this.handleSave(todo.id, text)} /\u003e\n      )\n    } else {\n      element = (\n        \u003cdiv className={style.view}\u003e\n          \u003cinput className={style.toggle}\n             type=\"checkbox\"\n             checked={todo.completed}\n-            onChange={() =\u003e completeTodo(todo.id)} /\u003e\n+            onChange={() =\u003e completeTodo(todo)} /\u003e\n\n          \u003clabel onDoubleClick={::this.handleDoubleClick}\u003e\n            {todo.text}\n          \u003c/label\u003e\n\n          \u003cbutton className={style.destroy} onClick={() =\u003e deleteTodo(todo.id)} /\u003e\n        \u003c/div\u003e\n      )\n    }\n```\n\n## Add complete all\n\nThe original app allowed you to mark all todos as complete with a single click. We'll add the action.\n\n```\n// ./client/actions/todos.js\n\n+ import { batchActions } from 'redux-batched-actions'\n...\n\nclass ToDoActions extends Actions {\n  ...\n  completeAll = (todos, allCompleted)=\u003edispatch=\u003e{\n    dispatch(\n      batchActions(\n        todos.map(t=\u003ethis.entities.update({\n          id: t.id,\n          completed: !allCompleted,\n        }))\n    ))\n  }\n```\n\nHere we're using the batchedActions action to group several update actions together so that react only tries to rerender once. We add the action to mapDispatchToProps\n\nAdd action to mapDispatchToProps\n```\n// ./client/containers/App/index.js\n\n...\n\nfunction mapDispatchToProps(dispatch) {\n  return {\n    actions: \n      bindActionCreators({\n        getTodos: homeActions.getTodos,\n        addTodo: todoActions.create,\n        deleteTodo: todoActions.entities.remove,\n        completeTodo: todoActions.complete,\n        completeAll: todoActions.completeAll,\n      }, dispatch),\n    }\n  }\n}\n```\n\nAnd since the original function did not take an argument, we need to modify where this action is being to called to take the array of todos\n\n```\n// ./client/components/MainSection/index.js\n\n  ...\n  renderToggleAll(completedCount) {\n    const { todos, actions } = this.props\n    const allCompleted = completedCount === todos.length\n    if (todos.length \u003e 0) {\n      return \u003cinput\n        className={style.toggleAll}\n        type=\"checkbox\"\n        checked={allCompleted}\n        onChange={()=\u003eactions.completeAll(todos, allCompleted)} /\u003e\n    }\n  }\n```\n\n## Add clear completed\n\nThis is the final test. See if you can complete this step on your own. Check the completed branch for the final solution (doesn't have to exactly match, only the functionality matters)\n\nThat concludes the tutorial!\n\nOriginal Readme\n\n__I don't use this anymore, it's unsupported, use https://github.com/mozilla-neutrino/neutrino-dev__.\n\n# Frontend Boilerplate\n\nA boilerplate of things that mostly shouldn't exist.\n\n## Contains\n\n- [x] [Webpack](https://webpack.github.io)\n- [x] [React](https://facebook.github.io/react/)\n- [x] [Redux](https://github.com/reactjs/redux)\n- [x] [Babel](https://babeljs.io/)\n- [x] [Autoprefixer](https://github.com/postcss/autoprefixer)\n- [x] [PostCSS](https://github.com/postcss/postcss)\n- [x] [CSS modules](https://github.com/outpunk/postcss-modules)\n- [x] [Rucksack](http://simplaio.github.io/rucksack/docs)\n- [x] [React Router Redux](https://github.com/reactjs/react-router-redux)\n- [x] [Redux DevTools Extension](https://github.com/zalmoxisus/redux-devtools-extension)\n- [ ] Redux effects\n- [x] TodoMVC example\n\n## Setup\n\n```\n$ npm install\n```\n\n## Running\n\n```\n$ npm start\n```\n\n## Build\n\n```\n$ npm run build\n```\n\n## Note\n\nMy personal projects have diverged from this quite a bit, I use browserify now instead etc, but feel free to use this if it fits your needs! I won't be updating it a ton for now unless I have time to update it to match my current workflow.\n\n# License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fl2silver%2Ferschema-redux-immutable-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fl2silver%2Ferschema-redux-immutable-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fl2silver%2Ferschema-redux-immutable-tutorial/lists"}