reactjs - redux 表单和 material ui 框架——具有自动类型——和清除字段功能
reactjs - redux form and material ui framework -- with auto type - and clearing field functionality
我正在构建一个嵌套表单框架,它使用 redux 表单和 material ui 框架——我在这里构建了组件ui迄今为止 - https://codesandbox.io/s/heuristic-hopper-lzekw
我想做的是 - 将一些“动画”附加到字段 - 模仿输入 - 我已经通过一个小函数实现了这一点,该函数将获取初始文本并逐步处理字符 - 更新该字段初始值的属性。
现在我遇到的问题是——我需要在 textField 上创建一个 onClick——如果它是一个自动 typetext 字段——将值重置为空字符串——将这个 onclick 传递回父 shell -- 甚至返回到 typetext 函数来打破超时 --- 所以如果用户加载页面,他们会看到文本输入 - 但是 UI 功能改进 - 如果我在动画中间单击该字段- 我想要动画 stop/break,我想要清除字段。
我还想控制哪些字段应该被清除——所以在这种情况下——有一个参数——指示 onClickClear: true——以免破坏用户编辑个人资料的预填表格。
===没有 typetext 的沙箱——但是如何将这两个框架粘合在一起的良好基础
https://codesandbox.io/s/heuristic-hopper-lzekw?file=/src/Home.js
== 这是最新的自动输入沙箱
https://codesandbox.io/s/amazing-bell-z8nhf
var self = this;
typeAnimation(this.state.initial_search_term.search_term, 100, function(msg){
self.setState({
initial_search_term: {"search_term": msg}
});
});
我知道这不是您要找的答案,但最简单的方法是为占位符文本而不是主要输入文本设置动画。然后你就什么都不用担心了,不管用户的动作如何,都可以让动画播放出来。
Now the problem I have -- is I need to create an onClick on the
textField -- and if its an automated typetext field - reset the value
to an empty string -- pass this onclick back up to the parent shells
-- and even back up to the typetext function to break the timeout --- so if the user loads the page they see the text typing - but with UI
functionality improvements - if I click on the field during
mid-animation - I want the animation to stop/break, I want the field
to clear.
I want to have control over also which fields should be cleared -- so
in this case - have a param - that indicates onClickClear: true -- so
as not to break user edit profile pre-filled forms.
所有这些都可以通过使用字段的占位符来满足(尽管 typetext 不会停止,因为没有必要,因为用户的文本/预填充文本将隐藏占位符)。我唯一没有连接的是停止 Home
的 componentWillUnmount
上的 typetext,因为如果没有它,它会抛出警告消息,指出正在未安装的组件上调用 setState。
我不得不进行一些重构,因为 (toggleFieldVisibility
in FieldMaker.js
) and not updating this.state.fields
when new props are passed down as the state was only being set in the constructor 之类的东西存在一些问题。我也在 FieldMaker.js
中重命名了一些东西(在这种情况下主要是由于个人喜好)。
无论你怎么做,尝试从 props 派生状态肯定存在问题:You Probably Don't Need Derived State
运行代码:
https://codesandbox.io/s/busy-davinci-mk0dq?file=/src/Home.js
Home.js
state = {
initial_search_term: { search_term: "" },
searchPlaceholder: "",
textPlaceholder: "",
valPlaceholder: ""
};
componentDidMount() {
typeAnimation("Search Text...", 100, (msg) => {
this.setState({
searchPlaceholder: msg
});
});
typeAnimation("Just some super long text you used to know", 100, (msg) => {
this.setState({
textPlaceholder: msg
});
});
typeAnimation("I'm a value, but am I valuable??", 100, (msg) => {
this.setState({
valPlaceholder: msg
});
});
}
// Render funct:
let fieldsSearchForm = [
{
type: "text",
label: "Search Term",
name: ["search_term"],
props: { placeholder: this.state.searchPlaceholder },
options: []
},
{
type: "text",
label: "Text",
name: ["test"],
props: { placeholder: this.state.textPlaceholder },
options: []
},
{
type: "text",
label: "Value",
name: ["test2"],
props: { placeholder: this.state.valPlaceholder }
}
];
FieldMaker.js
getDerivedStateFromProps 是这里真正的主要区别,这是在字段更改时根据字段填充 subs 数组(并设置可见性)。我不知道其中有多少是真正必要的,因为没有关于其中 any 实际上应该做什么的概念。所以它可能需要更多的工作才能让它完全正常工作。
另一个区别是在状态中有一个单独的 visiblity
对象而不是修改状态中的 fields
。
修改此文件的主要原因是确保对 fields
属性的更新转换为对子 Fields
的更新,以便占位符可以通过属性传递给 Field
因此 renderTextField
state = {
visibility: {}
};
static getDerivedStateFromProps(props, state) {
let newState = { prevFields: props.fields };
if (props.fields !== state.prevFields) {
let visibility = state.visibility;
let subs = props.fields.reduce((subs, field) => {
if (field.sub) {
subs.push(field.sub);
visibility[field.name] = false;
} else {
visibility[field.name] = true;
}
return subs;
}, []);
newState.subs = subs;
}
return newState;
}
toggleFieldVisibility(pos, isVisibile) {
let field = this.props.fields[pos].name;
this.setState((prev) => {
return { ...prev, [field]: isVisibile };
});
// This directly manipulates state, and is likely problematic in React
// let fields = { ...this.state.fields };
// fields[pos]["visibility"] = isVisibile;
}
componentDidMount() {
this.hideSubs();
}
// In render:
return (
<>
{this.props.fields.map((item, j) => {
if (this.state.visibility[item.name]) {
if (item.type !== "checkbox") {
return (
<Field
key={j}
name={item.name[0]}
props={item.props}
label={item.label}
// ...
renderTextField.js
在此,更改的目的只是将占位符向下传递给MUI TextField,并通过设置InputLabelProps = {shrink: true}
使MUI TextField的标签收缩
const renderTextField = ({
input,
rows,
multiline,
label,
type,
meta: { touched, error, warning },
placeholder,
InputLabelProps
}) => {
// Ensure that the label is shrunk to the top of the input
// whenever there's a placeholder set
InputLabelProps = placeholder
? { ...(InputLabelProps ?? {}), shrink: true }
: InputLabelProps;
return (
<FormControl
component="fieldset"
fullWidth={true}
className={multiline === true ? "has-multiline" : null}
>
<TextField
InputLabelProps={InputLabelProps}
placeholder={placeholder}
label={label}
multiline={multiline}
rows={rows}
type={type}
error={touched && (error && error.length > 0 ? true : false)}
helperText={
touched &&
((error && error.length > 0 ? error : null) ||
(warning && warning.length > 0 ? warning : null))
}
{...input}
/>
</FormControl>
);
};
我以非常快速和肮脏的方式重做了解决方案,以避免 FieldMaker 文件中存在的陷阱,这些陷阱最初导致原始解决方案出现问题:
https://codesandbox.io/s/fervent-moser-0qtvu?file=/src/Home.js
我修改了 typeAnimation 以通过返回停止循环的取消函数来支持类似的取消,并使用回调将值设置为结束状态。
export function typeAnimation(text, timing, callback) {
let concatStr = "";
let canceled = false;
function cancel() {
canceled = true;
}
async function runAnimation() {
for (const char of text) {
concatStr += char;
await sleep(timing);
if (canceled) {
break;
}
callback(concatStr);
}
if (canceled) {
callback(text);
}
}
runAnimation();
return cancel;
}
然后在 Home.js
中,我修改了初始状态和 componentDidMount 以使用占位符并给我一个存储取消函数的位置。
constructor(props, context) {
super(props, context);
this.state = {
initial_search_term: { search_term: "" },
placeholders: { search_term: "" }
};
}
cancelAnimations = {};
componentDidMount() {
var self = this;
this.cancelAnimations.search_term = typeAnimation(
"Start typing...",
100,
function (msg) {
self.setState((state) => ({
placeholders: { ...state.placeholders, search_term: msg }
}));
}
);
}
我还添加了 fieldsExtras
并将其一直传递到 FieldMaker 组件,以通过与 fieldsSearchForm
数组匹配的索引向该组件中的 Field 添加额外的道具。
let fieldsExtras = [
{
placeholder: this.state.placeholders.search_term,
onClick: this.cancelAnimations.search_term
}
];
然后,一旦额外的道具一直向下传递到字段中,在 renderTextField
中,我会做与以前相同的事情,但我还添加了 onClick 来调用传入的 onClick
函数
const renderTextField = ({
input,
rows,
multiline,
label,
type,
meta: { touched, error, warning },
placeholder,
onClick,
InputLabelProps
}) => {
InputLabelProps = placeholder
? { ...(InputLabelProps ?? {}), shrink: true }
: InputLabelProps;
return (
<FormControl
component="fieldset"
fullWidth={true}
className={multiline === true ? "has-multiline" : null}
>
<TextField
placeholder={placeholder}
InputLabelProps={InputLabelProps}
onClick={(e, value) => {
onClick && onClick(e);
}}
label={label}
multiline={multiline}
rows={rows}
type={type}
error={touched && (error && error.length > 0 ? true : false)}
helperText={
touched &&
((error && error.length > 0 ? error : null) ||
(warning && warning.length > 0 ? warning : null))
}
{...input}
/>
</FormControl>
);
};
我认为使用输入引用更新占位符 属性 是一个很好的解决方案,这样你就不需要更新输入值(避免组件重新渲染),并且你可以清除占位符点击事件的文本:
Home.js
class Home extends Component {
constructor(props, context) {
super(props, context);
this.searchInputRef = React.createRef(null);
this.state = { initial_search_term: { search_term: "" } };
}
componentDidMount() {
var self = this;
typeAnimation("Start typing...", 100, function (msg) {
if (document.activeElement !== self.searchInputRef.current) {
self.searchInputRef.current.setAttribute("placeholder", msg);
} else {
return true; // stop typings
}
});
}
render() {
//...
let fieldsSearchForm = [
{
id: "search-field",
type: "text",
label: "Search Term",
name: ["search_term"],
options: [],
fieldRef: this.searchInputRef,
onClick: () => (this.searchInputRef.current.placeholder = "")
}
];
//...
}
}
FieldMaker.js
class FieldMaker extends Component {
//...
render() {
return (
<>
{this.state.fields.map((item, j) => {
if (item.visibility) {
if (item.type !== "checkbox") {
return (
<Field
id={item.id}
//...other props
fieldRef={item.fieldRef}
onClick={item.onClick}
/>
);
} else {
//...
}
} else {
//...
}
})}
</>
);
}
}
renderTextField.js
const renderTextField = ({
id,
input,
rows,
multiline,
label,
type,
meta: { touched, error, warning },
onClick,
fieldRef
}) => (
<FormControl
component="fieldset"
fullWidth={true}
className={multiline === true ? "has-multiline" : null}
>
<TextField
id={id}
inputRef={fieldRef}
onClick={onClick}
// other props
/>
</FormControl>
);
Utility.js
export async function typeAnimation(text, timing, callback) {
let concatStr = "";
for (const char of text) {
concatStr += char;
await sleep(timing);
const shouldStop = callback(concatStr);
if (shouldStop) break; // stop the loop
}
}
styles.css
// 保持占位符可见
#search-field-label {
transform: translate(0, 1.5px) scale(0.75);
transform-origin: top left;
}
#search-field::-webkit-input-placeholder {
opacity: 1 !important;
}
我正在构建一个嵌套表单框架,它使用 redux 表单和 material ui 框架——我在这里构建了组件ui迄今为止 - https://codesandbox.io/s/heuristic-hopper-lzekw
我想做的是 - 将一些“动画”附加到字段 - 模仿输入 - 我已经通过一个小函数实现了这一点,该函数将获取初始文本并逐步处理字符 - 更新该字段初始值的属性。
现在我遇到的问题是——我需要在 textField 上创建一个 onClick——如果它是一个自动 typetext 字段——将值重置为空字符串——将这个 onclick 传递回父 shell -- 甚至返回到 typetext 函数来打破超时 --- 所以如果用户加载页面,他们会看到文本输入 - 但是 UI 功能改进 - 如果我在动画中间单击该字段- 我想要动画 stop/break,我想要清除字段。
我还想控制哪些字段应该被清除——所以在这种情况下——有一个参数——指示 onClickClear: true——以免破坏用户编辑个人资料的预填表格。
===没有 typetext 的沙箱——但是如何将这两个框架粘合在一起的良好基础 https://codesandbox.io/s/heuristic-hopper-lzekw?file=/src/Home.js
== 这是最新的自动输入沙箱 https://codesandbox.io/s/amazing-bell-z8nhf
var self = this;
typeAnimation(this.state.initial_search_term.search_term, 100, function(msg){
self.setState({
initial_search_term: {"search_term": msg}
});
});
我知道这不是您要找的答案,但最简单的方法是为占位符文本而不是主要输入文本设置动画。然后你就什么都不用担心了,不管用户的动作如何,都可以让动画播放出来。
Now the problem I have -- is I need to create an onClick on the textField -- and if its an automated typetext field - reset the value to an empty string -- pass this onclick back up to the parent shells -- and even back up to the typetext function to break the timeout --- so if the user loads the page they see the text typing - but with UI functionality improvements - if I click on the field during mid-animation - I want the animation to stop/break, I want the field to clear.
I want to have control over also which fields should be cleared -- so in this case - have a param - that indicates onClickClear: true -- so as not to break user edit profile pre-filled forms.
所有这些都可以通过使用字段的占位符来满足(尽管 typetext 不会停止,因为没有必要,因为用户的文本/预填充文本将隐藏占位符)。我唯一没有连接的是停止 Home
的 componentWillUnmount
上的 typetext,因为如果没有它,它会抛出警告消息,指出正在未安装的组件上调用 setState。
我不得不进行一些重构,因为 toggleFieldVisibility
in FieldMaker.js
) and not updating this.state.fields
when new props are passed down as the state was only being set in the constructor 之类的东西存在一些问题。我也在 FieldMaker.js
中重命名了一些东西(在这种情况下主要是由于个人喜好)。
无论你怎么做,尝试从 props 派生状态肯定存在问题:You Probably Don't Need Derived State
运行代码:
https://codesandbox.io/s/busy-davinci-mk0dq?file=/src/Home.js
Home.js
state = {
initial_search_term: { search_term: "" },
searchPlaceholder: "",
textPlaceholder: "",
valPlaceholder: ""
};
componentDidMount() {
typeAnimation("Search Text...", 100, (msg) => {
this.setState({
searchPlaceholder: msg
});
});
typeAnimation("Just some super long text you used to know", 100, (msg) => {
this.setState({
textPlaceholder: msg
});
});
typeAnimation("I'm a value, but am I valuable??", 100, (msg) => {
this.setState({
valPlaceholder: msg
});
});
}
// Render funct:
let fieldsSearchForm = [
{
type: "text",
label: "Search Term",
name: ["search_term"],
props: { placeholder: this.state.searchPlaceholder },
options: []
},
{
type: "text",
label: "Text",
name: ["test"],
props: { placeholder: this.state.textPlaceholder },
options: []
},
{
type: "text",
label: "Value",
name: ["test2"],
props: { placeholder: this.state.valPlaceholder }
}
];
FieldMaker.js
getDerivedStateFromProps 是这里真正的主要区别,这是在字段更改时根据字段填充 subs 数组(并设置可见性)。我不知道其中有多少是真正必要的,因为没有关于其中 any 实际上应该做什么的概念。所以它可能需要更多的工作才能让它完全正常工作。
另一个区别是在状态中有一个单独的 visiblity
对象而不是修改状态中的 fields
。
修改此文件的主要原因是确保对 fields
属性的更新转换为对子 Fields
的更新,以便占位符可以通过属性传递给 Field
因此 renderTextField
state = {
visibility: {}
};
static getDerivedStateFromProps(props, state) {
let newState = { prevFields: props.fields };
if (props.fields !== state.prevFields) {
let visibility = state.visibility;
let subs = props.fields.reduce((subs, field) => {
if (field.sub) {
subs.push(field.sub);
visibility[field.name] = false;
} else {
visibility[field.name] = true;
}
return subs;
}, []);
newState.subs = subs;
}
return newState;
}
toggleFieldVisibility(pos, isVisibile) {
let field = this.props.fields[pos].name;
this.setState((prev) => {
return { ...prev, [field]: isVisibile };
});
// This directly manipulates state, and is likely problematic in React
// let fields = { ...this.state.fields };
// fields[pos]["visibility"] = isVisibile;
}
componentDidMount() {
this.hideSubs();
}
// In render:
return (
<>
{this.props.fields.map((item, j) => {
if (this.state.visibility[item.name]) {
if (item.type !== "checkbox") {
return (
<Field
key={j}
name={item.name[0]}
props={item.props}
label={item.label}
// ...
renderTextField.js
在此,更改的目的只是将占位符向下传递给MUI TextField,并通过设置InputLabelProps = {shrink: true}
const renderTextField = ({
input,
rows,
multiline,
label,
type,
meta: { touched, error, warning },
placeholder,
InputLabelProps
}) => {
// Ensure that the label is shrunk to the top of the input
// whenever there's a placeholder set
InputLabelProps = placeholder
? { ...(InputLabelProps ?? {}), shrink: true }
: InputLabelProps;
return (
<FormControl
component="fieldset"
fullWidth={true}
className={multiline === true ? "has-multiline" : null}
>
<TextField
InputLabelProps={InputLabelProps}
placeholder={placeholder}
label={label}
multiline={multiline}
rows={rows}
type={type}
error={touched && (error && error.length > 0 ? true : false)}
helperText={
touched &&
((error && error.length > 0 ? error : null) ||
(warning && warning.length > 0 ? warning : null))
}
{...input}
/>
</FormControl>
);
};
我以非常快速和肮脏的方式重做了解决方案,以避免 FieldMaker 文件中存在的陷阱,这些陷阱最初导致原始解决方案出现问题:
https://codesandbox.io/s/fervent-moser-0qtvu?file=/src/Home.js
我修改了 typeAnimation 以通过返回停止循环的取消函数来支持类似的取消,并使用回调将值设置为结束状态。
export function typeAnimation(text, timing, callback) {
let concatStr = "";
let canceled = false;
function cancel() {
canceled = true;
}
async function runAnimation() {
for (const char of text) {
concatStr += char;
await sleep(timing);
if (canceled) {
break;
}
callback(concatStr);
}
if (canceled) {
callback(text);
}
}
runAnimation();
return cancel;
}
然后在 Home.js
中,我修改了初始状态和 componentDidMount 以使用占位符并给我一个存储取消函数的位置。
constructor(props, context) {
super(props, context);
this.state = {
initial_search_term: { search_term: "" },
placeholders: { search_term: "" }
};
}
cancelAnimations = {};
componentDidMount() {
var self = this;
this.cancelAnimations.search_term = typeAnimation(
"Start typing...",
100,
function (msg) {
self.setState((state) => ({
placeholders: { ...state.placeholders, search_term: msg }
}));
}
);
}
我还添加了 fieldsExtras
并将其一直传递到 FieldMaker 组件,以通过与 fieldsSearchForm
数组匹配的索引向该组件中的 Field 添加额外的道具。
let fieldsExtras = [
{
placeholder: this.state.placeholders.search_term,
onClick: this.cancelAnimations.search_term
}
];
然后,一旦额外的道具一直向下传递到字段中,在 renderTextField
中,我会做与以前相同的事情,但我还添加了 onClick 来调用传入的 onClick
函数
const renderTextField = ({
input,
rows,
multiline,
label,
type,
meta: { touched, error, warning },
placeholder,
onClick,
InputLabelProps
}) => {
InputLabelProps = placeholder
? { ...(InputLabelProps ?? {}), shrink: true }
: InputLabelProps;
return (
<FormControl
component="fieldset"
fullWidth={true}
className={multiline === true ? "has-multiline" : null}
>
<TextField
placeholder={placeholder}
InputLabelProps={InputLabelProps}
onClick={(e, value) => {
onClick && onClick(e);
}}
label={label}
multiline={multiline}
rows={rows}
type={type}
error={touched && (error && error.length > 0 ? true : false)}
helperText={
touched &&
((error && error.length > 0 ? error : null) ||
(warning && warning.length > 0 ? warning : null))
}
{...input}
/>
</FormControl>
);
};
我认为使用输入引用更新占位符 属性 是一个很好的解决方案,这样你就不需要更新输入值(避免组件重新渲染),并且你可以清除占位符点击事件的文本:
Home.js
class Home extends Component {
constructor(props, context) {
super(props, context);
this.searchInputRef = React.createRef(null);
this.state = { initial_search_term: { search_term: "" } };
}
componentDidMount() {
var self = this;
typeAnimation("Start typing...", 100, function (msg) {
if (document.activeElement !== self.searchInputRef.current) {
self.searchInputRef.current.setAttribute("placeholder", msg);
} else {
return true; // stop typings
}
});
}
render() {
//...
let fieldsSearchForm = [
{
id: "search-field",
type: "text",
label: "Search Term",
name: ["search_term"],
options: [],
fieldRef: this.searchInputRef,
onClick: () => (this.searchInputRef.current.placeholder = "")
}
];
//...
}
}
FieldMaker.js
class FieldMaker extends Component {
//...
render() {
return (
<>
{this.state.fields.map((item, j) => {
if (item.visibility) {
if (item.type !== "checkbox") {
return (
<Field
id={item.id}
//...other props
fieldRef={item.fieldRef}
onClick={item.onClick}
/>
);
} else {
//...
}
} else {
//...
}
})}
</>
);
}
}
renderTextField.js
const renderTextField = ({
id,
input,
rows,
multiline,
label,
type,
meta: { touched, error, warning },
onClick,
fieldRef
}) => (
<FormControl
component="fieldset"
fullWidth={true}
className={multiline === true ? "has-multiline" : null}
>
<TextField
id={id}
inputRef={fieldRef}
onClick={onClick}
// other props
/>
</FormControl>
);
Utility.js
export async function typeAnimation(text, timing, callback) {
let concatStr = "";
for (const char of text) {
concatStr += char;
await sleep(timing);
const shouldStop = callback(concatStr);
if (shouldStop) break; // stop the loop
}
}
styles.css // 保持占位符可见
#search-field-label {
transform: translate(0, 1.5px) scale(0.75);
transform-origin: top left;
}
#search-field::-webkit-input-placeholder {
opacity: 1 !important;
}