React-dnd + Redux
файлы редьюсера и экшена для каждого компонента: DropTarget и DraggableAnimal. Редьюсер DropTarget будет содержать только информацию о названии каждой из досок:
// /services/reducers/drop-target.js
const initialState = {
boards: ["default", "fish", "mammals", "insects"]
};
export const dropTargetReducer = (state = initialState, action) => {
return state;
};
Редьюсер DraggleAnimal будет содержать исходное состояние с массивом животных animals. У каждого животного будет атрибут board с начальным значением default. В сам редьюсер draggableAnimalReducer будет записана обработка только одного экшена — UPDATE_TYPE:
// /services/reducers/draggable-animal.js
import { UPDATE_TYPE } from "../actions/draggable-animal";
const initialState = {
animals: [
{
id: 12,
content: "🦔",
description: "Ёжики очень классные. И являются настоящими звёздами в Instagram.",
board: "default"
},
// Другие животные
]
};
export const draggableAnimalReducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_TYPE: {
return {
...state,
animals: state.animals.map(animal =>
animal.id === action.id ? {...animal, board: action.board} : animal
)
};
}
default:
return state;
}
}
// /services/reducers/index.js
import { combineReducers } from "redux";
import { draggableAnimalReducer } from "./draggable-animal";
import { dropTargetReducer } from "./drop-target";
export const rootReducer = combineReducers({
animalList: draggableAnimalReducer,
boardList: dropTargetReducer
});
Подключим стор к проекту и переделаем компонент DragAndDropContainer. Теперь у нас есть несколько целевых элементов для перетаскивания, поэтому заберём из хранилища массив boards и отрисуем по компоненту DropTarget:
const DragAndDropContainer = () => {
// Получим все доски из хранилища
const boards = useSelector(state => state.boardList.boards)
return (
<section className={styles.app}>
<DndProvider backend={HTML5Backend}>
<article className={styles.element}>
{
// Отрисуем каждую доску и передадим её название в качестве пропса
boards.map((item, i) => (
<DropTarget key={i} board={item} />
))
}
</article>
</DndProvider>
</section>
)
};
export default DragAndDropContainer;
Обновим DropTarget и хук useDrop в нём:
const DropTarget = ({ board }) => {
const dispatch = useDispatch();
const animals = useSelector(state => state.animalList.animals)
const [{ isHover } , drop] = useDrop({
accept: "animal",
collect: monitor => ({
isHover: monitor.isOver(),
}),
drop(itemId) {
dispatch({
type: UPDATE_TYPE,
...itemId,
board
});
},
});
const boardClass = board === 'default' ? styles.animalsPull : styles.animals;
const borderColor = isHover ? 'lightgreen' : 'transparent';
return (
<div ref={drop} className={boardClass} style={{borderColor}}>
{animals
// Получим массив животных, соответствующих целевому элементу
.filter(animal => animal.board === board)
// Отрисуем массив
.map(animal => <DraggableAnimal key={animal.id} data={animal} />)
}
</div>
)
};
export default DropTarget
Последнее, что требуется сделать для достижения такого результата, — модифицировать разметку DraggableAnimal в зависимости от того, в каком целевом элементе располагается перетаскиваемый.
const DraggableAnimal = ({ data }) => {
const { id, board } = data;
const [{ isDrag }, drag] = useDrag({
type: "animal",
item: { id },
collect: monitor => ({
isDrag: monitor.isDragging()
})
});
// Отображение DraggableAnimal в целевом элементе "default"
const draggableAnimalPreview = (
<div ref={drag} className={styles.animalElement}>
{data.content}
</div>
);
// Отображение DraggableAnimal в других целевых элементах
const draggableAnimalCard = (
<div ref={drag} className={styles.item}>
<span className={styles.animalItem}>
{data.content}
</span>
<p>
{data.description}
</p>
</div>
);
// Проверяем, где сейчас находится карточка
const draggableAnimalContent = (
board === "default" ? draggableAnimalPreview : draggableAnimalCard
);
return (
!isDrag && draggableAnimalContent
);
};
export default DraggableAnimal
#react