React-Loadable повторный рендеринг, в результате чего ввод теряет фокус - PullRequest
0 голосов
/ 04 июля 2018

У меня проблема, из-за которой react-loadable вызывает повторный рендеринг одного из моих компонентов ввода и потерю фокуса после обновления состояния. Я немного покопался и не могу найти никого другого, имеющего эту проблему, поэтому я думаю, что здесь что-то упущено.

Я пытаюсь использовать react-loadable для динамического включения компонентов в мое приложение на основе темы, выбранной пользователем. Это работает нормально.

. / Компоненты / приложение

import React from 'react';
import Loadable from 'react-loadable';

/**
 * Import Containers
 */
import AdminBar from '../../containers/AdminBar';
import AdminPanel from '../../components/AdminPanel';

import 'bootstrap/dist/css/bootstrap.css';
import './styles.css';

const App = ({ isAdmin, inEditMode, theme }) => {
    const MainContent = Loadable({
        loader: () => import('../../themes/' + theme.name + '/components/MainContent'),
        loading: () => (<div>Loading...</div>)
    });

    const Header = Loadable({
        loader: () => import('../../themes/' + theme.name + '/components/Header'),
        loading: () => (<div>Loading...</div>)
    });

    return (
        <div>
            {
                (isAdmin) ? <AdminBar
                                className='admin-bar'
                                inEditMode={inEditMode} /> : ''
            }
            <Header
                themeSettings={theme.settings.Header} />
            <div className='container-fluid'>
                <div className='row'>
                    {
                        (isAdmin && inEditMode) ? <AdminPanel
                                                    className='admin-panel'
                                                    theme={theme} /> : ''
                    }
                    <MainContent
                        inEditMode={inEditMode} />
                </div>
            </div>
        </div>
    );
};

export default App;

. / Компоненты / админпанель

import React from 'react';
import Loadable from 'react-loadable';

import './styles.css';

const AdminPanel = ({ theme }) => {
    const ThemedSideBar = Loadable({
        loader: () => import('../../themes/' + theme.name +  '/components/SideBar'),
        loading: () => null
    });

    return (
        <div className='col-sm-3 col-md-2 sidebar'>
            <ThemedSideBar
                settings={theme.settings} />
        </div>
    );
};

export default AdminPanel;

Вот так выглядят мои <ThemedSideBar /> компоненты:

. / Темы / Default / компоненты / SideBar

import React from 'react';

import ThemeSettingPanel from '../../../../components/ThemeSettingPanel';
import ThemeSetting from '../../../../containers/ThemeSetting';

import './styles.css';

const SideBar = ({ settings }) => {
    return (
        <ThemeSettingPanel
            name='Header'>
            <ThemeSetting
                name='Background Color'
                setting={settings.Header}
                type='text'
                parent='Header' />
            <ThemeSetting
                name='Height'
                setting={settings.Header}
                type='text'
                parent='Header' />
        </ThemeSettingPanel>
    );
};

export default SideBar;

. / Компоненты / ThemeSettingPanel

import React from 'react';
import { PanelGroup, Panel } from 'react-bootstrap';

const ThemeSettingPanel = ({ name, children }) => {
    return (
        <PanelGroup accordion id='sidebar-accordion-panelGroup'>
            <Panel>
                <Panel.Heading>
                    <Panel.Title toggle>{name}</Panel.Title>
                </Panel.Heading>
                <Panel.Body collapsible>
                    {children}
                </Panel.Body>
            </Panel>
        </PanelGroup>
    );
};

export default ThemeSettingPanel;

. / Контейнеры / ThemeSetting

import React, { Component } from 'react';
import { connect } from 'react-redux';

import { themeSettingChange } from '../App/actions';

import ThemeSetting from '../../components/ThemeSetting';

class ThemeSettingContainer extends Component {
    constructor(props) {
        super(props);

        this.handleOnChange = this.handleOnChange.bind(this);
    }

    handleOnChange(name, parent, value) {
        const payload = {
            name: name,
            parent,
            value: value
        };

        this.props.themeSettingChange(payload);
    }

    render() {
        return (
            <ThemeSetting
                name={this.props.name}
                setting={this.props.setting}
                parent={this.props.parent}
                type={this.props.type}
                handleOnChange={this.handleOnChange} />
        );
    }
}

//----Redux Mappings----//
const mapStateToProps = (state) => ({
});

const mapDispatchToProps = {
    themeSettingChange: (value) => themeSettingChange(value)
};

export default connect(mapStateToProps, mapDispatchToProps)(ThemeSettingContainer);

. / Компонент / ThemeSetting

import React from 'react';

import TextField from '../common/TextField';

import './styles.css';

const ThemeSetting = ({ name, setting, type, parent, handleOnChange }) => {
    return (
        <div className='row theme-setting'>
            <div className='col-xs-7'>
                {name}
            </div>
            <div className='col-xs-5'>
                {
                    generateField(type, setting, name, parent, handleOnChange)
                }
            </div>
        </div>
    );
};

function generateField(type, setting, name, parent, handleOnChange) {
    const value = setting ? setting[name] : '';

    switch (type) {
        case 'text':
            return <TextField
                        value={value}
                        name={name}
                        parent={parent}
                        handleOnChange={handleOnChange} />;
        default:
            break;
    }
}

export default ThemeSetting;

. / Компоненты / общий / TextField

import React from 'react';
import { FormControl } from 'react-bootstrap';

const TextField = ({ value, name, parent, handleOnChange }) => {
    return (
        <FormControl
            type='text'
            value={value}
            onChange={(e) => {
                handleOnChange(name, parent, e.target.value);
            }} />
    );
};

export default TextField;

Когда поле внутри моей админ-панели обновляется, происходит изменение состояния. Кажется, что это вызывает react-loadable для повторного рендеринга моих <ThemedSideBar /> компонентов, который разрушает мой ввод и создает новый с обновленным значением. У кого-нибудь еще была эта проблема? Есть ли способ остановить react-loadable от повторного рендеринга?

EDIT: Здесь - запрашиваемая ссылка на репо.

1 Ответ

0 голосов
/ 04 июля 2018

РЕДАКТИРОВАТЬ: Согласно разговору в комментариях, мои извинения, я неправильно прочитал вопрос. Ответ здесь обновлен (оригинальный ответ ниже обновленного ответа)

Обновленный ответ

Из анализа реактивно-загружаемых документов выясняется, что загружаемый HOC предназначен для вызова вне метода render. В вашем случае вы загружаете ThemedSideBar в методе рендеринга AdminPanel. Я подозреваю, что изменение вашего TextEdit ввода, переданное для обновления вашего состояния Redux, а затем переданное через цепочку компонентов, заставило React подумать о повторном рендеринге AdminPanel. Поскольку ваш вызов Loadable был внутри метода render (т.е. AdminPanel - это презентационный компонент), react-loadable представлял совершенно новый загруженный компонент каждый раз, когда React нажимал на этот путь кода. Таким образом, React считает, что ему необходимо уничтожить предыдущий компонент, чтобы соответствующим образом привести компоненты в соответствие с новыми реквизитами.

Это работает:

import React from 'react';
import Loadable from 'react-loadable';

import './styles.css';

const ThemedSideBar = Loadable({
  loader: () => import('../../themes/Default/components/SideBar'),
  loading: () => null
});

const AdminPanel = ({ theme }) => {
    return (
        <div className='col-sm-3 col-md-2 sidebar'>
            <ThemedSideBar
                settings={theme.settings} />
        </div>
    );
};

export default AdminPanel;

Оригинальный ответ

Похоже, ваша проблема связана с тем, как вы построили TextField, а не с react-loadable.

FormControl принимает value={value} и обработчик onChange в качестве реквизита. Это означает, что вы указали, что это контролируемый (в отличие от неконтролируемый ) компонент.

Если вы хотите, чтобы поле получало обновленное значение, когда пользователь вводит данные, вам нужно распространить изменение, пойманное вашим обработчиком onChange, и убедиться, что оно возвращается к значению в value={value} проп.

Прямо сейчас, похоже, что value всегда будет равно theme.settings.Height или тому подобному (который предположительно равен нулю / пуст).

Альтернативой было бы сделать этот FormControl неуправляемым компонентом, но я полагаю, вы не хотите этого делать.

...