Компонент не обновляется правильно при обновлении магазина - PullRequest
2 голосов
/ 22 марта 2019

Я вызываю мое 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 && !!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 && !!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(), но, когда запрос завершен, и мое хранилище резервных копий обновлено, мой экран продолжает загружаться так, даже если состояние загрузки в магазин false

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

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

Когда я нажимаю одну из этих кнопок, компонент обновляет свое состояние, и это вызывает повторную визуализацию, а затем, наконец, данные из моего хранилища отображаются как объекты в цензурированных областях - это был единственный способ, которым я решил -рендерит вещи с помощью 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",

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

1 Ответ

0 голосов
/ 24 марта 2019

В вашем коде нет ничего плохого. Именно так и должно быть.

Ваш компонент реагирования не должен перерисовываться при изменении состояния редукса. Изменение состояния редукса только меняет ваш реквизит. На основе новых реквизитов вам решать, хотите ли вы, чтобы ваш компонент повторно рендерился, что бы вы сделали, изменив состояние компонента.

Если вы хотите увидеть, появляются ли новые реквизиты, вам следует использовать метод componentDidUpdate(prevProps) lifeCycle, а не componentDidMount(). Вы можете сделать это следующим образом:

componentDidUpdate(prevProps) {
    if (prevProps.someProp != this.props.someProp) {
        console.log(this.props);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...