Компонент не выполняет повторную визуализацию корректно при обновлении хранилища

#android #react-native #redux #react-redux #redux-saga

#Android #react-native #переопределение #реагировать-redux #redux-saga

Вопрос:

Я вызываю свое GET_REQUEST действие на экране, оно Saga выполняет запрос и запускает мое GET_SUCCESS действие, но мой экран не выполняет повторную визуализацию самостоятельно, только когда я обновляю состояние компонента, запускается повторная визуализация и отображаются новые реквизиты, отражающие хранилище.

итак … мой магазин, по-видимому, работает нормально, в Reactotron хранилище заполнено, как вы можете видеть ниже:

https://user-images.githubusercontent.com/27635248/54751638-52671b80-4bba-11e9-9f9b-b7eee7f3deba.png

Мой user Редуктор:

 export const Types = {
  GET_SUCCESS: 'user/GET_SUCCESS',
  GET_REQUEST: 'user/GET_REQUEST',
  GET_FAILURE: 'user/GET_FAILURE',
  UPDATE_USER: 'user/UPDATE_USER',
  REHYDRATE_SUCCESS: 'user/REHYDRATE_SUCCESS',
  REHYDRATE_FAILURE: 'user/REHYDRATE_FAILURE',
}


const INITIAL_STATE = {
  data: {},
  status: {
    loading: false,
    error: null,
  }
}

export default function user(state = INITIAL_STATE, action) {
  switch (action.type) {
    case Types.GET_SUCCESS: {
      return {
        ...state,
        data: action.payload.user,
        status: {
          ...state.status,
          loading: false,
          error: null
        }
      }
    }

    case Types.GET_REQUEST: {
      return {
        ...state,
        status: {
          error: null,
          loading: true
        }
      }
    }

    case Types.GET_FAILURE: {
      if (Object.keys(state.data).length) {
        action.payload.isStateEmpty = false
        return {
          ...state,
          status: {
            ...state.status,
            loading: false
          }
        }
      } else {
        return {
          ...state,
          status: {
            ...state.status,
            loading: false
          }
        }
      }
    }

    case Types.REHYDRATE_SUCCESS: {
      return {
        ...state,
        data: action.payload.user,
        status: {
          error: null,
          loading: false
        }
      }
    }

    case Types.REHYDRATE_FAILURE: {
      return {
        ...state,
        status: {
          loading: false,
          error: action.payload.error
        }
      }
    }

    case Types.UPDATE_USER: {
      return {
        ...state,
        data: {
          ...state.data,
          ...action.payload.data
        }
      }
    }

    default: {
      return state
    }
  }
}

export const Creators = {
  getUserSuccess: user => ({
    type: Types.GET_SUCCESS,
    payload: {
      user,
    },
  }),

  getUserRequest: () => ({
    type: Types.GET_REQUEST,
    payload: {}
  }),

  getUserFailure: error => ({
    type: Types.GET_FAILURE,
    payload: {
      error,
      isStateEmpty: true
    }
  }),

  rehydrateUserSuccess: user => ({
    type: Types.REHYDRATE_SUCCESS,
    payload: {
      user
    }
  }),

  rehydrateUserFailure: error => ({
    type: Types.REHYDRATE_FAILURE,
    payload: {
      error
    }
  }),

  updateUser: data => ({
    type: Types.UPDATE_USER,
    payload: {
      data
    }
  }),
}
  

Моя user сага:

 import { Types } from '../ducks/user'
import { call, put, takeLatest } from 'redux-saga/effects'
import { Creators as UserActions } from '../ducks/user'

import API from 'utils/api'
import DAO from '../../utils/dao';


function formatUsuarioToDbKeys(usuario, pk_pessoa) {
    return {
      pk_user: usuario.id,
      fk_pessoa: pk_pessoa,
      username_usu: usuario.username,
      email_usu: usuario.email,
      first_name_usu: usuario.first_name,
      las_name_usu: usuario.last_name,
      ativo_usu: usuario.ativo,
    }
}

function formatPessoaToDbKeys(pessoa) {
    return {
      pk_pessoa: pessoa.pessoa_id,
      fk_funcao: pessoa.funcao_id,
      nome_pes: pessoa.nome,
      codigo_erp_pes: pessoa.codigo_erp,
      ativo_pes: pessoa.ativa,
    }
}

function formatFuncaoToDbKeys(funcao) {
    return {
      pk_funcao: funcao.id,
      nome_fun: funcao.nome,
    }
}

function formatEquipeToDbKeys(equipe) {
    return {
      pk_equipe: equipe.id,
      nome_equ: equipe.nome,
      ativo_equ: equipe.ativo,
    }
}

function* getUser() {
  try {
    const dados = yield call(API.getInstance().getRequest, '/initializer/?tipo=1')

    const funcao = yield call(formatFuncaoToDbKeys, dados.funcao)
    const pessoa = yield call(formatPessoaToDbKeys, dados.pessoa)
    const usuario = yield call(formatUsuarioToDbKeys, ...[dados.usuario, dados.pessoa.pessoa_id])
    const equipe = yield call(formatEquipeToDbKeys, dados.equipe)

    yield put(UserActions.getUserSuccess({ usuario, pessoa, funcao, equipe }))

    yield call(DAO.getInstance().createFuncao, funcao)
    yield call(DAO.getInstance().createPessoa, pessoa)
    yield call(DAO.getInstance().createUsuario, usuario)
    yield call(DAO.getInstance().createEquipe, equipe)
  } catch (error) {
    yield put(UserActions.getUserFailure('Ocorreu um erro ao buscar dados do usuário.'))
    yield call(console.warn, error.message)
  }
}

function* rehydrateUser(action) {
  if (action) {
    try {
      const user = yield call(DAO.getInstance().getUsuario)
      yield put(UserActions.rehydrateUserSuccess(user))
    } catch (error) {
      yield put(UserActions.rehydrateUserFailure('Ocorreu um erro ao buscar dados do usuário.'))
      yield call(console.warn, error.message)
    }
  }
}

export default sagas = [
  takeLatest(Types.GET_REQUEST, getUser),
  takeLatest(Types.GET_FAILURE, rehydrateUser)
]
  

и, наконец, это мой компонент:

 class Dashboard extends Component {

 ...state and others methods

  componentDidMount() {
    this.props.getUserRequest()
    this.props.getConstructionsRequest()
  }

  render() {
    const spin = this.state.spin_value.interpolate({
      inputRange: [0, 1],
      outputRange: ['0deg', '360deg']
    })
    return (
      <View style={{ flex: 1 }}>
        <StatusBar
          hidden={false}
          backgroundColor={colors.blueDarker} />

        <ScrollView
          ref={'refScrollView'}
          stickyHeaderIndices={[2]}
          style={styles.container}>

          <ImageBackground
            source={require('imgs/capa_dashboard.png')}
            blurRadius={4}
            style={styles.imageBackground}>

            <View style={styles.headerContainer}>

              <Text style={styles.textVersion}>{require('../../../package.json').version}</Text>

              <Animated.View style={{ transform: [{ rotate: spin }] }}>
                <TouchableOpacity
                  onPress={() => {
                    this._spinIcon()
                    this._handleRefresh()
                  }}>
                  <MaterialIcons
                    name={'refresh'}
                    size={35}
                    style={styles.refreshIcon}
                    color={'white'} />
                </TouchableOpacity>
              </Animated.View>
            </View>

            <View style={styles.headerButtonsContainer}>
              <HeaderLeft
                navigation={this.props.navigation} />

              <Image
                style={styles.image}
                source={require('imgs/logo_header.png')} />

              <HeaderRight />
            </View>

            <View style={{ flex: 1, justifyContent: 'center' }}>
              {!this.props.user.status.loading amp;amp; !!Object.keys(this.props.user.data).length
                ? <View>
                  <Text style={[styles.textNome, { textAlign: 'center' }]}>{this.props.user.data.pessoa.nome_pes}</Text>
                  <Text style={styles.textAuxiliar}>{this.props.user.data.funcao.pk_funcao == 1 ? this.props.user.data.equipe.nome_equ : 'Engenheiro(a)'}</Text>
                </View>
                : <ActivityIndicator style={{ alignSelf: 'center' }} size={'large'} color={colors.blue} />
              }
            </View>
          </ImageBackground>

          <ActionsButtons
            stylesButton1={{ borderBottomColor: this.state.button_obras_selected ? 'white' : colors.transparent }}
            stylesText1={{ color: this.state.button_obras_selected ? 'white' : 'lightgrey' }}
            numberButton1={this.props.count_obras}
            callbackButton1={async () => this.setState({ button_obras_selected: true })}

            stylesButton2={{ borderBottomColor: !this.state.button_obras_selected ? 'white' : colors.transparent }}
            stylesText2={{ color: !this.state.button_obras_selected ? 'white' : 'lightgrey' }}
            numberButton2={this.state.count_enviar}
            callbackButton2={async () => this.setState({ button_obras_selected: false })}
          />

          <View>
            {this.state.button_obras_selected
              ? <View style={styles.containerTextInput} >
                <TextInput
                  onEndEditing={() => this._handleEndSearch()}
                  onFocus={() => this._handleStartSearch()}
                  ref={'textInputRef'}
                  style={styles.textInput}
                  onChangeText={search_text => this._filterSearch(search_text)}
                  autoCapitalize="none"
                  onSubmitEditing={() => this._handleClickSearch()}
                  underlineColorAndroid='rgba(255,255,255,0.0)'
                  placeholder={'Buscar obras...'} />

                <TouchableOpacity
                  onPress={() => this._handleClickSearch()}
                  style={styles.searchImage}>
                  <MaterialCommunityIcons
                    name={'magnify'}
                    size={30}
                    color={'black'} />
                </TouchableOpacity>
              </View >
              : null}
          </View>

          <View>
            {this.state.button_obras_selected
              ? !this.props.constructions.status.loading amp;amp; !!Object.keys(this.props.constructions.data).length
                ? <View style={{ flex: 1, marginTop: 8 }}>
                  <FlatList
                    data={this.props.constructions.data}
                    removeClippedSubviews={true}
                    keyExtractor={item => item.pk_obra.toString()}
                    renderItem={obra =>
                      <ObraComponent
                        callback={this._callbackObra}
                        item={obra.item}
                        isCurrentObra={nome => this.state.obra_atual == nome}
                      />
                    }
                  />
                </View>
                : <ActivityIndicator style={{ marginTop: 150 }} size={50} color={colors.blueDarker} />
              :
              <View style={{
                flex: 1,
                marginTop: 8,
              }}>
                <FlatList
                  data={[]}
                  removeClippedSubviews={true}
                  keyExtractor={item => item.fk_obra.toString()}
                  renderItem={acao =>
                    <AcaoComponent item={acao.item} />
                  }
                />
              </View>
            }
          </View>
        </ScrollView>
      </View>
    )
  }
}

const mapStateToProps = state => ({
  user: state.user,
  constructions: state.constructions,
  count_obras: state.constructions.data.length
})

const mapDispatchToProps = dispatch =>
  bindActionCreators({
    ...UserActions,
    ...ConstructionsActions.DEEP,
    ...ConstructionsActions.SHALLOW
  }, dispatch)


export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
  

Как я уже говорил ранее, я вызываю getUserRequest() для запуска GET_REQUEST действия в componentDidMount() , но когда запрос выполнен и мое хранилище redux обновлено, мой экран продолжает загружаться таким образом, даже если состояние загрузки в хранилище равно false

https://user-images.githubusercontent.com/27635248/54752551-4e88c880-4bbd-11e9-9164-5fad46db6c13.png

Я использовал очень похожие структуры хранилища и saga в других проектах, но по какой-то причине в этом конкретном я не смог разобраться

Когда я нажимаю одну из этих кнопок, компонент обновляет свое состояние, и это запускает повторный рендеринг, затем, наконец, данные из моего хранилища отображаются в виде реквизитов в областях с цензурой — это был единственный способ, который я придумал для повторного рендеринга с помощью redux (по крайней мере, в этом проекте), принудительный повторный рендеринг путем обновления состояния компонентов с помощью this.setState() :

https://user-images.githubusercontent.com/27635248/54752771-fef6cc80-4bbd-11e9-9d15-a46acb87f3a4.png

Я не уверен, что не так с моей реализацией, я проверил другие похожие проблемы, ни одна из них, похоже, не сработала, если у кого-то есть предложения, не стесняйтесь публиковать их ниже, заранее спасибо.

 "axios": "^0.18.0",
"react-native": "~0.55.2",
"react-redux": "^6.0.0",
"reactotron-redux": "^2.1.3",
"reactotron-redux-saga": "^4.0.1",
"redux": "^4.0.1",
"redux-saga": "^1.0.1",
  

Редактировать:
Итак, насколько я понимаю, обновление хранилища redux отражает новые реквизиты подключенных компонентов, и React обновляет их на основе неглубокого сравнения. Я немного поэкспериментировал, и по какой-то причине мои действия запускаются, саги выполняются нормально, и хранилище успешно изменяется, но mapStateToProps не вызывается после запуска моего успешного действия.
В принципе, я не могу выбрать, должен ли компонент обновляться (используя shouldComponentUpdate для сравнения prevProps и this.props ), потому что компонент даже не получает новые реквизиты от mapStateToProps .