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

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

ただいまの
回答率

87.35%

React Nativeでredux-sagaが機能しない問題

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 910

score 6

前提

React Native + ReduxでGitHubのリポジトリを検索できるアプリを作成しています。
非同期処理の部分でredux-sagaを使用しています。

想定している流れとしては以下の通りです。

  1. SearchBarにテキストを入力するたびにonChangeTextでactionをdispatchする。(問題ない)
  2. Middlewareで 1 の時にdispatchされるactionを監視する(問題あり)
  3. APIを叩いて取得したリポジトリを包んでreducerにsuccessかfailのactionをdispatch(問題あり)
  4. reducerで新たなstateを作成してstoreを更新(問題ありかも)
  5. FlatListでstateの状態を監視してリポジトリの状態が変わったら、UIに反映させる(問題ありかも)

ゴール

SearchBarで onChnageTextのたびにAPIを叩いてリポジトリを取得し、FlatListに表示する。
※ Javascirpt初心者のため記法など雑な部分が散見されますがご容赦頂ければ幸いです。

現状の把握と問題点の仮説

ここまでで確認したことを簡潔にまとめると

・ ActionCreatorは問題なく機能している(ActionはDispatchされている)

です。テキスト入力のたびにdispatchされるactionは想定ではmiddlewareで止まり、reducerには流れません。
middlewareではsuccessかfailの別のactionをdispatchするようにしています。
しかし、実際にはreducerにテキスト入力のたびにdispatchされるactionが流れています。
(一時的にreducerにテキスト入力のたびにdispatchされるactionのcaseを追加して確かめました)

また、前提の 4,5 を問題ありかもにしたのはreducerで initialState に適当なオブジェクトを入れてもFlatListに反映されなかったためです。

大きく分けて
・ 前提における 2, 3 が問題。つまり redux-saga の扱いに問題がある。
・ 前提における 4, 5 が問題。つまり ReducerとStore、Componentの連携に問題がある。
のどちらかだと考えています。

解決の糸口になるようなアドバイスを頂ければ幸いです。

コード

  • Component(問題となるFlatListを使用しているComponent)
    このアプリではSearchBarの onChangeText と連動してリアルタイムでAPIを叩いています。
    そのため、表示する責務だけを担う Repo_FlatList では makeStateToProps だけを指定し、あえて makeDispatchToProps は nullにしています。 
import React, {Component} from 'react';
import {View, Text, FlatList} from 'react-native';
import {connect} from 'react-redux';

export class Repo_FlatList extends Component {
    render() {
        return(
            <FlatList
                data={this.props.repos}
                renderItem={({repo}) =>
                    <View style={styles.cell}>
                        <Text style={styles.text} >{repo.name}</Text>
                    </View>}
            />
        )
    }
}

const styles = {
    cell: {
        flexDirection: 'row',
        borderStyle: 'solid',
        borderWidth: 0.5,
        borderColor: '#bbb',
    },
    text: {
        padding: 10,
        fontSize: 18,
    },
}

const mapStateToProps = state => ({
    repos: state.responseHandler.repos
})


export default connect(mapStateToProps, null)(Repo_FlatList);
  • Component(SearchBarの部分)
import React, {Component} from 'react';
import {SearchBar} from 'react-native-elements';
import {bindTextChangedAction} from '../actions/ui_action';
import {fetchAction} from '../actions/model_action';
import {connect} from 'react-redux';

export class Repo_SearchBar extends Component {
    render() {
        return(
            <SearchBar 
                round 
                placeholder="Search"
                 onChangeText={text => {this.props.bindTextChangedAction(text); this.props.fetchAction(text)}}
                value={this.props.searchText}
            />
        )
    }
}

const mapStateToProps = state => ({
    searchText: state.bindTextChanged.searchText
})

const mapDispatchToProps = {
   bindTextChangedAction,fetchAction
}

export default connect(mapStateToProps, mapDispatchToProps)(Repo_SearchBar);
  • ActionCreator(fetchの部分)
import {FETCH_REPOSITORY, FETCHREQUEST_SUCCESS, FETCHREQUEST_FAILE} from "../constants/constants"

export const fetchAction = text => {
    return {type: FETCH_REPOSITORY, payload: text}
}

export const requestSuccess = repos => {
    return {type: FETCHREQUEST_SUCCESS, payload: repos}
}

export const requestFaile = e => {
    return {type: FETCHREQUEST_FAILE, payload: e.message}
}
  • reducer 
import { FETCHREQUEST_FAILE, FETCHREQUEST_SUCCESS, FETCH_REPOSITORY } from "../constants/constants";

const initialState = {
    repos: []
}

const responseHandler = (state = initialState, action) => {
    switch(action.type) {
        case FETCHREQUEST_SUCCESS:
            return Object.assign({}, state, {repos: action.payload});
        case FETCHREQUEST_FAILE:
            return Object.assign({}, state, {repos: action.payload});
        default:
            return state
    }
}

export default responseHandler;
  • Middelware(fetchする部分)
import {call, put, takeEvery, take} from 'redux-saga/effects';
import {FETCH_REPOSITORY} from '../constants/constants';
import {requestSuccess, requestFaile} from '../actions/model_action';

function* fetchRepos(action) {
    while(true) {
        const action = yield take(FETCH_REPOSITORY)
        const text = action.payload

        try {
            const repos = yield call(fetchAsync, text)
            yield put(requestSuccess(repos))
        } catch(e) {
            yield put(requestFaile(e.message))
        }
    }
}


function* fetchAsync(text) {
    return fetch(`https://api.github.com/search/repositories?q=${text}+in:name&sort=stars`)
            .then(response => response.json(JSON.parse))
            .then(json => JSON.stringify(json))
            .catch((e) => { throw(e)})
}

export default fetchRepos;
  • Middelware(root)
import { all } from 'redux-saga/effects';
import {fetchRepos} from './watchFetchAction';

export default function* rootSaga() {
    yield all([fetchRepos])
}
  • Store
import { createStore, combineReducers, applyMiddleware} from "redux";
import createSagaMiddleware from 'redux-saga';
import bindTextChanged from './src/reducers/ui_reducer';
import responseHandler from './src/reducers/model_reducer';
import fetchRepos from './src/middleware/watchFetchAction';

const rootReducer = combineReducers({bindTextChanged, responseHandler});

const configureStore = () => {
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));

    sagaMiddleware.run(fetchRepos);

    return store
}

export default configureStore;
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

こんにちは

以下の修正が必要と思われます。

(1) fetchAsync

主な修正点は以下の3点です。

  • fetchAsync はジェネレーター関数にする必要がないので、 function の後の * は不要
  • response.json() の引数に与えている、 JSON.parse が不要
  • 検索に該当したレポジトリー情報の配列は、レスポンスの中の items プロパティを取得する。

修正前

function* fetchAsync(text) {
    return fetch(`https://api.github.com/search/repositories?q=${text}+in:name&sort=stars`)
            .then(response => response.json(JSON.parse))
            .then(json => JSON.stringify(json))
            .catch((e) => { throw(e)})
}

修正後

function fetchAsync(text) {
  return fetch(`https://api.github.com/search/repositories?q=${text}+in:name&sort=stars`)
    .then(response => response.json())
    .then(data => data.items)
    .catch(e => {
      throw e;
    });
}

(2) Repo_FlatList#render

修正点は主に以下の2つです。

  • renderItem に与える関数の引数のオブジェクトに、配列要素が入ってくるプロパティは item と決まっています。
  • keyExtractor  を追加して、key を生成させるようにしないとエラー(ないし警告)が表示されると思われます。

修正前

    render() {
        return(
            <FlatList
                data={this.props.repos}
                renderItem={({repo}) =>
                    <View style={styles.cell}>
                        <Text style={styles.text} >{repo.name}</Text>
                    </View>}
            />
        )
    }

修正後

  render() {
    return(
      <FlatList
        data={this.props.repos}
        renderItem={({item}) =>
          <View style={styles.cell}>
            <Text style={styles.text} >{item.name}</Text>
          </View>
        }
        keyExtractor={item => item.name}
      />
    )
  }

以下は、動作確認のため私の手元でご質問に挙げられているコードをコピペして、各ファイルを作成してアプリを構成し、上記の修正後、iOSシミュレータに表示させた画面です。

イメージ説明

なお上記のサンプルでは、Repo_FlatList の componentDidMount に以下を追加しています。

  componentDidMount() {
    this.props.dispatch(fetchAction('React'));
  }

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

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/10/27 14:49

    ご回答ありがとうございます!正常に動作しました!
    ・JSONのパース
    ・renderItemの引数はitemと決まっていること
    ・keyExtractorの役割
    などとても参考になりました!

    引き続き自分でも理解を深めていきたいと思います!
    お忙しい中お時間をとって回答して頂き本当にありがとうございます。

    キャンセル

  • 2019/10/27 16:10

    どういたしまして。
    > 正常に動作しました!
    とのことで、よかったです👏

    キャンセル

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

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

関連した質問

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