Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert to Hooks #8

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"env": {
"es6": true,
"browser": true,
"jest": true
},
"plugins": ["react", "prettier", "jsx-a11y", "sort-imports-es6-autofix", "react-hooks"],
"extends": [
"eslint:recommended",
"plugin:react/all",
"plugin:import/errors",
"prettier",
"prettier/react",
"plugin:jsx-a11y/recommended"
],
"settings": {
"import/resolver": {
"node": {
"moduleDirectory": ["node_modules", "app/"]
}
},
"react": {
"version": "16.5.2"
}
},
"globals": {
"Promise": true,
"process": true,
"module": true
},
"rules": {
"import/namespace": ["error", { "allowComputed": true }],
"prettier/prettier": [
"error",
{
"printWidth": 120,
"singleQuote": true,
"semi": false,
"trailingComma": "es5",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "avoid"
}
],
"no-console": 0,
"quotes": ["error", "single", { "avoidEscape": true }],
"object-shorthand": ["error", "always"],
"jsx-a11y/no-autofocus": 0,
"react-hooks/exhaustive-deps": "warn",
"react-hooks/rules-of-hooks": "error",
"react/prop-types": 0,
"react/forbid-prop-types": 0,
"react/jsx-filename-extension": 0,
"react/jsx-curly-brace-presence": ["error", { "props": "never", "children": "ignore" }],
"react/jsx-handler-names": 0,
"react/jsx-max-depth": [2, { "max": 5 }],
"react/jsx-no-bind": 0,
"react/no-danger": 0,
"react/function-component-definition": [
2,
{
"namedComponents": "arrow-function",
"unnamedComponents": "arrow-function"
}
],
"react/jsx-sort-props": [
"error",
{
"callbacksLast": false,
"shorthandFirst": false,
"shorthandLast": false,
"ignoreCase": true,
"noSortAlphabetically": false,
"reservedFirst": false
}
],
"react/no-set-state": 0,
"react/require-optimization": 0,
"react/no-multi-comp": 0,
"react/forbid-component-props": 0,
"react/sort-prop-types": [
"error",
{
"callbacksLast": false,
"ignoreCase": true,
"requiredFirst": false
}
],
"sort-imports-es6-autofix/sort-imports-es6": [
2,
{
"ignoreCase": true,
"ignoreMemberSort": false,
"memberSyntaxSortOrder": ["single", "multiple", "all", "none"]
}
],
"sort-keys": 0,
"sort-vars": ["error", { "ignoreCase": false }]
}
}
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.cache
package.json
package-lock.json
public
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
48 changes: 48 additions & 0 deletions app/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import HTML5Backend from 'react-dnd-html5-backend'
import initialData from './initial-data'
import React, { useCallback, useState } from 'react'
import Tree from './components/Tree'
import { DndProvider } from 'react-dnd'
import { findItem, removeNode } from './utils'

const App = () => {
const [tree, setTree] = useState(initialData)

const moveItem = useCallback(
(id, afterId, nodeId) => {
if (id === afterId) return
const newTree = [...tree]

const item = { ...findItem(id, newTree) }
if (!item.id) {
return
}

const destination = nodeId ? findItem(nodeId, newTree).items : newTree

if (!afterId) {
removeNode(id, newTree)
destination.push(item)
} else {
const index = destination.indexOf(destination.filter(destinationItem => destinationItem.id == afterId).shift())
removeNode(id, newTree)
destination.splice(index, 0, item)
}

setTree(newTree)
},
[tree]
)

return <Tree items={tree} moveItem={moveItem} parent={null} />
}

const AppWrapped = () => {
return (
<DndProvider backend={HTML5Backend}>
<App />
</DndProvider>
)
}

export default AppWrapped
117 changes: 42 additions & 75 deletions app/components/Item.js
Original file line number Diff line number Diff line change
@@ -1,78 +1,45 @@
import React, { Component, PropTypes } from 'react'
import { DragSource, DropTarget } from 'react-dnd'
import React from 'react'
import Tree from './Tree'

const source = {
beginDrag(props) {
return {
id: props.id,
parent: props.parent,
items: props.item.children
}
},

isDragging(props, monitor) {
return props.id == monitor.getItem().id
}
}

const target = {
canDrop() {
return false
},

hover(props, monitor) {
const {id: draggedId} = monitor.getItem()
const {id: overId} = props

if (draggedId == overId || draggedId == props.parent) return
if (!monitor.isOver({shallow: true})) return

props.move(draggedId, overId, props.parent)
}
}

@DropTarget('ITEM', target, connect => ({
connectDropTarget: connect.dropTarget()
}))
@DragSource('ITEM', source, (connect, monitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging()
}))
export default class Item extends Component {
static propTypes = {
id : PropTypes.any.isRequired,
parent : PropTypes.any,
item : PropTypes.object,
move : PropTypes.func,
find : PropTypes.func
};

render() {
const {
connectDropTarget, connectDragPreview, connectDragSource,
item: {id, title, children}, parent, move, find
} = this.props

return connectDropTarget(connectDragPreview(
<div>
{connectDragSource(
<div style={{
background: 'white',
border: '1px solid #ccc',
padding: '1em',
marginBottom: -1
}}
>{title}</div>
)}
<Tree
parent={id}
items={children}
move={move}
find={find}
/>
import { useDrag, useDrop } from 'react-dnd'

const Item = ({ item, parentId, moveItem }) => {
const [{}, dropRef] = useDrop({
accept: 'item',
canDrop: false,
hover: (draggedItem, monitor) => {
if (draggedItem.id === item.id || draggedItem.id === parentId) return
if (!monitor.isOver({ shallow: true })) return

moveItem(draggedItem.id, item.id, parentId)
},
})

const [{}, dragRef] = useDrag({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pull out a preview ref here so we can set the previewing component: const [{}, dragRef, previewRef]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, so there exists the ability to show custom preview components? Nice!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, maybe we can remove the {} here, because eslint is complaining on those lines.
So it will become const [, dragRef, previewRef] = ...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that would be great!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The official react-dnd docs didn't seem to have any info about previewRef thing in the useDrag...
But yeah, that's awesome that we can specify that!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 yeah, i'm not sure why the docs are lacking this. I found a codesandbox example using it.

item: {
id: item.id,
parentId,
items: item.items,
type: 'item',
},
isDragging: monitor => item.id == monitor.getItem().id,
})

return (
<div ref={dropRef}>
<div
Copy link
Owner

@tamagokun tamagokun May 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then use that previewRef here:

<div ref={dropRef}>
  <div ref={previewRef}>
    <div ref={dragRef}>..</div>
    <Tree ... />
  </div>
</div>

This makes it so the Tree attached to the node you are dragging is a part of the drag preview

ref={dragRef}
style={{
background: 'white',
border: '1px solid #ccc',
padding: '1em',
marginBottom: -1,
}}
>
{item.title}
</div>
))
}
<Tree items={item.items} moveItem={moveItem} parentId={item.id} />
</div>
)
}

export default Item
91 changes: 41 additions & 50 deletions app/components/Tree.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,47 @@
import React, { Component, PropTypes } from 'react'
import { DropTarget } from 'react-dnd'
import Item from './Item'

const target = {
drop() {},

hover(props, monitor) {
const {id: draggedId, parent, items} = monitor.getItem()

if (!monitor.isOver({shallow: true})) return

const descendantNode = props.find(props.parent, items)
if (descendantNode) return
if (parent == props.parent || draggedId == props.parent) return

props.move(draggedId, props.id, props.parent)
}
}

@DropTarget('ITEM', target, (connect, monitor) => ({
connectDropTarget: connect.dropTarget()
}))
export default class Tree extends Component {
static propTypes = {
items : PropTypes.array.isRequired,
parent : PropTypes.any,
move : PropTypes.func.isRequired,
find : PropTypes.func.isRequired
};

render() {
const {connectDropTarget, items, parent, move, find} = this.props

return connectDropTarget(
<div style={{
import React from 'react'
import { findItem } from '../utils'
import { useDrop } from 'react-dnd'

const Tree = ({ items, parentId, moveItem }) => {
const [{}, dropRef] = useDrop({
accept: 'item',
// drop: () => {},
// collect: monitor => ({
// isOver: !isPrimary && !!monitor.isOver({ shallow: true }),
// canDrop: !!monitor.canDrop(),
// item: monitor.getItem(),
// dropOffset: monitor.getSourceClientOffset(),
// }),
hover: (draggedItem, monitor) => {
if (!monitor.isOver({ shallow: true })) return

const descendantNode = findItem(parentId, draggedItem.items)
if (descendantNode) return
if (draggedItem.parentId == parentId || draggedItem.id == parentId) return

moveItem(draggedItem.id, undefined, parentId)
},
})

if (!items) return null

return (
<div
ref={dropRef}
style={{
position: 'relative',
minHeight: 10,
paddingTop: 10,
marginTop: -11,
marginLeft: '2em'
}}>
{items.map((item, i) => {
return <Item
key={item.id}
id={item.id}
parent={parent}
item={item}
move={move}
find={find}
/>
})}
</div>
)
}
marginLeft: '2em',
}}
>
{items.map(item => (
<Item id={item.id} item={item} key={item.id} moveItem={moveItem} parentId={parentId} />
))}
</div>
)
}

export default Tree
Loading