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

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

ただいまの
回答率

89.69%

React: 1. ラジオボタンが選択されない 2. Stateが2回目のクリック以降正常に動かない

解決済

回答 1

投稿

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

vankick

score 16

Reactで、フィルター機能を作成しています。
現在、2つの大きな問題があります。

1. ラジオボタンが選択できません。クリックして反応させることはできるのですが、アクティブにならないのです。初期状態のstateをfalseにし、selectしたらtrueになるように設定たらいいのではと思ったのですが、それも上手く動きません。

2. JSONデータを、ラジオボタンのvalueに応じてフィルターしようとしています。let filteredItemを作成し、その中に、大元のJSONデータを、ラジオボタンのvalueでフィルターし、そのデータをpropsとして通そうとしています。
しかし、1回目のクリックでは上手く動くのですが、2回目に別のボタンをクリックすると、filteredItem は空になってしまいます(何の値も入っていない)。どうしたら、フィルター済みのJSONデータをstateに入れ、それをpropsとしてItemコンポーネントに渡すことが出来るのでしょうか。

よろしくお願いします。

import fashion from '../data/fashion.json';

class App extends React.Component {

    state = { products: fashion }

    updateItem  = (filter) => {
        this.setState({ products: fashion})
        let filteredItem = this.state.products.slice();


        let products = filteredItem.filter(item => {
            if (item.category === filter || item.size === filter) {
                return true;
            }

        });
        this.setState({ products: products })
        console.log(products);

    }
    render() {
        return (
            <div className="wrapper">
                <div className="sidebar">
                    <SideBar 
                        onClick = {this.updateItem}
                        items = {this.state.products}
                    />

                </div>

                <main>
                    <Item 
                        items = {this.state.products}
                    />
                </main>
            </div>
        );
    }
}
export default App;

class SideBar extends React.Component {
    constructor(props) {
        super(props);
        this.state = { checked: false }
    }

    getValue = (event) => {
        // console.log("Get value", event.target.value);

        // Trigers updateItem() on App Component
        this.props.onClick(event.target.value);
    }

    toggleChecked = () => {
        if(this.state.checked===false) {
            this.setState({ checked: !this.state.checked });
            console.log("toggleChecked", this.state.checked);
        }
    }

    render() {
        return (
            <div>

                <ul className="category_list">
                    <li>
                        <input 
                            type="radio"
                            value="shirt" 
                            checked={this.toggleChecked} 
                            onChange={(e) => this.getValue(e)}
                        />Shirts
                    </li>
                    <li>
                        <input 
                            type="radio"
                            value="jacket" 
                            checked={this.toggleChecked} 
                            onChange={(e) => this.getValue(e)}
                        />Jackets
                    </li>
                    <li>
                        <input 
                            type="radio"
                            value="skirt" 
                            checked={this.toggleChecked} 
                            onChange={(e) => this.getValue(e)}
                        />Skirts
                    </li>
                    <li>
                        <input 
                            type="radio"
                            value="pants" 
                            checked={this.toggleChecked} 
                            onChange={(e) => this.getValue(e)}
                        />Pants
                    </li>
                </ul>

                <ul>
                    <li>
                        <input 
                            type="radio"
                            value="S" 
                            checked={this.toggleChecked} 
                            onChange={(e) => this.getValue(e)}
                        />S
                    </li>
                    <li>
                        <input 
                            type="radio"
                            value="M" 
                            checked={this.toggleChecked} 
                            onChange={(e) => this.getValue(e)}
                        />M
                    </li>
                    <li>
                        <input 
                            type="radio"
                            value="L" 
                            checked={this.toggleChecked} 
                            onChange={(e) => this.getValue(e)}
                        />L
                    </li>
                </ul>
            </div>
        );
    }    
}
Item.js

const Item = (props) => {

    const renderedList = props.items.map((item) => {

    return (
        <li key={item.id}>
            <div className="image_container"><img src={item.image} alt={item.name} /></div>
            <p className="product_name">{item.name}</p>
            <p className="price">{item.price}</p>
            <p>Size: {item.size}</p>
            <p>Category: {item.category}</p>
        </li>
    )
    });

    return <ul className="products">{renderedList}</ul>
}
[
    {
        "id": "1",
        "name": "Black Shirt",
        "price": "$4.99",
        "image": "201904041432_1.jpg",
        "size": "S",
        "category": "shirt"
    },
    {
        "id": "2",
        "name": "Pink Medium Shirt",
        "price": "$6.99",
        "image": "201904041432_1.jpg",
        "size": "M",
        "category": "shirt"
    },
    {
        "id": "3",
        "name": "Red Shirt",
        "price": "$3.49",
        "image": "201904190794_1.jpg",
        "size": "L",
        "category": "shirt"
    },
    {
        "id": "4",
        "name": "Tight Skirt",
        "price": "$12.99",
        "image": "401902170012_1.jpg",
        "size": "S",
        "category": "skirt"
    },
    {
        "id": "5",
        "name": "Short Skirt",
        "price": "$14.99",
        "image": "901904190003_1.jpg",
        "size": "M",
        "category": "skirt"
    },
    {
        "id": "6",
        "name": "Winter Skirt",
        "price": "$10.99",
        "image": "401902170126_1.jpg",
        "size": "L",
        "category": "skirt"
    },   
    {
        "id": "7",
        "name": "Black Jacket",
        "price": "$44.99",
        "image": "201902181237_1.jpg",
        "size": "S",
        "category": "jacket"
    },
    {
        "id": "8",
        "name": "Denim Jacket",
        "price": "$56.99",
        "image": "042-901811090003_1.jpg",
        "size": "M",
        "category": "jacket"
    },     
    {
        "id": "9",
        "name": "Rider's Jacket",
        "price": "$84.99",
        "image": "201902041846_1.jpg",
        "size": "L",
        "category": "jacket"
    },
    {
        "id": "10",
        "name": "Blue Medium Shirt",
        "price": "$6.99",
        "image": "901904240035_1.jpg",
        "size": "M",
        "category": "pants"
    }
]
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

0

こんにちは

ざっと見たところ、ご質問の 

  • 1.については、Reactを使って、(ラジオボタンに限らず) <input> などの入力要素から発生したイベントから、入力された値を取得し、それを適切なコンポーネントの state に反映して、再度、入力要素の表示状態に反映させるという一連の流れのご理解がまだ少し曖昧なのかなという印象です。
          
  • 次に、2.については、

フィルター済みのJSONデータをstateに入れ、

という着想があまりよろしくないかなという気がしました。
具体的には、Appがstate に持つべき値は以下の2つで事足ります。

  • カテゴリーのフィルタとして選ばれている値
  • サイズのフィルタとして選ばれている値

上記の考えでリファクタしたコードが以下です。(フィルタのクリアやスタイルを適宜追加、修正しています)

App.js

import React from 'react'
import SideBar from './SideBar';
import Item from './Item';
import FASHION_DATA from '../data/fashion.json';
import './App.css';

class App extends React.Component {

  state = {
    category: "",
    size: "",
  }

  filterChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value,
    });
  }

  filterClear = () => {
    this.setState({
      category: "",
      size: "",
    })
  }

  render() {
    const { category, size } = this.state;
    const items = FASHION_DATA.filter(item =>
      [item.category, ""].includes(category) &&
      [item.size, ""].includes(size)
    );

    return (
      <div className="wrapper">
        <div className="sidebar">
          <SideBar
            filter={this.state}
            onChange={this.filterChange}
            onClear={this.filterClear}
          />
        </div>

        <main>
          <Item items={items} />
        </main>
      </div>
    );
  }
}

export default App;

SideBar.js

import React from 'react'

const FILTER_VALUES = [
  { name: 'category', values: ['shirt', 'jacket', 'skirt', 'pants'] },
  { name: 'size', values: ['S', 'M', 'L'] }
];

class SideBar extends React.Component {
  render() {
    const { filter, onChange, onClear } = this.props;

    return (
      <div>
        {
          FILTER_VALUES.map(({name, values}) =>
            <ul
              className={`${name}_list`}
              key={name}
            >
              {
                values.map(v =>
                  <li key={v}>
                    <input
                      name={name}
                      type="radio"
                      value={v}
                      checked={filter[name] === v}
                      onChange={onChange}
                    />
                    <span className='capitalize'>{v}</span>
                  </li>
                )
              }
            </ul>
          )
        }
        <button onClick={onClear}>フィルタをクリア</button>
      </div>
    );
  }
}

export default SideBar;

Item.js

import React from 'react'

const Item = (props) => {
  const { items } = props;

  const renderedList = items
    .map(item => {
      return (
        <li key={item.id}>
          <div className="image_container"><img src={item.image} alt={item.name} /></div>
          <p className="product_name">{item.name}</p>
          <p className="price">{item.price}</p>
          <p>Size: {item.size}</p>
          <p>Category: {item.category}</p>
        </li>
      )
  });

  return <ul className="products">{renderedList}</ul>
};

export default Item;

App.css

.sidebar ul {
    list-style: none;
    border: 1px solid #ccc;
}

main ul {
    list-style: none;
}

main li {
    border: 1px solid #ccc;
    margin-bottom: 5px;
}

.capitalize {
    text-transform: capitalize;
}

上記のコードを以下にpushしていますので、お手元に clone するなどして、動かしてみてください。

なお上記のレポジトリの最初のコミット では、コンポーネント(App, SideBar, Item) のコードはご質問に挙げられているものをほぼそのままコミットしており、その後、何度かのコミットを経て上記のコードになっています。

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

追記

SideBar もItemと同様に state を持たないので、 Functional Component にしました。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/04/27 15:58

    非常にご丁寧に解説していただき、ありがとうございます!
    正直まだ修正していただいたコードの意味は完璧には理解できていない部分もあるのですが、これからじっくり見させていただきたいと思います。

    キャンセル

  • 2019/04/27 16:01

    どういたしまして! 回答のコードについて何か不明点あれば、またコメント頂ければと思います。

    キャンセル

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

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