質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.03%

Reduxで配列の要素を削除したい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 507

sakusaka

score 6

React+Reduxで配列要素をIDで削除したい

React+Reduxを学び始めてまだ、数ヶ月です。
簡単なTodoリストを作成し、
配列に要素を追加していくことはできたのですが、
削除することができません。

削除する際にKeyとして利用したいIDをどこから取ればいいのか、
わからなく困っています。

できれば、配列のKey要素をうまくReducerに渡して、処理したいと思っています。

実際のコード

export const addTodo = (todo) => {
  return { 
    type: 'ADD_TODO',
    // id: nextTodoId++,
    payload: { todo: todo }
  };
}

export const delTodo = (deltodo) => {
    return { 
      type: 'DEL_TODO',
      payload: { deltodo: deltodo }
    };
  }

export const changeTodo = (changetodo) => {
    return { 
      type: 'CHANGE_TODO',
      payload: { changetodo: changetodo }
    };
  }
import React from 'react';

export default class Todo extends React.Component {
  state = {
    todo: ''
  }

  render() {
    console.log(this.props);

    // StoreのTodoからリストを生成
    const list = this.props.todo.todoList.map((todo, index) => <li key={index}>{todo}</li>)
    // <button onClick={() => this.props.changeTodo(this.state.todo)}>変更</button></li>)

    return (
      <div>
        <input type="text" onChange={elm => this.setState({ todo: elm.target.value })} />
        <button onClick={() => this.props.addTodo(this.state.todo)}>追加</button>
        <button onClick={() => this.props.delTodo(this.state.deltodo)}>削除</button>
        <ul>
          {list}
        </ul>
      </div>
    );
  }
}
import { connect } from 'react-redux';
import * as actions from '../actions/Todo';
import Todo from '../components/Todo';

const mapStateToProps = state => {
  return {
    todo: state.todo,
    deltodo: state.deltodo,
    changetodo: state.changetodo,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    addTodo: (todo) => dispatch(actions.addTodo(todo)),
    delTodo: (deltodo) => dispatch(actions.delTodo(deltodo)),
    changeTodo: (changetodo) => dispatch(actions.changeTodo(changetodo)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Todo)
const initialState = {
    todoList: [],
  }

  export const todoReducer = (state = initialState, action) => {
    switch (action.type) {
      case 'ADD_TODO':
        return {
          ...state,
          todoList: state.todoList.concat([action.payload.todo])
        };
      case 'DEL_TODO':
        return {
          ...state,
          todoList: state.todoList.concat([action.payload.todo])
        };
      case 'CHANGE_TODO' :
        const changetodo = action.payload.changetodo;
        const changeState = Object.assign({}, state);
        changeState.todoList.pop(action.id, changetodo);
        return changeState;
      default:
        return state;
    }
  };

試したこと

Reducer内で、TodoをAddする際に、action.idを付与したりしましたが、
配列として、下記のような感じとなり、うまく取得できませんでした。

todoList: Array(3)
0: "0"
1: "dscsd"
2: "1"
3: "dscsd"
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

こんにちは。

この回答では、削除の仕様を、以下の手順(1),(2)のようなものと解釈しました。

(1) foobarbazfoobarbaz を順に追加する。以下のように表示される。

  • foo
  • bar
  • baz
  • foo
  • bar
  • baz

(2) その後、入力欄に bar と入力して [削除] をクリックすると、以下のように表示される。

  • foo
  • baz
  • foo
  • baz

すなわち、入力されたテキストに完全一致する todoアイテムのすべてをリストから削除するものとします。

削除の仕様が上記のようなものであることを前提として、コードの修正は以下のようにすればよいです。

(1) components/Todo

this.state に todo がありますが、削除ボタンをクリックされたときに this.props.delTodo に渡す引数も、 this.state.todo を使うようにします。

修正前:

<button onClick={() => this.props.delTodo(this.state.deltodo)}>削除</button>

修正後:

<button onClick={() => this.props.delTodo(this.state.todo)}>削除</button>

(2) actions/Todo

上記の (1) に合わせて、アクションクリエータdelTodoで返されるアクションのpayload に含まれるプロパティも修正しておきます。

修正前:

export const delTodo = (deltodo) => {
   return {
     type: 'DEL_TODO',
     payload: { deltodo: deltodo }
   };
 }

修正後:

export const delTodo = (todo) => {
   return {
     type: 'DEL_TODO',
     payload: { todo }
   };
 }

(3) containers/Todo

(2)と同様に、deltodo を todo に修正します。

修正前:

delTodo: (deltodo) => dispatch(actions.delTodo(deltodo)),

修正後:

delTodo: (todo) => dispatch(actions.delTodo(todo)),

(4) reducers/Todo

削除後の state.todoList を作成する部分を以下のように修正します。

修正前:

todoList: state.todoList.concat([action.payload.todo])

修正後:

todoList: state.todoList.filter(todo => todo !== action.payload.todo)

動作確認用のコード

ご質問に挙げられているコードに対して上記の修正を加えていったものを、以下のレポジトリ

に上げておきましたので、お手元に git clone するなりして動作確認してみてください。アプリのひな形をcreate-react-appで作っているので、 yarn install , yarn start で動きます。初回のコミットでは、JSのコードはご質問に挙げられているものをそのままコピペしたものを使って、index.js と App.jsはこちらで適宜作成しました。

もし削除の要件が、冒頭に書いたようなものではない場合は、<input>で入力された文字列から、どのように削除されるアイテムが指定されるのか、コメントからお知らせください。

以上、参考になれば幸いです。

追記

コメントに回答します。

まず初めに、components/Todo.js の以下の部分

// StoreのTodoからリストを生成
const list = this.props.todo.todoList.map((todo, index) => <li key={index}>{todo}</li>)

で <li> の key として配列のインデクスである index を渡していますが、これは実は推奨されないkey の指定方法です。 Reactのドキュメントの Lists and Keys の下の方に、

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);


というコード例に対して

We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state. 

と書かれています。将来、ご質問上げられているコードの todoList も、要素の順序を入れ替える機能を追加したくなるかもしれません。なので、まず todoList を単純な文字列の配列ではなく、以下のような形式のオブジェクトの配列にします。

{
  id: 10,  /* 1ずつ増える整数 */ 
  todo: "コードを書く", /* 文字列 */
}

このように、todoListの要素を拡張したのち、

  • IDを特定して、そのIDを持つ要素を配列から削除するアクションとリデューサー内の処理の追加
  • 適宜、各ToDoアイテムごとの1件削除を行うUIを追加

という修正を行います。

さきほど挙げたレポジトリでは、順に、以下の3点のコミットでこれらを行いました。(cloneしたのであれば pull してください)

以下は、現時点のものを実行して、foobarbaz とアイテムを追加していったときの表示です。

イメージ説明

ちなみに、 id を 9001 始まりにしているのは特に意味はありませんが、配列のインデクスではない ことを明確にするために 0 始まりではない数字にしてみたという意図です。

上記の状態から bar の左側にある削除ボタンをクリックすると、リストから bar が削除され、以下のようになります。

イメージ説明

以上です。何か不明な点あれば、またコメントください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/08 00:51

    おおおお!ありがとうございます!!!!!

    表示できました。そういうことなんですね。
    全く初歩的な質問に長らくお付き合いいただき、本当にありがとうございます。

    そして、最後ご確認遅くなり申し訳ありません。。。

    本当にありがとうございました!!

    キャンセル

  • 2019/05/08 14:58

    どういたしまして!
    解決されたようでよかったです 👏
    ちなみに、質問の対象になるコードをきちんと用意してGitHub に上げておいたのは、とてもよいと思いました。(ので、質問の評価を+1)

    キャンセル

  • 2019/05/09 00:59

    ありがとうございます!
    他の質問するときも気をつけますね。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る