Почему дочерний компонент с redux-формой не перерисовывается? - PullRequest
1 голос
/ 17 апреля 2019

Родительский компонент отправляет данные, но потомки не перерисовывают, только когда нажимают клавишу на входе.

SMART Я отправляю состояние формы userValues, если я помещаю console.log (this.props.state.userValues) в render (), компонент smart render, когда при редуксе получают новые свойства.

import React, { Component, RefObject } from 'react';
import { bindActionCreators } from 'redux';
import CSSModules from 'react-css-modules';
import { connect } from 'react-redux';

import WebappHeader from '../../containers/WebappHeader/WebappHeader';
import BlueSection from '../../containers/BlueSection/BlueSection';
import Title from '../../components/Title/Title';

import { getBase64, isString, userData } from '../../helpers';

import * as styles from './MyAccount.css';
import * as actions from './../../actions/accounts';
import { updateAccountInformation, getAccountInformation } from '../../services/AccountService';
import UserProfile from '../../components/UserProfile/UserProfile';

interface State {
    tabActiveClass: string;
    extensions: string;
    file_64: string;
    unsavedChanges: boolean;
}

interface Props { state: any, actions: any }

class MyAccount extends Component <Props, State> {
    private fileInput: RefObject <HTMLInputElement>;
    private avatarImage: RefObject <HTMLImageElement>;

    constructor(props: any) {
        super(props);
        this.state = { ...this.props.state }
        this.avatarImage = React.createRef();
        this.fileInput = React.createRef();
    }

    componentDidMount() {
        setTimeout(() => {
            getAccountInformation().then(result => {
                result.user
                    ? this.props.actions.userAccountLoad(result.user)
                    : null
            });
        }, 1000)
        // setea la primera tab como la que esta activa
        this.setState({ tabActiveClass: document.getElementsByClassName('tablinks')[0].classList[2] });
    }

    handleSubmit = (userData: any): void => {
        // console.log(userData)
        updateAccountInformation(userData)
            .then((result: any) => {
                !result.error
                    ? this.props.actions.userAccountUpdate(result)
                    : this.props.actions.userAccountError()
            })
    }

    private uploadAvatar(files: any): void {
        if (files.length > 0){
            let file = files[0], extensions_allowed = this.state.extensions.split(',');
            let extension = `.${file.name.split('.').pop().toLowerCase()}`;
            if(extensions_allowed.indexOf(extension) === -1){
                alert(`This extension is not allowed. Use: ${this.state.extensions}`);
                this.fileInput.current!.value = '';
            } else {
                getBase64(file, (result: any) => {
                    this.setState({file_64: result, unsavedChanges: true});
                    this.avatarImage.current!.src = result;
                    console.log(result); // nueva img, ejecutar disparador
                });
            }
        }
    }

    private changeTab(e: any, name: string): void {
        let i: number;
        const future_tab: any = document.getElementById(name);
        const tabcontent: any = document.getElementsByClassName('tabcontent');
        for (i = 0; i < tabcontent.length; i++) {
            tabcontent[i].style.display = 'none';
        }
        const tablinks: any = document.getElementsByClassName('tablinks');
        for (i = 0; i < tablinks.length; i++) {
            tablinks[i].className = tablinks[i].className.replace(` ${this.state.tabActiveClass}`, '');
        }
        future_tab.style.display = 'flex';
        e.currentTarget.classList.add(this.state.tabActiveClass);
    }

    public render() {
        return (
            <BlueSection styleName="section">
                <WebappHeader />
                <div styleName="tabs-headers">
                    <div className="wrapper">
                        <Title text="Account Information" />
                        <ul styleName="list">
                            <li styleName="item active" className="tablinks" onClick={(e: any) => this.changeTab(e, 'profile')}>
                                Profile
                            </li>
                            <li styleName="item" className="tablinks" onClick={(e: any) => this.changeTab(e, 'activity')}>
                                Activity
                            </li>
                            <li styleName="item" className="tablinks" onClick={(e: any) => this.changeTab(e, 'plan')}>
                                Plan & Billing
                            </li>
                        </ul>
                    </div>
                </div>
                <div styleName="tabs-body">
                    <div className="wrapper">
                        <ul styleName="list">
                            <li styleName="item" id="profile" className="tabcontent" style={{'display':'flex'}}>
                                <UserProfile 
                                    onSubmit={this.handleSubmit}
                                    userValues={this.props.state.values}
                                    // avatarImage={this.avatarImage}
                                    // uploadAvatar={this.uploadAvatar}
                                    // fileInput={this.fileInput}
                                />
                            </li>
                            <li styleName="item" id="activity" className="tabcontent">

                            </li>
                            <li styleName="item" id="plan" className="tabcontent">

                            </li>
                        </ul>
                    </div>
                </div>
            </BlueSection>
        )
    }
}

const mapStateToProps = (state: any) => ({ state: state.account });
const mapDispatchToProps = (dispatch: any) => ({ actions: bindActionCreators(actions, dispatch) });

const ComponentWithCSS = CSSModules(MyAccount, styles, { allowMultiple: true });
export default connect(mapStateToProps, mapDispatchToProps)(ComponentWithCSS);

CHILDREN

Я получаю userValues ​​из смарт-компонента формы, если я помещаю console.log (это.props.userValues) в render (), компонент не рендерится при получении новых свойств.

import React, { Component, Fragment } from 'react';
import CSSModules from 'react-css-modules';
import { InjectedFormProps, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';

import Heading from '../Heading/Heading';
import Button from '../Button/Button';
import Input from '../Input/Input';

import * as styles from './UserProfile.css';

interface Props {
  userValues: any,
  avatarImage: any,
  uploadAvatar: any,
  fileInput: any,
  handleSubmit: any,
}

const inputField = ({ input, label, type, meta, disabled, field_value }: any) => (
  <div>
    <Input
      {...input}
      labelText={label}
      styleName="input"
      type={type ? type : "text"}
      disabled={disabled ? disabled : false}
      placeholder={field_value ? field_value : ''}
    />
    {/* fix mejorar mensajes css */}
    {
      meta.error && meta.touched && <span>{meta.error}</span>
    }
  </div>
)
const isRequired = (value: any) => (
  value ? undefined : 'Field Required'
)

class UserProfile extends Component<Props & InjectedFormProps<{}, Props>> {
  constructor(props: any) {
    super(props);
  }
  componentWillMount() { this.props.initialize({ name: this.props.userValues.name }) }
  public render() {
    console.log(this.props.userValues)
    // setTimeout(() => {
    //   this.forceUpdate() // temp (bad) fix
    // }, 2000)
    return (
      <Fragment>
        <div styleName="right-info">
          <Heading text="Your data" styleName="heading" />
          <form id="UserProfile" onSubmit={this.props.handleSubmit} method="POST" styleName="form">
            <fieldset styleName="fieldset">
              <Field
                name="name"
                label="Name"
                validate={isRequired}
                placeholder={this.props.userValues.name}
                component={inputField} />
              <br />
              <Input
                labelText="Email"
                styleName="input"
                type="email"
                disabled={true}
                placeholder={this.props.userValues.email} />
            </fieldset>
            <fieldset styleName="fieldset">
              <Field
                name="phone_number"
                label="Phone Number"
                validate={isRequired}
                component={inputField} />
              <br />
              <Field
                name="company"
                label="Company"
                validate={isRequired}
                component={inputField} />
            </fieldset>
          </form>
          <Heading text="Notifications" styleName="heading" />
          <form styleName="notification-form">
            <label styleName="label">
              I wish to recieve newsletters, promotions and news from BMS
              <input type="checkbox" name="notifications" />
            </label>
            <p styleName="disclaimer">
              <span styleName="bold">Basic information on Data Protection:</span> BMS collects your data too improve our services and, if given consent, will keep you updated on news and promotions of BMS projects.  +Info Privacy Policy
            </p>
          </form>
        </div>
        <div styleName="cta-wrapper">
          <Button
            onClick={this.props.handleSubmit}
            text="SAVE CHANGES"
            filled={true}
          // disabled={!this.props.state.unsavedChanges} 
          />
        </div>
      </Fragment>
    )
  }
}

const UserProfileWithCSS = CSSModules(UserProfile, styles, { allowMultiple: true });
export default connect()(reduxForm<{}, Props>({ form: 'UserProfile' })(UserProfileWithCSS));

REDUCER Я думаю, что все в порядке

import { USER_ACCOUNT_UPDATE, USER_ACCOUNT_LOAD, USER_ACCOUNT_ERROR } from './../actions/types';
import { userData } from '../helpers';

const engine = require('store/src//store-engine');
const storages = require('store/storages/sessionStorage');
const store = engine.createStore(storages);

const INITIAL_STATE = {
    tabActiveClass: '',
    extensions: '.jpeg,.jpg,.gif,.png',
    file_64: '',
    unsavedChanges: false,
    values: {
        name: '',
        email: '',
        phone_number: '',
        company: '',
        notifications: false
    },
};

export default function (state = INITIAL_STATE, action: any) {
    switch (action.type) {
        case USER_ACCOUNT_LOAD: {
            let newState = state;

            newState.values.name = action.payload.profile.first_name;
            newState.values.email = action.payload.email;
            newState.values.phone_number = action.payload.profile.phone_number;
            newState.values.company = action.payload.profile.company;

            return { ...newState };
        }
        case USER_ACCOUNT_UPDATE: {
            let newState = state;
            let storage = store.get('user_data');

            storage.profile.first_name = action.payload.data.name;
            storage.profile.company = action.payload.data.company;
            storage.profile.phone_number = action.payload.data.phone_number;

            newState.values.name = action.payload.data.name;
            newState.values.phone_number = action.payload.data.phone_number;
            newState.values.company = action.payload.data.company;

            store.set('user_data', storage);

            return { ...newState };
        }
        case USER_ACCOUNT_ERROR:
            return { ...state };
        default:
            return state;
    }
}

1 Ответ

0 голосов
/ 17 апреля 2019

Вы правильно создаете новый объект state в редукторе своей учетной записи, которого достаточно, чтобы сообщить родительскому компоненту, что сопоставленные реквизиты изменились. Однако в вашем редукторе вы всегда переносите один и тот же объект values.

case USER_ACCOUNT_LOAD: {
    let newState = state;

    newState.values.name = action.payload.profile.first_name;
    newState.values.email = action.payload.email;
    newState.values.phone_number = action.payload.profile.phone_number;
    newState.values.company = action.payload.profile.company;

    return { ...newState };
}

Это не проблема для запуска рендеров в родительском компоненте, поскольку state.account имеет обновленную ссылку после каждого действия. Тем не менее:

<UserProfile 
    ...
    userValues={this.props.state.values}
    ...
/>

UserProfile специально передается объекту values и, таким образом, будет выполнять поверхностные проверки ссылок на входящие реквизиты для объекта values, а не для объекта состояния. Поскольку values всегда ссылается на один и тот же объект в соответствии с вашими редукторами, UserProfile не будет повторно визуализироваться при изменении props.userValues. Если вы собираетесь передавать свойство одного из ваших реквизитов дочернему компоненту, вам необходимо убедиться, что оно ТАКЖЕ проходит проверку мелкой ссылки:

case USER_ACCOUNT_LOAD: {
    let newState = { ...state };

    newState.values = {
        ...state.values, // Copy all properties from old state
        name: action.payload.profile.first_name, // Replace individual properties with new values.
        email: action.payload.email,
        phone_number = action.payload.profile.phone_number,
        company = action.payload.profile.company
    };

    return newState;
}

И та же идея для USER_ACCOUNT_UPDATE.

Вот почему гораздо безопаснее всегда рассматривать объекты и массивы как неизменные в ваших редукторах. Ваши редукторы в настоящее время хороши для сигнализации Redux об измененном состоянии, но вы теряете гарантию, когда / если вы начинаете проп-бурение, как вы делаете с UserProfile.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...