今回はReactのバージョン18で、ドラッグ&ドロップを実現する方法を書いていきます。
react-beautiful-dndを利用します。
Contents
アプリ作成
長いのでさっそくはじめます。任意のフォルダにReactアプリを作成します。
npx create-react-app myapp
cd myapp
yarn start
App.jsの不要なコードを削除して、Hello worldを表示するように記述します。
import './App.css';
function App() {
return (
<div className="App">
<h2>Hello world</h2>
</div>
);
}
export default App;
ブラウザの表示はこのようになります。
データ作成
initial-Data.jsファイルを作成して、データを作成します。
const initialData = {
tasks: {
'task-1': { id: 'task-1', content: '掃除をする' },
'task-2': { id: 'task-2', content: '洗濯をする' },
'task-3': { id: 'task-3', content: '買い物をする' },
'task-4': { id: 'task-4', content: '料理をする' },
'task-5': { id: 'task-5', content: '運動をする' },
},
columns: {
'column-1': {
id: 'column-1',
title: 'To do',
taskIds: ['task-1', 'task-2', 'task-3', 'task-4', 'task-5' ],
},
},
columnOrder: ['column-1'],
};
export default initialData;
App.js
App.jsに作成したデータをimportします。
import initialData from './initial-data';
App.jsをclass形式に書き換え、次のように記述します。
import React from 'react';
import './App.css';
import initialData from './initial-data';
class App extends React.Component {
state = initialData;
render() {
return this.state.columnOrder.map((columnId) => {
const column = this.state.columns[columnId];
const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]);
return column.title;
});
}
}
export default App;
この時点でブラウザには、columnsのtitleであるTo doが表示されます。
表示されない場合は、コードの打ち間違えがないか確認しましょう。
column.jsx
column.jsxファイルを作成して、次のように記述します。
import React from 'react';
export default class Column extends React.Component {
render() {
return this.props.column.title;
}
}
App.js
App.jsで、作成したcolumn.jsxファイルをインポートして、<Column />をreturnします。
次のように書き換えましょう。
次のように書き換えましょう。
import React from 'react';
import './App.css';
import initialData from './initial-data';
import Column from './column';
class App extends React.Component {
state = initialData;
render() {
return this.state.columnOrder.map((columnId) => {
const column = this.state.columns[columnId];
const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]);
return <Column key={column.id} column={column} tasks={tasks} />;
});
}
}
export default App;
スタイルシート
次にスタイルシートを定義していきますので、App.cssのインポート(
次のコマンドを打ちます。
import './App.css';
)は削除して、https://atlassian.design/のスタイルシートをインストールします。次のコマンドを打ちます。
yarn add @atlaskit/css-reset
column.jsxファイルでスタイルを定義していきます。
styled-componentsをインストールします。
yarn add styled-components
column.jsxファイルに次のように記述します。
`はバックティックですので、ご注意ください。
import React from 'react';
import styled from 'styled-components';
const Container = styled.div``;
const Title = styled.h3``;
const TaskList = styled.div``;
export default class Column extends React.Component {
render() {
return (
<Container>
<Title>{this.props.column.title}</Title>
<TaskList>タスクリスト</TaskList>
</Container>
)
}
}
ブラウザで見ると、現在はまだこのような表示です。
column.jsxファイルに、下記のコード加えます。
import React from 'react';
import styled from 'styled-components';
const Container = styled.div`
margin: 8px;
border: 1px solid lightgrey;
border-radius: 2px;
`;
const Title = styled.h3`
padding: 8px;
`;
const TaskList = styled.div`
padding: 8px;
`;
ブラウザにスタイルが適用されます。
task.jsx
次にtask.jsxファイルを作成して、次のように記述します。
import React from 'react';
import styled from 'styled-components';
const Container = styled.div`
border: 1px solid lightgrey;
border-radius: 2px;
padding: 8px;
margin-bottom: 8px;`
export default class Task extends React.Component {
render() {
return <Container>{this.props.task.content}</Container>;
}
}
colum.jsx
作成したTaskコンポーネントをcolum.jsxでインポートして利用します。
import React from 'react';
import styled from 'styled-components';
import Task from './task';
const Container = styled.div`
margin: 8px;
border: 1px solid lightgrey;
border-radius: 2px;
`;
const Title = styled.h3`
padding: 8px;
`;
const TaskList = styled.div`
padding: 8px;
`;
export default class Column extends React.Component {
render() {
return (
<Container>
<Title>{this.props.column.title}</Title>
<TaskList>
{this.props.tasks.map(task => <Task key={task.id} task={task} />)}
</TaskList>
</Container>
)
}
}
ブラウザにTo do表示
ブラウザではこのように表示されます。
react-beautiful-dndインストール
react-beautiful-dndをインストールします。
yarn add react-beautiful-dnd
react-beautiful-dndの構成要素
react-beautiful-dndは、次の3つの要素で構成されます。
DragDropContext
Droppable
Draggable
DragDropContext
App.jsファイルで、DragDropContextを定義します。次のように記述します。
import React from 'react';
import initialData from './initial-data';
import Column from './column';
import '@atlaskit/css-reset';
import { DragDropContext } from 'react-beautiful-dnd';
class App extends React.Component {
state = initialData;
onDragEnd = result => {
}
render() {
return (
<DragDropContext onDragEnd={this.onDragEnd}>{this.state.columnOrder.map((columnId) => {
const column = this.state.columns[columnId];
const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]);
return <Column key={column.id} column={column} tasks={tasks} />;
})}
</DragDropContext>
);
}
}
export default App;
Droppable
column.jsxにDroppableを定義します。
import React from 'react';
import styled from 'styled-components';
import Task from './task';
import { Droppable } from 'react-beautiful-dnd';
const Container = styled.div`
margin: 8px;
border: 1px solid lightgrey;
border-radius: 2px;
`;
const Title = styled.h3`
padding: 8px;
`;
const TaskList = styled.div`
padding: 8px;
`;
export default class Column extends React.Component {
render() {
return (
<Container>
<Title>{this.props.column.title}</Title>
<Droppable droppableId={this.props.column.id}>
{provided => (
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
>
{this.props.tasks.map((task, index) =>
<Task key={task.id} task={task} index={index} />)}
{provided.placeholder}
</TaskList>
)}
</Droppable>
</Container>
)
}
}
Draggable
task.jsxファイルにDraggableを適用します。
import React from 'react';
import styled from 'styled-components';
import { Draggable } from 'react-beautiful-dnd';
const Container = styled.div`
border: 1px solid lightgrey;
border-radius: 2px;
padding: 8px;
margin-bottom: 8px;
background-color: white;
`;
export default class Task extends React.Component {
render() {
return (
<Draggable draggableId={this.props.task.id} index={this.props.index}>
{(provided) => (
<Container
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
{this.props.task.content}</Container>
)}
</Draggable>
);
}
}
<React.StrictMode>をコメントアウト
Reactのバージョン18では、エラーが発生してドラッグできませんので、index.jsファイルの
<React.StrictMode>をコメントアウトします。
import React from 'react';
import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
//<React.StrictMode>
<App />
//</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(console.log);
ドラッグ&ドロップ
これでブラウザに戻り、ドラッグ&ドロップを試しましょう。
現状ではまだ完成ではありません。
試してみるとわかりますが、ドラッグ&ドロップした後に、リストの表示が初期の状態に戻ってしまいます。
現在は次のような動きになっています。
onDragEnd
解決するには、onDragEndにロジックを定義する必要がある為、App.jsに次のように書き加えます。
import React from 'react';
import initialData from './initial-data';
import Column from './column';
import '@atlaskit/css-reset';
import { DragDropContext } from 'react-beautiful-dnd';
class App extends React.Component {
state = initialData;
onDragEnd = result => {
const { destination, source, draggableId } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
const column = this.state.columns[source.droppableId];
const newTaskIds = Array.from(column.taskIds);
newTaskIds.splice(source.index, 1);
newTaskIds.splice(destination.index, 0, draggableId);
const newColumn = {
...column,
taskIds: newTaskIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newColumn.id]: newColumn,
},
};
this.setState(newState);
};
render() {
return (
<DragDropContext onDragEnd={this.onDragEnd}>{this.state.columnOrder.map((columnId) => {
const column = this.state.columns[columnId];
const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]);
return <Column key={column.id} column={column} tasks={tasks} />;
})}
</DragDropContext>
);
}
}
export default App;
完成
これで完成です。
ドラッグ&ドロップが実現できています。
まとめ
今回はReactのバージョン18で、ドラッグ&ドロップを実現する方法を書きました。
最後までお読みいただきありがとうございました。