Skip to content

Instantly share code, notes, and snippets.

@markerikson
Created May 2, 2016 16:53
Show Gist options
  • Save markerikson/ca34306c54b21e7c9505b524cd537abe to your computer and use it in GitHub Desktop.
Save markerikson/ca34306c54b21e7c9505b524cd537abe to your computer and use it in GitHub Desktop.
Redux-ORM tree data sample
import {
schema,
getModelByType,
} from "models/models";
import {UPDATE_ITEM_CHECK_STATES} from "constants/items";
export const updateItemCheckState = (itemID, itemType, newCheckState) => (dispatch, getState) => {
const state = getState();
const session = schema.from(state.entities);
const model = getModelByType(session, itemID, itemType);
let descendants = model.children;
const leaves = [];
const otherItems = [];
// Find all things underneath the clicked item, and split them into
// a list of A's (our "leaves") and a list of all other items
while(descendants.length > 0) {
const childItem = descendants.shift();
const childType = childItem.getClass().modelName;
const itemsArray = (childType === "A") ? leaves : otherItems;
// Don't bother updating things that already have the right value
if(childItem.checked !== newCheckState) {
itemsArray.push({itemID : childItem.getId(), itemType : childType});
}
descendants = descendants.concat(childItem.children);
}
dispatch({
type : UPDATE_ITEM_CHECK_STATES,
payload : {itemID, itemType, newCheckState, leaves, otherItems},
});
// call server API with list of updated leaves
};
import React, {Component} from "react";
import {connect} from "react-redux";
import {schema} from "models/models";
import {isSelectedItemSelector} from "selectors/entities";
const leafMapState = (state, ownProps) => {
const session = schema.from(state.entities);
const leafModel = session.A.withId(ownProps.itemID);
const leafData = leafModel.ref;
const leaf = {
...leafData,
checkState : leafModel.checkState.value,
};
const selected = isSelectedItemSelector(state, ownProps);
return {leaf, selected};
};
class LeafComponent extends Component {
render() {
const {leaf, selected} = this.props;
// render appropriate stuff here
return <div />
}
}
export default connect(leafMapState)(LeafComponent);
/*
My app has three main data types: A, B, and C. I have a treeview that displays items of each type
inside a Folder, with checkboxes for each type. A is considered a "leaf", can also appear under
B > D > A, and C > A. Also, A is the only type that has its checked state stored on the server.
For the others, I need to determine the checked state based on the combined values of their children
(all checked -> checked, none checked -> unchecked, some checked -> partial). This means I need to
be able to walk up and down the tree structure.
*/
import {fk, many, oneToOne, Model, Schema} from "redux-orm";
import * as folderConstants from "constants/folders";
import _ from "lodash";
export function getModelByType(session, itemID, itemType) {
const modelClass = session[itemType];
const model = modelClass.withId(itemID);
return model;
}
class CheckState extends Model {
static generate(value = false) {
return this.create({value});
}
}
CheckState.generate = CheckState.generate.bind(CheckState);
CheckState.modelName = "CheckState";
class TreeItemWithChildren extends Model {
get children() {
return [];
}
get checked() {
return this.checkState.value;
}
set checked(newCheckedValue) {
this.checkState.value = newCheckedValue;
}
updateCheckState() {
// snipped: loop over children, determine check state based on theirs
}
}
class A extends TreeItemWithChildren {
static get fields() {
return {
checkState : oneToOne("CheckState")
};
}
get parent() {
switch(this.type) {
case "TYPE_1" :
return Folder.withId(folderConstants.FOLDER_TYPE_1);
case "TYPE_2" :
return this.b;
case "TYPE_3" :
return this.d.first();
case "TYPE_4" :
return this.c.first();
default :
break;
}
}
}
import {UPDATE_ITEM_CHECK_STATES} from "constants/items";
import _ from "lodash";
import {schema, getModelByType} from "models/models";
export function updateCheckStateForModels(state, action) {
const {payload} = action;
const session = schema.from(state);
const {newCheckState, leaves, otherItems, itemID, itemType} = payload;
// Create a list of all changed items, clicked item first
const allChangedItems = [{itemID, itemType}].concat(leaves, otherItems);
// Look up all explicitly changed items, hydrate models, and update their checkstates
const allChangedModels = allChangedItems.map(item => {
const itemModel = getModelByType(session, item.itemID, item.itemType);
itemModel.checked = newCheckState;
return itemModel;
});
const startingModel = _.first(allChangedModels);
let ancestor = startingModel.parent;
// Recurse upwards and recalculate dependent checkstates from the new values
while(ancestor) {
ancestor.updateCheckState();
ancestor = ancestor.parent;
}
return session.reduce();
}
export default function entitiesReducer(state = initialState, action) {
switch(action.type) {
case UPDATE_ITEM_CHECK_STATES:
return updateCheckStateForModels(state, action);
default:
return state;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment