React-Loadable 重新渲染导致输入失去焦点
React-Loadable re-rendering causing input to lose focus
我遇到一个问题,react-loadable
导致我的输入组件之一在状态更新后重新呈现并失去焦点。我已经做了一些挖掘,但找不到其他人遇到这个问题,所以我认为我在这里遗漏了一些东西。
我正在尝试使用 react-loadable
根据用户选择的主题将组件动态包含到我的应用程序中。这工作正常。
./components/App
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;
这是我的 <ThemedSideBar />
组件的样子:
./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
重新渲染我的 <ThemedSideBar />
组件,这会破坏我的输入并创建一个具有更新值的新组件。其他人遇到过这个问题吗?有没有办法阻止 react-loadable
重新渲染?
编辑:Here 是请求 link 到 repo。
编辑:根据评论中的对话,我很抱歉,我误读了这个问题。此处的答案已更新(原始答案在更新后的答案下方)
更新答案
从 react-loadable docs 来看,Loadable HOC 似乎打算在 render
方法之外调用。在您的情况下,您正在 AdminPanel
的渲染方法中加载 ThemedSideBar
。我怀疑你 TextEdit
的输入的变化,传递来更新你的 Redux 状态,然后通过组件链传回导致 React 考虑重新渲染 AdminPanel
。因为您对 Loadable
的调用是在 render
方法内部(即 AdminPanel
是一个展示组件),所以每次 React 命中该代码路径时 react-loadable
都会展示一个全新的加载组件.因此,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
处理程序作为 props。这意味着您已经指出它是一个受控(而不是 uncontrolled)组件。
如果您希望该字段在用户键入输入时采用更新的值,您需要传播您的 onChange
处理程序捕获的更改,并确保它被反馈到 value={value}
道具
现在,看起来 value
总是等于 theme.settings.Height
或类似的东西(大概是 null/empty)。
另一种方法是使 FormControl 成为不受控制的组件,但我猜你不想那样做。
我遇到一个问题,react-loadable
导致我的输入组件之一在状态更新后重新呈现并失去焦点。我已经做了一些挖掘,但找不到其他人遇到这个问题,所以我认为我在这里遗漏了一些东西。
我正在尝试使用 react-loadable
根据用户选择的主题将组件动态包含到我的应用程序中。这工作正常。
./components/App
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;
这是我的 <ThemedSideBar />
组件的样子:
./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
重新渲染我的 <ThemedSideBar />
组件,这会破坏我的输入并创建一个具有更新值的新组件。其他人遇到过这个问题吗?有没有办法阻止 react-loadable
重新渲染?
编辑:Here 是请求 link 到 repo。
编辑:根据评论中的对话,我很抱歉,我误读了这个问题。此处的答案已更新(原始答案在更新后的答案下方)
更新答案
从 react-loadable docs 来看,Loadable HOC 似乎打算在 render
方法之外调用。在您的情况下,您正在 AdminPanel
的渲染方法中加载 ThemedSideBar
。我怀疑你 TextEdit
的输入的变化,传递来更新你的 Redux 状态,然后通过组件链传回导致 React 考虑重新渲染 AdminPanel
。因为您对 Loadable
的调用是在 render
方法内部(即 AdminPanel
是一个展示组件),所以每次 React 命中该代码路径时 react-loadable
都会展示一个全新的加载组件.因此,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
处理程序作为 props。这意味着您已经指出它是一个受控(而不是 uncontrolled)组件。
如果您希望该字段在用户键入输入时采用更新的值,您需要传播您的 onChange
处理程序捕获的更改,并确保它被反馈到 value={value}
道具
现在,看起来 value
总是等于 theme.settings.Height
或类似的东西(大概是 null/empty)。
另一种方法是使 FormControl 成为不受控制的组件,但我猜你不想那样做。