I made a useInput hook that looks like this.
export function useInput({
type,
name,
placeholder,
initialValue,
helpText,
required,
onKeyUp,
errorMessage,
}: Props) {
const [value, setValue] = useState(initialValue)
const input =
type === 'textarea' ? (
<div className="form-group">
<label>{name}</label>
<span>{helpText}</span>
<textarea
name="email"
className="form-control"
required={required}
id={name}
value={value}
aria-describedby={name}
placeholder={placeholder}
onChange={(e) => setValue(e.target.value)}
/>
</div>
) : (
<div className="form-group">
<label>{name}</label>
<span>{helpText}</span>
<input
type={type}
name="email"
className="form-control"
id={name}
required={required}
value={value}
aria-describedby={name}
placeholder={placeholder}
onChange={(e) => setValue(e.target.value)}
onKeyUp={onKeyUp}
/>
<>{errorMessage}</>
</div>
)
return [value, input]
}
In my pages, I can simply use the hook, like below
const [description, descriptionInput] = useInput({
type: 'textarea',
name: 'Description',
helpText: <small className="muted"> (Optional)</small>,
placeholder: 'I found this interesting because...',
})
and inside the render function use them as {descriptionInput} and the value will be available in description.
However, I'm looking for ways to update the value programmatically - to be specific, to reset the value of inputs on form submission for example. All I have is a reference to the input, and the value itself. Is there a nice way to keep using the hook and reset the value at will?
You can simply expose a reset function from your hook which you can call to reset the state to either the initialValue or empty
export function useInput({
type,
name,
placeholder,
initialValue,
helpText,
required,
onKeyUp,
errorMessage,
}: Props) {
const [value, setValue] = useState(initialValue);
const resetInput = useCallback(() => {
setValue(initialValue || "" );
}, [])
const input =
type === 'textarea' ? (
<div className="form-group">
<label>{name}</label>
<span>{helpText}</span>
<textarea
name="email"
className="form-control"
required={required}
id={name}
value={value}
aria-describedby={name}
placeholder={placeholder}
onChange={(e) => setValue(e.target.value)}
/>
</div>
) : (
<div className="form-group">
<label>{name}</label>
<span>{helpText}</span>
<input
type={type}
name="email"
className="form-control"
id={name}
required={required}
value={value}
aria-describedby={name}
placeholder={placeholder}
onChange={(e) => setValue(e.target.value)}
onKeyUp={onKeyUp}
/>
<>{errorMessage}</>
</div>
)
return [value, input, resetInput]
}
and use it like
const [description, descriptionInput, resetDescriptionInput] = useInput({
type: 'textarea',
name: 'Description',
helpText: <small className="muted"> (Optional)</small>,
placeholder: 'I found this interesting because...',
})
P.S. However, since this hook is actually returning JSX content, you could write it as a component too and expose a function to be used by a ref with useImperativeHandle
Related
I am building a to-do list in react and when adding a new task to the list, the input value is not being cleared from the form after submit, I'm trying to achieve this using setInput('');
const Form = (props) => {
const [input, setInput] = useState('');
const handleChange = e => {
setInput(e.target.value);
}
const handleSubmit = e => {
e.preventDefault();
const newTask = {
id: uuidv4(),
text: input,
completed: false
};
props.onSubmit(newTask);
setInput('');
}
return (
<form
className='task-form'
>
<input
className='task-input'
type='text'
placeholder='Enter a task'
name='text'
onChange={handleChange}
/>
<button className='task-btn' onClick={handleSubmit}>Add Task</button>
</form>
)
}
export default Form
You aren't using the input state anywhere except when creating the newTask object. You need to pass it as a prop to the <input> component to make it fully controlled and for calls to the state setter to result in changes to the DOM.
<input
className='task-input'
type='text'
placeholder='Enter a task'
name='text'
onChange={handleChange}
/>
should be
<input
className='task-input'
type='text'
placeholder='Enter a task'
name='text'
onChange={handleChange}
value={input}
/>
I want to pass a state variable to another component but i don't get it what i'm doing wrong.
I have a component for radio inputs and I need to pass that taskLabel to Form component.
path is components/inputs/index.js
const TaskLabel = (props) => {
const [taskLabel, setTaskLabel] = useState('');
return (
<div label={props.taskLabel} >
<input
type='radio'
name='label'
value='urgent'
onChange={(e) => setTaskLabel(e.target.value)}
/>
<input
type='radio'
name='label'
value='not-urgent'
onChange={(e) => setTaskLabel(e.target.value)}
/>
</div>
);
};
i want to receive the taskLabel value, to use it in submitHandler function.
components/form/index.js
const Form = ({taskLabel}) => {
return (
<form onSubmit={submitHandler}>
<input
type='text'
placeholder='Text here'
className='form-input'
value={task}
onChange={(e) => {
setTask(e.target.value);
}}
/>
<TaskLabel taskLabel={taskLabel} />
</form>
)
}
This is what i tried, to pass taskLabel props from label={taskLabel}.
You need to move your state, to Form component, like this:
const [labelProp, setLabelProp] = useState("");
Your TaskLabel component should be
<TaskLabel label={{ labelProp, setLabelProp }} />
That means, you send label, as a prop to TaskLabel component.
In TaskLabel component you need to recive the prosp, by passing to component {label}.
Next, for every input use onChange={(e) => label.setLabelProp(e.target.value)}.
Edited sandbox => https://codesandbox.io/s/laughing-proskuriakova-dhi568?file=/src/components/task-label/index.js
Generally speaking, the concept is "data-down, actions-up". So if you want to pass data down to a lower-level component, you just pass a prop. If you want to update a value from a lower-level component to a higher-level component, you could pass a setter function down as a prop and call that.
Note I just call it taskLevelProp for a little more clarity. You should probably use a better name.
TaskLabel (lower level component)
/* It looks like TaskLabelProp gets passed in from something else.
Otherwise, you would use useState to get your setter function */
const TaskLabel = ({taskLabelProp, setTaskLabelProp}) => {
return (
<div label={props.taskLabel} >
<input
type='radio'
name='label'
value='urgent'
onChange={(e) => setTaskLabelProp(e.target.value)}
/>
<input
type='radio'
name='label'
value='not-urgent'
onChange={(e) => setTaskLabelProp(e.target.value)}
/>
</div>
);
};
Form (higher level component)
const Form = () => {
const [taskLabelProp, setTaskLabelProp] = useState('');
return (
<form onSubmit={submitHandler}>
<input
type='text'
placeholder='Text here'
className='form-input'
value={task}
onChange={(e) => {
setTask(e.target.value);
}}
/>
<TaskLabel taskLabel={taskLabelProp, setTaskLabelProp} />
</form>
)
}
Let me know if this answers your question.
EDIT: Made Form use useState. Based off your code, I was assuming you were using useState at the app level.
function App() {
const [task, setTask] = useState("");
const [taskLabelProp, setTaskLabelProp] = useState("");
const handleChange = (e) => {
setTaskLabelProp(e.target.value);
};
const submitHandler = (e) => {
e.preventDefault();
};
return (
<div className="App">
<form onSubmit={submitHandler}>
<input
type="text"
placeholder="Text here"
className="form-input"
value={task}
onChange={(e) => {
setTask(e.target.value);
}}
/>
<TaskLabel
onChange={handleChange}
taskLabelProp={taskLabelProp}
taskLabel="select"
/>
<button type="submit">fs</button>
</form>
</div>
);
}
export default App;
const TaskLabel = ({ taskLabel, onChange, taskLabelProp }) => {
return (
<div label={taskLabel}>
<input
type="radio"
name="label"
value="urgent"
onChange={(e) => onChange(e)}
checked={taskLabelProp === "urgent"}
/>
urgent
<input
type="radio"
name="label"
value="not-urgent"
onChange={(e) => onChange(e)}
checked={taskLabelProp === "not-urgent"}
/>
not-urgent
</div>
);
};
export default TaskLabel;
I have an input as a component in my TSX file.
import { InputHTMLAttributes } from "react";
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
placeholder: string,
label?: string,
}
export const Input = ({ placeholder, label, ...rest}: InputProps) => (
<>
{label && <label>{label}</label>}
<input placeholder={placeholder} {...rest} />
</>
)
Although, when I try to use it to take a number value I got a problem.
const [name, setName] = useState<string>("")
const [hobby, setHobby] = useState<string>("")
const [age, setAge] = useState<number>(0)
<Input
placeholder='Your name'
onChange={(e) => setName(e.target.value)}
value={name}
/>
<Input
placeholder='Your hobby'
onChange={(e) => setHobby(e.target.value)}
value={hobby}
/>
<Input
placeholder='Your age'
onChange={(e) => setAge(e.target.value)}
value={age}
/>
The first and second inputs are working well, but my third is not. Is it a problem with my placeholder? The problem says:
Argument of type 'string' is not assignable to parameter of type
'SetStateAction'.
I am not sure but you can try this
<Input
placeholder='Your age'
onChange={(e) => setAge( parseInt(e.target.value) )}
value={age}
/>
I am trying to retrieve the value of the input in my Input component, to assign it to my user object in the parent component.
Here is my user object and my JSX:
const Form = ({ form }) => {
const [inputValue, setInputValue] = useState("");
const user = {
user_firstname: "OO",
user_lastname: "OO",
user_email: "lOOl0jyyv",
user_password: "OO"
};
return (
<form className="form" onSubmit={callApi}>
<Input
key={uuidv4()}
className="input_container"
type="text"
placeholder="Prénom"
id="firstname"
name="firstname"
min="2"
max="40"
/>
<Input
key={uuidv4()}
className="input_container"
type="text"
placeholder="Nom"
id="lastname"
name="lastname"
min="2"
max="60"
/>
<Input
key={uuidv4()}
className="input_container"
type="email"
placeholder="Votre adresse mail"
id="email"
name="email"
/>
<Input
key={uuidv4()}
className="input_container"
type="password"
placeholder="Mot de passe"
id="password"
name="password"
min="10"
max="32"
/>
<Input
key={uuidv4()}
className="input_container"
type="password"
placeholder="Confirmez le mot de passe"
id="verify-password"
name="verify-password"
min="10"
max="32"
/>
<Button name="Inscription" />
</form>
);
};
Each has its own state, I would like to be able to retrieve it from the parent component to assign them to my "user" object. This is my Input component :
const Input = ({
className,
type,
id,
name,
placeholder,
min,
max,
icon1,
icon2
}) => {
const inputHandler = e => {
setInputValue(e.target.value);
};
const [inputValue, setInputValue] = useState("");
return (
<div className={className}>
<input
className="testt"
type={type}
id={id}
name={name}
placeholder={placeholder}
minLength={min}
maxLength={max}
required
value={inputValue}
onChange={inputHandler}
/>
{icon1 && (
<div className="icons_container">
<FontAwesomeIcon icon={icon1} />
<FontAwesomeIcon icon={icon2} />
</div>
)}
</div>
);
};
Why not define the state in the parent element, then pass a reference to the state in the props of the child? In your case, place the input state of all the <Input /> components in the <Form /> component and pass each state to its respective input field.
You can pass onChange handler function as a prop from parent to child. This function will set value for user when child (Input) component invokes the function.
Changes needed for parent component:
...
const [user, setUser] = React.useState({
user_firstname: "OO",
user_lastname: "OO",
user_email: "lOOl0jyyv",
user_password: "OO"
});
return (
<form className="form" onSubmit={callApi}>
<Input
key={uuidv4()}
className="input_container"
type="text"
placeholder="Prénom"
id="firstname"
name="firstname"
min="2"
max="40"
onChange={value => setUser({ ...user, user_firstname: value })}
/>
...
Changes needed for Input component:
const Input = ({
className,
type,
id,
name,
placeholder,
min,
max,
icon1,
icon2,
onChange
}) => {
const inputHandler = e => {
setInputValue(e.target.value);
onChange(e.target.value);
};
...
I got hook:
const [myName, setMyName] = useState("");
const myNameRef = useRef();
Then I have form:
<form onSubmit={(e) => addVist(e, user.id)}>
<input type="text" name="myName" ref={myNameRef} onChange={e => setMyName(e.target.value)} />
<input type="submit" value="Run" />
</form>
And method:
const addVist = (e, userId) => {
e.preventDefault();
console.log(myName)
//some code....
//clear value form
setMyName("");
//setMyName('');
//setMyName(e.target.value = "");
//myNameRef.current.value("");
}
But the setMyName("") is not working - still I see value inside input.
You missed binding myName as value attribute of the input tag.
<input type="text" name="myName" value={myName} ref={myNameRef} onChange={e => setMyName(e.target.value)} />
Here is a complete example of clearing input using state OR a reference:
export default function App() {
const [value, setValue] = useState("");
const inputRef = useRef();
return (
<>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<input ref={inputRef} />
<button
onClick={() => {
setValue("");
inputRef.current.value = "";
}}
>
Clear
</button>
</>
);
}
Refer to Controlled VS Uncontrolled components.
On your text input you should pass myName into the value attribute like so
<input type="text" name="myName" value={myName} ref={myNameRef} onChange={e => setMyName(e.target.value)} />
You forgot to add myName as value.
<input type="text" name="myName" value={myName} ref={myNameRef} onChange={e => setMyName(e.target.value)} />