Повторный дочерний элемент при обновлении родительского состояния

#reactjs #typescript #components

#reactjs #typescript #Компоненты

Вопрос:

В настоящее время я создаю компонент с несколькими флажками, например:

Checkbox.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);
  

и

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);
  

И моя главная проблема заключается в том, что всякий раз, когда я нажимаю на «All-Checkbox», другие записи не обновляются…
Состояние get изменяется в «MultiCheckboxComponent», но «конструктор» не вызывается в «Checkbox», поэтому его состояние не обновляется и не отображается правильно. Я новичок в React и хочу создать компонент без «redux-store», который можно использовать в разных формах (локальное хранилище) и заполняет его значения / состояния до более конкретного компонента, который сохраняет его в redux.

Нравится:

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

Но всякий раз, когда я вызываю «setState ()» в MultiCheckboxComponent, происходит «рендеринг», и рендеринг также происходит в CheckboxComponent, но «реквизиты» не используются («Конструктор» не вызывается). Как я могу установить состояние для «дочернего элемента» из «родительского»?

Ответ №1:

Я полагаю, что у вас возникли проблемы, потому что вы думаете о том, как подобные вещи должны быть смоделированы в react немного неправильно.

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

Когда вы пишете код React — думайте о своих реквизитах как об изменяющихся значениях, которые будут вызывать обновления в ваших дочерних компонентах. Используйте их, чтобы повлиять на внешний вид компонента.

Вот некоторые изменения, которые я бы внес:

  • Избавьтесь от состояния флажка — вам, вероятно, это не нужно
  • Поскольку вам не нужно состояние, вы можете написать чистый функциональный компонент (без состояния). Это упрощает анализ вашего кода, поскольку в нем меньше движущихся частей
  • Уничтожьте свой реквизит, чтобы не повторять this.props.X
  • Используйте короткое замыкание, чтобы уменьшить детализацию (возврат ложного значения в средство визуализации игнорирует его — подробнее об этом здесь https://reactjs.org/docs/conditional-rendering.html#inline-if-with-logical—operator )

Это оставляет вас со следующим:

 
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 amp;amp; onChange(e.target.checked)}
        checked={checked}
        disabled={disabled}
      />
      {showLabel amp;amp; (
        <label className="checkbox-label" htmlFor={id}>
          {labelFunc ? labelFunc(value) : value}?
        </label>
      )}
    </div>
  );
}

export const Checkbox = (CheckboxComponent);
  

Дайте мне знать, если вам понадобится дополнительная помощь в этом!

Комментарии:

1. Но, делая это так, я больше не могу использовать флажок в качестве неконтролируемого компонента.

2. Я не уверен, что вы имеете в виду — в настоящее время это не неконтролируемый компонент. Чтобы компонент был неконтролируемым, нужно позволить DOM быть единственным источником истины, из которого вы затем извлекаете значения в тот момент, когда вам это нужно (т.Е. После завершения работы с формой). На данный момент ваше состояние (в некоторой степени) контролируется родительским элементом — по крайней мере, оно хранится в родительском элементе, но ссылка на обновление значения в дочернем элементе разорвана.

3. Подробнее о неконтролируемых компонентах смотрите здесь . Вы используете ссылку для извлечения значения из DOM reactjs.org/docs/uncontrolled-components.html