我正在构建一个使用redux表单和材料ui框架的嵌套表单框架-到目前为止,我已经在此处构建了组件-https://codesandbox.io/s/heuristic-hopper-lzekw
我想做的-将一些“动画”添加到字段中-模仿打字-我已经通过一个小功能实现了这一点,该功能将使用初始文本并逐步处理字符-更新道具该字段的初始值。
我现在遇到的问题是-我需要在textField上创建一个onClick-如果它是一个自动的typetext字段-将值重置为空字符串-将此onclick传递回父外壳-甚至备份到typetext函数来打破超时---因此,如果用户加载页面,他们会看到文本输入-但UI功能得到了改进-如果我在动画中期单击该字段-我希望动画能够停止/中断,我想清除该字段。
我还希望控制应清除哪些字段-在这种情况下-具有一个参数-表示onClickClear:true-以免破坏用户编辑配置文件的预填写表格。
===没有类型文本的沙盒-但是是如何将这两个框架粘合在一起的良好基础 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}
});
});
答案 0 :(得分:1)
我知道这不是您要查找的答案,但是最简单的方法是设置占位符文本的动画而不是主要输入文本。这样,您就不必担心任何事情,只要让动画播放就可以了,而与用户的操作无关。
现在的问题是-我需要在 textField-如果它是一个自动的typetext字段,则重置该值 到一个空字符串-将此onclick传递回父外壳 -甚至备份到typetext函数以打破超时--因此,如果用户加载页面,他们会看到文本输入-但使用UI 功能改进-如果我在 动画中期-我希望动画停止/中断,我希望该字段 清除。
我还想控制应该清除哪些字段-所以 在这种情况下-有一个参数-表示onClickClear:true-因此 以免破坏用户编辑个人资料的预填写表格。
所有这些都可以通过使用字段的占位符来满足(尽管输入文本不会停止,因为不需要,因为用户的文本/预填充文本会隐藏占位符)。我唯一没有想到的就是在Home
的{{1}}上停止输入文本,否则,它将抛出警告消息,提示已在未安装的组件上调用setState。
我必须进行一些重构,因为mutating React state({{1}中的componentWillUnmount
)存在一些问题,并且在新道具传递为state was only being set in the constructor。我还在toggleFieldVisibility
内重命名了某些内容(主要是由于个人偏好)。
无论您如何操作,尝试从道具中获取状态肯定存在问题:You Probably Don't Need Derived State
运行代码:
https://codesandbox.io/s/busy-davinci-mk0dq?file=/src/Home.js
Home.js
FieldMaker.js
FieldMaker.js
这里的getDerivedStateFromProps是真正的主要区别,这是每当字段更改时,便根据字段填充subs数组(并设置可见性)。我不知道其中有多少是真正必要的,因为对此没有任何想法。因此,它可能需要更多的工作才能完全发挥作用。
另一个区别是重构了一个单独的this.state.fields
对象,而不是修改FieldMaker.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 }
}
];
道具的更新转换为对子项visiblity
的更新,以便可以通过道具将占位符向下传递到{{1 }}和fields
fields
renderTextField.js
在这种情况下,更改的目的只是将占位符向下传递到MUI TextField,并通过设置Fields
Field
我以一种非常肮脏的方式重新解决了这个问题,避免了FieldMaker文件中存在的陷阱,这些陷阱最初会在原始解决方案中引起问题:
https://codesandbox.io/s/fervent-moser-0qtvu?file=/src/Home.js
我修改了typeAnimation,通过返回取消函数来支持某种取消效果,该函数停止循环并设置使用回调将值设置为结束状态。
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}
// ...
中,我修改了初始状态和componentDidMount以使用占位符,并给了我一个存储取消功能的位置。
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组件,以通过与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;
}
数组匹配的索引向该组件中的Field添加额外的道具。
Home.js
然后,一旦额外的道具一直传递到字段中,在 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 }
}));
}
);
}
中,我将执行与以前相同的操作,但是我还添加了onClick来调用传递的{{1 }}功能
fieldsExtras
答案 1 :(得分:1)
我认为使用input ref更新占位符属性是一个很好的解决方案,这样您就不需要更新输入值(避免重新渲染组件),并且可以在click事件中清除占位符文本:< / p>
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;
}