父组件仅在按输入键时才发送数据,但子对象不会重新渲染。
智能 如果将console.log(this.props.state.userValues)放置在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);
孩子
我从智能组件接收到userValues,如果我将console.log(this.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));
减速机 我觉得还可以
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;
}
}
答案 0 :(得分:0)
You're correctly creating a new state
object in your account reducer, which is sufficient to signal to your parent component that the mapped props have changed. However, in your reducer, you're always carrying forward the same values
object.
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 };
}
This isn't a problem when it comes to triggering renders in your parent component, since state.account
has an updated reference after each action. However:
<UserProfile
...
userValues={this.props.state.values}
...
/>
UserProfile
is specifically being passed the values
object, and will thus be doing its shallow reference checks for incoming props on the values
object, not the state object. Since values
always refers to the same object according to your reducers, UserProfile
will not re-render when props.userValues
changes. If you're going to be passing a property of one of your props to a child component, you need to ensure that it ALSO passes the shallow reference check:
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;
}
And the same idea for USER_ACCOUNT_UPDATE
.
This is why its much safer to always treat objects and arrays as immutable in your reducers. Your reducers are currently fine for signalling Redux of changed state, but you lose the guarantee when/if you start prop-drilling as you're doing with UserProfile
.