【问题标题】:React-Loadable re-rendering causing input to lose focusReact-Loadable 重新渲染导致输入失去焦点
【发布时间】:2018-12-12 07:25:12
【问题描述】:

我遇到了一个问题,react-loadable 导致我的一个输入组件在状态更新后重新渲染并失去焦点。我已经进行了一些挖掘,但找不到其他人有这个问题,所以我认为我在这里遗漏了一些东西。

我正在尝试使用react-loadable 根据用户选择的主题将组件动态包含到我的应用程序中。这工作正常。

./components/应用程序

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;

./components/AdminPanel

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;

这就是我的&lt;ThemedSideBar /&gt; 组件的样子:

./themes/Default/components/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;

./components/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;

./containers/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);

./component/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;

./components/common/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 重新渲染我的 &lt;ThemedSideBar /&gt; 组件,这会破坏我的输入并创建一个具有更新值的新组件。其他人遇到过这个问题吗?有没有办法阻止react-loadable 重新渲染?

编辑:Here 是请求的回购链接。

【问题讨论】:

    标签: javascript reactjs input rerender react-loadable


    【解决方案1】:

    编辑:根据 cmets 中的对话,我很抱歉,我误读了这个问题。这里的答案已更新(更新答案下方的原始答案)

    更新答案

    react-loadable docs 看来,Loadable HOC 似乎打算在render 方法之外调用。在您的情况下,您在AdminPanel 的渲染方法中加载ThemedSideBar。我怀疑 TextEdit 的输入中的更改,通过更新您的 Redux 状态,然后通过组件链传递回导致 React 考虑重新渲染 AdminPanel。因为您对Loadable 的调用是在render 方法内(即AdminPanel 是一个展示组件),所以react-loadable 每次React 访问该代码路径时都会展示一个全新的加载组件。因此,React 认为它需要销毁先前的组件以适当地使组件与新的 props 保持同步。

    这行得通:

    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

    FormControlvalue={value}onChange 处理程序作为道具。这意味着您已经表明它是一个受控(而不是uncontrolled)组件。

    如果您希望该字段在用户键入输入时采用更新的值,您需要传播 onChange 处理程序捕获的更改,并确保它被反馈到 value={value} 属性中的值。

    现在,value 看起来总是等于 theme.settings.Height 或类似的(可能是 null/空)。

    另一种方法是让 FormControl 成为不受控制的组件,但我猜你不想这样做。

    【讨论】:

    • 感谢您的回答!实际上,theme.settings.Height 最终会被我的 reducer 中的 onChange 方法更新。我知道这是有效的,因为重新渲染的组件反映了新的状态值。我怀疑react-loadable 的原因是,如果我静态加载组件,我就没有焦点问题。
    • 啊,对不起,我误读了“这会破坏我的输入并使用 updated 值创建一个新输入”。有时间我会回来的,可能是明天。
    • @NorianNyx 我尝试在本地重现您的问题 - 无法做到。当我设置一个 reducer 来适当地更新值时,一切正常(react-loadable 仍然存在)。你能分享一个完整的公共回购吗?
    • 感谢您的更新!我会将其标记为正确答案。我需要找到一种方法来规避这种情况,或者使用不同的库,因为我需要在此处访问主题道具以了解用户选择了哪个主题。感谢您的帮助。
    • 关于主题——我没有很多使用 CSS 和 React 的适当范例的经验,但我的直觉是,出于样式原因制作完全独立的组件库是一种反模式(如果这是您在“主题”背后的意图)。也许看看像 Material UI 这样的 UI 框架如何在他们的主题系统中处理这个问题以获得替代方法的想法,这应该有助于解决这个问题
    猜你喜欢
    • 2014-04-29
    • 2022-01-24
    • 2017-10-09
    • 2023-01-20
    • 1970-01-01
    • 2021-03-19
    • 2022-01-16
    • 2021-05-29
    • 2019-10-10
    相关资源
    最近更新 更多