{"action":"opened","issue":{"url":"https://api.github.com/repos/dfilatov/vidom-animation-group/issues/1","repository_url":"https://api.github.com/repos/dfilatov/vidom-animation-group","labels_url":"https://api.github.com/repos/dfilatov/vidom-animation-group/issues/1/labels{/name}","comments_url":"https://api.github.com/repos/dfilatov/vidom-animation-group/issues/1/comments","events_url":"https://api.github.com/repos/dfilatov/vidom-animation-group/issues/1/events","html_url":"https://github.com/dfilatov/vidom-animation-group/issues/1","id":303185567,"number":1,"title":"child can be null","user":{"login":"LexRiver","id":1415005,"avatar_url":"https://avatars2.githubusercontent.com/u/1415005?v=4","gravatar_id":"","url":"https://api.github.com/users/LexRiver","html_url":"https://github.com/LexRiver","followers_url":"https://api.github.com/users/LexRiver/followers","following_url":"https://api.github.com/users/LexRiver/following{/other_user}","gists_url":"https://api.github.com/users/LexRiver/gists{/gist_id}","starred_url":"https://api.github.com/users/LexRiver/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/LexRiver/subscriptions","organizations_url":"https://api.github.com/users/LexRiver/orgs","repos_url":"https://api.github.com/users/LexRiver/repos","events_url":"https://api.github.com/users/LexRiver/events{/privacy}","received_events_url":"https://api.github.com/users/LexRiver/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-03-07T17:16:09Z","updated_at":"2018-03-07T17:16:09Z","closed_at":null,"author_association":"NONE","body":"Child can be null\r\n```jsx\r\n<CssAnimationGroup appear=\"error-message-appear\" enter=\"error-message-enter\" leave=\"error-message-leave\">\r\n                    {this.state.showErrorA &&\r\n                    <div class=\"error-message\" key=\"A\">Error A</div>\r\n                    }\r\n                    {this.state.showErrorB &&\r\n                    <div class=\"error-message\" key=\"B\">Error B</div>\r\n                    }\r\n\r\n</CssAnimationGroup>\r\n```\r\n\r\nHere is my quick fix, but I think it can be made much more elegant.\r\n\r\nAnimationGroup.js\r\n```js\r\nimport { Component, IS_DEBUG } from 'vidom';\r\nimport { childrenToArray, checkChildrenKeys, collectChildrenKeys, mergeChildren } from './utils';\r\n\r\nexport default class AnimationGroup extends Component {\r\n    onInit() {\r\n        this._appearingKeys = {};\r\n        this._enteringKeys = {};\r\n        this._leavingKeys = {};\r\n        this._keysToEnter = null;\r\n        this._keysToLeave = null;\r\n\r\n        const children = childrenToArray(this.children);\r\n\r\n        if(IS_DEBUG) {\r\n            checkChildrenKeys(children);\r\n        }\r\n\r\n        this.setState({ children });\r\n    }\r\n\r\n    onRender() {\r\n        return this.state.children;\r\n    }\r\n\r\n    onMount() {\r\n        const { onAppear } = this.attrs;\r\n\r\n        if(onAppear) {\r\n            this.state.children.forEach(child => {\r\n                if(!child) {\r\n                    return;\r\n                }\r\n                const { key } = child;\r\n\r\n                this._appearingKeys[key] = onAppear(child.getDomNode(), () => {\r\n                    this._onAppeared(key);\r\n                }) || noOp\r\n            });\r\n        }\r\n    }\r\n\r\n    onChange(prevAttrs, prevChildren) {\r\n        if(this.children === prevChildren) {\r\n            return;\r\n        }\r\n\r\n        const nextChildren = childrenToArray(this.children);\r\n\r\n        if(IS_DEBUG) {\r\n            checkChildrenKeys(nextChildren);\r\n        }\r\n\r\n        const { children } = this.state,\r\n            nextKeys = collectChildrenKeys(nextChildren),\r\n            currentKeys = collectChildrenKeys(children);\r\n\r\n        children.forEach(child => {\r\n            if(!child) {\r\n                return;\r\n            }\r\n            const { key } = child;\r\n\r\n            if(!nextKeys[key] && !this._leavingKeys[key]) {\r\n                (this._keysToLeave || (this._keysToLeave = {}))[key] = true;\r\n            }\r\n        });\r\n\r\n        nextChildren.forEach(child => {\r\n            if(!child) {\r\n                return;\r\n            }\r\n            const { key } = child;\r\n\r\n            if(!currentKeys[key] || this._leavingKeys[key]) {\r\n                (this._keysToEnter || (this._keysToEnter = {}))[key] = true;\r\n            }\r\n        });\r\n\r\n        this.setState({ children : mergeChildren(children, nextChildren, nextKeys) });\r\n    }\r\n\r\n    onUpdate() {\r\n        if(!this._keysToEnter && !this._keysToLeave) {\r\n            return;\r\n        }\r\n\r\n        const { onEnter, onLeave } = this.attrs;\r\n\r\n        this.state.children.forEach(child => {\r\n            if(!child) {\r\n                return;\r\n            }\r\n\r\n            const { key } = child;\r\n\r\n            if(this._keysToEnter && this._keysToEnter[key]) {\r\n                if(this._leavingKeys[key]) {\r\n                    this._leavingKeys[key]();\r\n                    delete this._leavingKeys[key];\r\n                }\r\n\r\n                if(onEnter) {\r\n                    this._enteringKeys[key] = onEnter(child.getDomNode(), () => {\r\n                        this._onEntered(key);\r\n                    }) || noOp;\r\n                }\r\n            }\r\n            else if(this._keysToLeave && this._keysToLeave[key]) {\r\n                if(this._appearingKeys[key]) {\r\n                    this._appearingKeys[key]();\r\n                    delete this._appearingKeys[key];\r\n                }\r\n                else if(this._enteringKeys[key]) {\r\n                    this._enteringKeys[key]();\r\n                    delete this._enteringKeys[key];\r\n                }\r\n\r\n                if(onLeave) {\r\n                    this._leavingKeys[key] = onLeave(child.getDomNode(), () => {\r\n                        this._onLeft(key);\r\n                    }) || noOp;\r\n                }\r\n                else {\r\n                    this._removeChildByKey(key);\r\n                }\r\n            }\r\n        });\r\n\r\n        this._keysToEnter = null;\r\n        this._keysToLeave = null;\r\n    }\r\n\r\n    _onAppeared(key) {\r\n        delete this._appearingKeys[key];\r\n    }\r\n\r\n    _onEntered(key) {\r\n        delete this._enteringKeys[key];\r\n    }\r\n\r\n    _onLeft(key) {\r\n        if(this._leavingKeys[key]) {\r\n            delete this._leavingKeys[key];\r\n            this._removeChildByKey(key);\r\n        }\r\n    }\r\n\r\n    _removeChildByKey(key) {\r\n        this.setState({ children : this.state.children.filter(child => child.key !== key) });\r\n    }\r\n}\r\n\r\nfunction noOp() {}\r\n\r\n```\r\n\r\n\r\nutils.js\r\n```js\r\nimport { console } from 'vidom';\r\n\r\nexport function childrenToArray(children) {\r\n    return children?\r\n        Array.isArray(children)? children : [children] :\r\n        [];\r\n}\r\n\r\nexport function checkChildrenKeys(children) {\r\n    children.forEach(child => {\r\n        if(!child) {\r\n            return;\r\n        }\r\n\r\n        if(child.key == null) {\r\n            console.error('You must specify a key for each child of AnimationGroup.')\r\n        }\r\n    });\r\n}\r\n\r\nexport function collectChildrenKeys(children) {\r\n    return children.reduce((res, child) => {\r\n        if(!child) {\r\n            return res;\r\n        }\r\n\r\n        res[child.key] = child;\r\n        return res;\r\n    }, {});\r\n}\r\n\r\nexport function mergeChildren(currentChildren, nextChildren, nextKeys) {\r\n    if(!currentChildren.length) {\r\n        return nextChildren;\r\n    }\r\n\r\n    if(!nextChildren.length) {\r\n        return currentChildren;\r\n    }\r\n\r\n    const nextChildrenPending = {};\r\n    let pendingChildren = [];\r\n\r\n    currentChildren.forEach(child => {\r\n        if(!child) {\r\n            return;\r\n        }\r\n\r\n        if(nextKeys[child.key]) {\r\n            if(pendingChildren.length) {\r\n                nextChildrenPending[child.key] = pendingChildren;\r\n                pendingChildren = [];\r\n            }\r\n        }\r\n        else {\r\n            pendingChildren.push(child);\r\n        }\r\n    });\r\n\r\n    return nextChildren\r\n        .reduce((res, child) => {\r\n            if(!child) {\r\n                return res;\r\n            }\r\n\r\n            if(nextChildrenPending[child.key]) {\r\n                res = res.concat(nextChildrenPending[child.key]);\r\n            }\r\n\r\n            res.push(child);\r\n\r\n            return res;\r\n        }, [])\r\n        .concat(pendingChildren);\r\n}\r\n\r\n```"}}
{"actor":{"display_login":"LexRiver"}}