How can I change the focus to description field in TaskCard component, after enter key be pressed on InputField component?
That's the father component
export default class Task extends Component {
constructor(props) {
super(props)
this.state = { cardID: '', list: [] }
this.handleAdd = this.handleAdd.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleEdit = this.handleEdit.bind(this)
this.handleRemove = this.handleRemove.bind(this)
this.refresh()
}
handleAdd = (event, cardID) => {
...
}
handleChange(e) {
...
}
handleEdit = (e, task) => {
task.description = e.target.value
axios.put(`${URL}/${task._id}`, task)
.then(resp => this.refresh(this.state.description))
}
refresh() {
...
}
handleRemove = task => {
...
}
render() {
return (
<Fragment>
<Row className="card-columns">
<taskCard title="Task Board" cardID='1' color='blue-violet'
handleAdd={this.handleAdd}
handleChange={this.handleChange}
handleEdit={this.handleEdit}
description={this.state.description}
list={this.state.list}
handleRemove={this.handleRemove}
/>
</Fragment >
)
}
}
The TaskCard component contains the description input field. That's the field who needs to receive the focus:
const TaskCard = props => {
const renderRows = () => {
const list = props.list || []
return list.map(task => (
task.cardID === props.cardID &&
<InputField task={task}
handleChange={props.handleChange}
handleEdit={props.handleEdit}
handleRemove={props.handleRemove}
/>
))
}
return (
<Fragment>
{renderRows()}
<Form>
{/* input who needs to receive focus */}
<Form.Control className='input-field description' name='description' type="text" placeholder="Insert a task"
onChange={props.handleChange}
onKeyPress={event => {
if (event.key === 'Enter') {
props.handleAdd(event, props.cardID)
event.target.value= ''
event.preventDefault()
}
}}
/>
</Form>
</Fragment >
)
}
export default TaskCard
Input field component is where the user edit a task and press Enter. When Enter is pressed, the task is modified and I'd like to focus on description field in TaskCard component
const InputField = props => {
return (
<Fragment>
<Row>
<Col xs={12} style={{paddingRight: 0}}>
<Form>
<Form.Group controlId="formBasicEmail">
<Form.Control className='inputs-field' type="text" name={props.task._id}
placeholder={props.task.description}
onChange={props.handleChange}
onKeyPress={event => {
if (event.key === 'Enter') {
props.handleEdit(event, props.task)
event.preventDefault()
}
}}
/>
</Form.Group>
</Form>
</Col>
</Row>
</Fragment >
)
}
export default InputField
Use refs to get a reference to the input in TaskCard and pass it to the input you want to focus:
const TaskCard = props => {
// useRef here
const inputRef = useRef(null)
const renderRows = () => {
const list = props.list || []
return list.map(task => (
task.cardID === props.cardID &&
<InputField task={task}
handleChange={props.handleChange}
handleEdit={props.handleEdit}
handleRemove={props.handleRemove}
/>
))
}
return (
<Fragment>
{renderRows()}
<Form>
{/* input who needs to receive focus */}
...
// pass ref here
ref={inputRef}
}}
/>
</Form>
</Fragment >
)
}
export default TaskCard
Then in the <InputField /> Component pass the ref:
<InputField task={task}
handleChange={props.handleChange}
handleEdit={props.handleEdit}
handleRemove={props.handleRemove}
// pass the ref down
inputRef={inputRef}
/>
Then from within the <InputField /> component call it where ever you call the handleEdit function like this:
() => {
props.handleEdit()
props.inputRef.current.focus()
}
Related
What I need is to be able to customize textAreaCount and other props in each separate instance of <Texarea/>. The textAreaCount is different for each <Texarea/> so how do I modify the component to be able to pass in custom textAreaCount for each <Texarea/>?
https://codesandbox.io/s/rkv88-forked-vjo0rn?file=/src/App.js:0-948
import React, { useState } from "react";
const Textarea = (value, id, maxLength, textAreaLimit) => {
const [textAreaCount, ChangeTextAreaCount] = React.useState(0);
const [state, setstate] = useState({
headline: "",
title: ""
});
const { headline, title } = state;
const changevalue = (e) => {
setstate({
...state,
[e.target.name]: value
});
ChangeTextAreaCount(e.target.value.length);
};
return (
<>
<p>{textAreaCount}/{textAreaLimit}</p>
<textarea
type="text"
rows={5}
id={id}
value={value}
maxLength={maxLength}
onChange={(e) => {
changevalue(e);
}}
/>
</>
);
};
export default function FullWidthTabs() {
return (
<div>
<Textarea value={headline} id="test" maxLength={5} textAreaLimit={5}/>
<Textarea value={title} id="test2" maxLength={10} textAreaLimit={10}/>
</div>
);
}
Forward the props you need.
const Textarea = (props) => {
const [textAreaCount, setTextAreaCount] = React.useState(0);
const recalculate = (e) => {
setTextAreaCount(e.target.value.length);
};
return (
<>
<p>{textAreaCount}/5</p>
<textarea type="text" rows={5} maxLength={5} onChange={recalculate} {...props} />
</>
);
};
Now it will forward any props into the textarea element. This will set the id and will overwrite the rows prop.
<Textarea id="textarea-1" rows={4} />
<Textarea id="textarea-2" rows={5} maxLength={10} />
As we can see you try to pass props as below:
<Textarea value={title} id="test2" maxLength={10} textAreaLimit={10}/>
But In Your Textarea Component you received props argument as multiple args as below:
const Textarea = (value, id, maxLength, textAreaLimit) => {
return (
<>
</>
);
};
Instead that you need to destruct your props argument or you can set whole passed value props as single object props as below:
Method 1:
const Textarea = ({value, id, maxLength, textAreaLimit}) => {
return (
<>
<textarea type="text" id={id} value={value} rows={5} maxLength={maxLength} onChange={recalculate} textAreaLimit={textAreaLimit} />
</>
);
};
Method 2:
const Textarea = ({...props}) => {
return (
<>
<textarea type="text" id={id} value={value} rows={5} maxLength={maxLength} onChange={recalculate} textAreaLimit={textAreaLimit} />
</>
);
};
Method 3:
const Textarea = (props) => {
return (
<>
<textarea type="text" id={props.id} value={props.value} rows={5} maxLength={props.maxLength} onChange={recalculate} textAreaLimit={props.textAreaLimit} />
// Or Instead you can do as below
// <textarea type="text" rows={5} maxLength={5} onChange={recalculate} {...props} />
</>
);
};
I have a textField which are be controlled by a MobX store, but if I enter something to the textfield only on letter will change. Here my code. There is my mistake?
I made my component as observer and my Store is makeAutoObservable inside the constructor.
class SettingsStore {
constructor() {
makeAutoObservable(this)
makePersistable(this, {
name: 'SampleStore',
properties: ['roadmapDescription'],
storage: window.localStorage
})
}
async onChangeRoadmapTitle(name: string): Promise<void> {
setTimeout(() => {
console.log('input', name)
this.roadmapDescription.title = name
console.log(
'this.roadmapDescription.title',
this.roadmapDescription.title
)
}, 50)
}
}
Here is my React Code
const SettingsGeneral: React.FC<ISettingsGeneral> = observer(({ onSubmit }) => {
return (
<div>
<Form<{ title: string; description: string; dataSource: string }>
onSubmit={(data) => onSubmit(data)}
>
{({ formProps = 'test', submitting }) => (
<form {...formProps}>
<FormSection>
<Field
aria-required={true}
name='title'
label='Name of the Roadmap'
isRequired
>
{({ fieldProps }) => (
<Fragment>
<TextField
testId='roadmap-title-text-field'
autoComplete='off'
{...fieldProps}
className='w-48'
onChange={async (
e: React.ChangeEvent<HTMLInputElement>
) =>
await settingsStore.onChangeRoadmapTitle(e.target.value)
}
value={settingsStore.roadmapDescription.title}
/>
</Fragment>
)}
</Field>
</form>
)}
</Form>
</div>
)
})
The mistake was I use value and not defaultVaule
Correct solution is:
<Fragment>
<TextField
testId='roadmap-title-text-field'
autoComplete='off'
{...fieldProps}
className='w-48'
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
settingsStore.onChangeRoadmapTitle(e.target.value)
}
defaultValue={settingsStore.roadmapDescription.title}
/>
</Fragment>
Trying to get this form field component to take a simple-react-code-editor:
not sure if I'm going about this the right way by trying to pass props form the useField hook, but it works for textfield tags, so thought the same method could apply to this as well. Although, I get the feeling the onValueChange callback is different from the onChange callback that this component doesn't have. Is there a way to add it somehow?
Editor Component:
const MyEditor = ({value, onChange}) => {
const highlight = (value) => {
return(
<Highlight {...defaultProps} theme={theme} code={value} language="sql">
{({ tokens, getLineProps, getTokenProps }) => (
<>
{tokens.map((line, i) => (
<div {...getLineProps({ line, key: i })}>
{line.map((token, key) => (
<span {...getTokenProps({ token, key })} />
))}
</div>
))}
</>
)}
</Highlight>
)
};
return (
<Editor
value={value}
onValueChange={onChange}
highlight={highlight}
padding={'40px'}
style={styles.root}
/>
);
}
export default MyEditor;
Form with Field Component as MyEditor (tried using useField hook):
const FormQueryTextBox = ({...props}) => {
const [field] = useField(props);
return (
<MyEditor onChange={field.onChange} value={field.value}/>
)
}
const validationSchema = yup.object({
query_name: yup
.string()
.required()
.max(50)
});
const AddQueryForm = () => {
return (
<div>
<Formik
validateOnChange={true}
initialValues={{
query:""
}}
validationSchema={validationSchema}
onSubmit={(data, { setSubmitting }) => {
console.log(data);
}}
>
{() => (
<Form>
<div>
<Field
placeholder="query name"
name="query_name"
type="input"
as={TextField}
/>
</div>
<div>
<Field
name="query"
type="input"
as={FormQueryTextBox}
/>
</div>
</Form>
)}
</Formik>
</div>
)
}
components render without errors, but as I type the text doesn't appear.
I figured out that I just need to customize my onChange with the setter from the useFormikContext hook like this:
const FormQueryTextBox = ({...props}) => {
const [field] = useField(props);
const { setFieldValue } = useFormikContext();
return (
<MyEditor {...field} {...props} onChange={val => {
setFieldValue(field.name, val)
}}/>
)
}
The Error Message:
If i dont use the Inputs inside div then it works perfectly but when i use Input inside div it shows me this error.
I wanted to keep the hook related stuff separated so it look clean.
why does it only works when its not inside a div?
Login.tsx
import { useHistory } from "react-router-dom";
import { useForm } from "react-hook-form";
import useAuth from "./../hooks/useAuth";
import { Form, Input } from "../components/FormGroup";
import MacNav from "../components/MacNav";
import { loginActionUrl } from "./../services/ApiLinks";
import {
fetchPostResopnse,
successPopUp,
errorPopUp,
} from "./../services/FormHelper";
type Tinputs = {
username: string;
password: string;
};
function Login() {
const auth = useAuth();
const history = useHistory();
const methods = useForm<Tinputs>();
const onSubmit = async (data: Tinputs) => {
const result = await fetchPostResopnse(loginActionUrl, data);
if (result.isAuth) {
successPopUp("Credentials Matched", () => {
auth.signIn(result);
history.push("/admin/dashboard");
});
} else {
errorPopUp("Credentials Does Not Matched");
}
};
return (
<div>
<MacNav />
<div className="section-secondary">
<div className="container">
<div className="contact-form-wrapper">
<div className="title-lg text-center">Enter Your Credentials</div>
<Form formMethods={methods} handler={onSubmit} submitBtn="Submit">
{/*If i dont use Input inside div it works*/}
<div>
<Input name="username" rule={{ required: true }} />
</div>
<Input name="password" rule={{ required: true }} />
</Form>
</div>
</div>
</div>
</div>
);
}
export default Login;
I have wrote the form components here.
FormGroup.tsx
import React from "react";
const Form = ({ children, formMethods, handler, submitBtn }: any) => {
return (
<form onSubmit={formMethods.handleSubmit(handler)}>
{React.Children.map(children, (child) => {
return child.props.name ? (
<div>
{React.createElement(child.type, {
...{
...child.props,
register: formMethods.register,
key: child.props.name,
},
})}
{child.props?.rule && formMethods.errors[child.props.name] && (
<div className="text-danger">
*
{formMethods.errors[child.props.name].message
? formMethods.errors[child.props.name].message
: `${child.props.name} is required`}
</div>
)}
</div>
) : (
child
);
})}
{submitBtn && <button type="submit">{submitBtn}</button>}
</form>
);
};
const Input = ({ register, name, label, rule, ...rest }: any) => {
label = label ? label : name?.charAt(0).toUpperCase() + name?.slice(1);
return (
<div>
<label htmlFor={name}>{label}</label>
<input name={name} ref={register(rule)} {...rest} />
</div>
);
};
const Textarea = ({ register, name, label, rule, ...rest }: any) => {
label = label ? label : name?.charAt(0).toUpperCase() + name?.slice(1);
return (
<div>
<label htmlFor={name}>{label}</label>
<textarea name={name} ref={register(rule)} {...rest}></textarea>
</div>
);
};
const SubmitButton = ({ name, ...rest }: any) => {
return (
<button type="submit" {...rest}>
{name}
</button>
);
};
export { Form, Input, Textarea, SubmitButton };
[1]: https://i.stack.imgur.com/PvEUA.png
Hello according to your code, what happened it's expected
the div doesn't have name so according to this code
{React.Children.map(children, (child) => {
return child.props.name ? (
<div>
{React.createElement(child.type, {
...{
...child.props,
register: formMethods.register,
key: child.props.name,
},
})}
{child.props?.rule && formMethods.errors[child.props.name] && (
<div className="text-danger">
*
{formMethods.errors[child.props.name].message
? formMethods.errors[child.props.name].message
: `${child.props.name} is required`}
</div>
)}
</div>
) : (
child
);
})}
And the below child
<div>
<Input name="username" rule={{ required: true }} />
/div>
The Input component will be rendrered without register prop, so when it will try to call it here, however it's value is undefined, what will cause an error
ref={register(rule)}
I suggest to create a new component
const InputWithDiv = (props) => (
<div>
<Input rule={{ required: true }} {..props} />
/div>
);
and use it like below
<Form formMethods={methods} handler={onSubmit} submitBtn="Submit">
<InputWithDiv name="username" />
<Input name="password" rule={{ required: true }} />
</Form>
Hi I am working on React JS application. I have assigned default value to textbox on page load. User should be allowed to change this but user is not able to edit the value in textbox. Below is my code.
const EditStyleFormComponent = ({
submitting,
invalid,
}) => (
<form className={className} onSubmit={handleSubmit}>
<h2>LSPL (Low Stock Presentation Level)</h2>
<Line />
<InputGroup>
<TextField value="Current" label="LSPL Manual" isEditable="true" />
</InputGroup>
</form>
);
Below is my TextField.js
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = _.uniqueId();
return (
<div className={className}>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{getStatusIcon(state)}
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
Can someone help me to fix this issue? Any help would be appreciated. Thanks
Using Uncontrolled input, you may use defaultValue
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = 1;
return (
<div>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
const EditStyleFormComponent = ({
submitting,
invalid,
}) => (
<form>
<h2>LSPL (Low Stock Presentation Level)</h2>
<TextField defaultValue="Current" label="LSPL Manual" isEditable="true" />
</form>
);
class Hello extends React.Component {
render() {
return <div><EditStyleFormComponent/></div>;
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
See the fiddle here https://jsfiddle.net/0f6n85ym/
Alternatively you can also do it in controlled input.
const TextField = ({
className,
label,
description,
state,
errorMessage,
isEditable,
spaceAtBottom, // Not used, but we don't want it in otherProps
...otherProps
}) => {
const inputId = 1;
return (
<div>
{label &&
<label htmlFor={inputId}>{label}</label>
}
<div className="input-group" id={isEditable ? 'editable' : 'readonly'}>
<input
id={inputId}
readOnly={!isEditable}
{...otherProps}
/>
{errorMessage &&
<Error>{errorMessage}</Error>
}
{description &&
<Description>{description}</Description>
}
</div>
</div>
);
};
const EditStyleFormComponent = ({
submitting,
invalid,
value,
onChange
}) => (
<form>
<h2>LSPL (Low Stock Presentation Level)</h2>
<TextField value={value} onChange={onChange} label="LSPL Manual" isEditable="true" />
</form>
);
class Hello extends React.Component {
constructor(props){
super(props);
this.state = {
name: 'Current'
}
}
onChange = (e)=>{
this.setState({name: e.target.value});
}
render() {
return <div><EditStyleFormComponent value={this.state.name} onChange={this.onChange}/></div>;
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
See the fiddle here
https://jsfiddle.net/bshumpy0/
You can use logical OR operator to set defaultValue if value is not provided(if you use controlled input)
Like that:
class App extends Component {
constructor() {
super();
this.state = {
value: ''
};
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<div>
<TextField value={this.state.value} defaultValue='213' onChange={this.handleChange} />
</div>
);
}
}
And in TextField component:
<input type='text' value={value || defaultValue} onChange={onChange}/ >
Full example - https://stackblitz.com/edit/react-4daxck