简体   繁体   English

更新父状态时重新渲染子级

[英]Rerender child when parent state is updated

I am currently creating a MultiCheckbox Component like:我目前正在创建一个 MultiCheckbox 组件,如:

Checkbox.tsx复选框.tsx

import './Checkbox.scss';

import React, {ChangeEvent, Component,} from 'react';

/*
 * The properties of Checkbox
 */
interface ICheckboxProps<TYPE> {
    id: string;
    name: string;
    value: TYPE;
    checked?: boolean;
    //if a cross should be used instead of a hook
    cross?: boolean;
    disabled?: boolean;
    //should show a label on the right side of the checkbox
    showLabel?: boolean;
    //get the string for the value of the checkbox
    labelFunc?: (value: TYPE) => string;
    // an onChange function which gets called with the state.checked argument
    onChange?: (checkbox: ICheckboxState<TYPE>) => void;
}

interface ICheckboxState<TYPE> {
    // checked state of the checkbox
    checked: boolean;
    value: TYPE;
}

class CheckboxComponent<TYPE> extends Component<ICheckboxProps<TYPE>, ICheckboxState<TYPE>> {
    constructor(props: ICheckboxProps<TYPE>) {
        super(props);
        console.log(props);
        // set the initial state
        this.state = {
            checked: props.checked == null ? false : props.checked,
            value: props.value,
        };
        console.log(props);
    }

    /*
     * Render the component as ReactElement
     */
    public render(): JSX.Element {
        console.log('Checkbox render: ');
        console.log(this.state);
        return (
            <div className={'checkbox'}>
                <input
                    id={this.props.id}
                    type={'checkbox'}
                    name={this.props.name}
                    className={this.props.cross === true ? 'checkbox-cross' : 'checkbox-hook'}
                    onChange={this.onInputElementChangeEvent}
                    checked={this.state.checked}
                    disabled={this.props.disabled}
                />
                {this.props.showLabel === true ? (
                    <label
                        className="checkbox-label"
                        htmlFor={this.props.id}>
                        {typeof this.props.labelFunc === 'function' ?
                            this.props.labelFunc(this.state.value) : String(this.state.value)}
                    </label>
                ) : null}
            </div>
        );
    }

    private onInputElementChangeEvent = (e: ChangeEvent<HTMLInputElement>): void => {
        this.onChange(e.target.checked);
    }

    private onChange(checked: boolean): void {
        // set the new state when the "onChange" event of the checkbox happens
        this.setState({
            checked: checked,
            value: this.state.value,
        }, () => {
            // if there is an onChange function supscribed to the event handler than execute it with the current "checked" as
            if (typeof this.props.onChange === 'function') {
                this.props.onChange(this.state);
            }
        });
    }

    public isChecked(): boolean {
        return this.state.checked;
    }

    //return only the value if it's checked
    public getValue(): TYPE {
        return this.state.value;
    }
}

export const Checkbox = (CheckboxComponent);

and the

MultiCheckbox.tsx MultiCheckbox.tsx

import './MultiCheckbox.scss';

import React, {Component,} from 'react';
import {Checkbox} from "../Checkbox";

/*
 * The properties of Checkbox
 */
interface IMultiCheckboxProps<TYPE> {
    id: string;
    values: TYPE[];
    idFunc: (value: TYPE) => any;
    //if a cross should be used instead of a hook
    cross?: boolean;
    initialChecked?: boolean;
    disabled?: boolean;
    //get the string for the value of the checkbox
    labelFunc: (value: TYPE) => string;
    // an onChange function which gets called with the state.checked argument
    onChange?: (selected: TYPE[]) => void;
    //all checkbox
    allButton?: boolean;
    //empty checkbox value
    emptyButton?: boolean;
    //label for empty checkbox
    emptyLabel?: string;
}

interface IMultiCheckboxState<TYPE> {
    values: SelectedValue<TYPE>[];
    all: boolean;
    empty: boolean;
}

interface SelectedValue<TYPE> {
    id: any;
    value: TYPE;
    selected: boolean;
}

class MultiCheckboxComponent<TYPE> extends Component<IMultiCheckboxProps<TYPE>, IMultiCheckboxState<TYPE>> {
    constructor(props: IMultiCheckboxProps<TYPE>) {
        super(props);

        // set the initial state
        this.state = {
            values: props.values.map(value => {
                return {
                    id: props.idFunc(value),
                    value: value,
                    selected: props.initialChecked == null ? false : this.props.initialChecked
                };
            }),
            all: props.initialChecked == null ? false : this.props.initialChecked,
            empty: false
        };
    }

    /*
       * Render the component as ReactElement
       */
    public render(): JSX.Element {
        console.log('render')
        console.log(this.state);
        const id = 'multicheckbox-' + this.props.id;
        const subId = id + '-checkbox-';
        var checkboxes = this.state.values.map(value =>
            <Checkbox
                key={subId + value.id}
                id={subId + value.id}
                name={this.props.labelFunc(value.value)}
                checked={value.selected}
                showLabel={true}
                value={value.value}
                labelFunc={this.props.labelFunc}
                cross={this.props.cross}
                disabled={this.props.disabled}
                onChange={(state) => this.onCheckboxChanged(state.checked, state.value)}
            />
        );

        if (this.props.allButton) {
            checkboxes = checkboxes.concat(
                <Checkbox
                    key={subId + 'all'}
                    id={subId + 'all'}
                    name={'Alle'}
                    value={'Alle'}
                    showLabel={true}
                    labelFunc={(value) => value}
                    cross={this.props.cross}
                    disabled={this.props.disabled}
                    checked={this.state.all}
                    onChange={(state) =>
                        this.setAllChecked(state.checked)
                    }
                />
            );
        }

        if (this.props.emptyButton) {

        }

        console.log(checkboxes);

        return (
            <div
                id={id}
                key={id}
            >{checkboxes}</div>
        );
    }

    private onCheckboxChanged(checked: boolean, value: TYPE): void {
        alert(value.toString() + ' is checked: ' + checked);
        //TODO set boolean true/false on this.state.values -> checked!
    }

    private setAllChecked(checked: boolean): void {
        console.log(checked);
        console.log(this.state);
        this.setState({
            values: this.state.values.map(val => {
                return {
                    id: val.id,
                    value: val.value,
                    selected: checked
                };
            }),
            all: checked,
            empty: this.state.empty
        }, this.onSelectedChanged);
    }

    private onSelectedChanged(): void {
        if (this.props.onChange) {
            this.props.onChange(this.state.values.map(value => {
                return value.value
            }));
        }
    }

}

export const MultiCheckbox = (MultiCheckboxComponent);

And my main problem is that whenever I click on the "All-Checkbox" the other entries are not updated... The state get's changed on the "MultiCheckboxComponent", but the "constructor" is not called on the "Checkbox", so it's state is not updated and rerendered correctly.我的主要问题是,每当我点击“All-Checkbox”时,其他条目都不会更新......“MultiCheckboxComponent”上的状态发生了变化,但是“Checkbox”上没有调用“构造函数”,所以它的状态未正确更新和重新渲染。 I am new to React and I want to create a component without "redux-store" which can be used on different forms (local-store) and populates it's values/states upwards to a more specific component which stores it in redux.我是 React 的新手,我想创建一个没有“redux-store”的组件,它可以在不同的形式(本地存储)上使用,并将它的值/状态向上填充到一个更具体的组件,将它存储在 redux 中。

Like:喜欢:

  1. FooComponent (list of Foo) -> MultiCheckboxComponent -> multiple Checkboxes
  2. FeeComponent (list of Fee) -> MultiCheckboxComponent -> multiple Checkboxes
  3. LuuComponent (stuff) -> single Checkbox

But whenever I call "setState()" on the MultiCheckboxComponent the "render" happens, and render also happens on CheckboxComponent, but the "props" are not used ("Constructor" not called).但是每当我在 MultiCheckboxComponent 上调用“setState()”时,“render”就会发生,并且渲染也会在 CheckboxComponent 上发生,但是没有使用“props”(“Constructor”没有被调用)。 How can I set the state on the "Child" from the "Parent"?如何从“父”设置“子”的状态?

I believe you're having trouble because you're thinking about how these sort of things should be modelled in react in slightly the wrong way.我相信您遇到了麻烦,因为您正在考虑如何以稍微错误的方式对此类事物进行建模以做出反应。

The constructor is only called once - when the component is created.构造函数只调用一次 - 在创建组件时。 I don't think the state inside your checkbox components is necessary - after all - they seem to be pretty much duplicating the props.我不认为你的复选框组件内的状态是必要的——毕竟——它们似乎几乎是在复制道具。

When you write React code - think of your props as changing values that will trigger updates in your child components.当您编写 React 代码时 - 将您的 props 视为将触发子组件更新的更改值。 Use these to affect how the component looks.使用这些来影响组件的外观。

Here's some changes I'd make:这是我要做的一些更改:

  • Get rid of the checkbox state - you probably don't need it摆脱复选框状态 - 你可能不需要它
  • Because you don't need state, you can write a pure functional component (stateless).因为不需要状态,所以可以写一个纯函数式组件(无状态)。 This makes your code easier to reason about as there are less moving parts这使您的代码更容易推理,因为移动部件较少
  • Destructure your props to save repeating this.props.X解构你的道具以节省重复 this.props.X
  • Use shortcircuiting to reduce verboseness (returning a falsey value to the renderer ignores it - more on this here https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical--operator )使用短路来减少冗长(将 falsey 值返回给渲染器会忽略它 - 更多关于这里https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical--operator

This leaves you with the following:这给您留下了以下内容:


interface ICheckboxProps<TYPE> {
  id: string;
  name: string;
  value: TYPE;
  checked?: boolean;
  //if a cross should be used instead of a hook
  cross?: boolean;
  disabled?: boolean;
  //should show a label on the right side of the checkbox
  showLabel?: boolean;
  //get the string for the value of the checkbox
  labelFunc?: (value: TYPE) => string;
  // an onChange function which gets called with the state.checked argument
  onChange?: (checkbox: ICheckboxState<TYPE>) => void;
}

function CheckboxComponent<TYPE>(props: ICheckboxProps<TYPE>) {
  console.log('Checkbox render: ');
  // Destructure props - save writing out this.props.foo each time
  const { name, id, cross, onChange, checked, disabled, showLabel } = this.props;

  return (
    <div className="checkbox">
      <input
        id={id}
        type="checkbox"
        name={name}
        className={cross ? 'checkbox-cross' : 'checkbox-hook'}
        onChange={(e) => onChange && onChange(e.target.checked)}
        checked={checked}
        disabled={disabled}
      />
      {showLabel && (
        <label className="checkbox-label" htmlFor={id}>
          {labelFunc ? labelFunc(value) : value}?
        </label>
      )}
    </div>
  );
}

export const Checkbox = (CheckboxComponent);

Let me know if you need any further help with this!如果您需要任何进一步的帮助,请告诉我!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM