My formik form contains a custom textfield component. The text field component reads the formik context and populates based on the field value passed into it. When i invoke setFieldValue(), the value changes in the formik context but the UI element(textField) does not update. If i navigate to the next step in my form and then back the new values are reflected. So i'm assuming my textfield component is not rerendering when i invoke setFieldValue().
How can i get my textbox element to update when i invoke setFieldValue?
Custom Textfield
import { FieldHookConfig, useField } from 'formik'
import React from 'react'
const TextField = ({
label,
...props
}) => {
const [field, meta, helper] = useField(props)
return (
<div className="relative mb-2 flex w-full flex-col px-1 text-gray-800">
<input
onChange={(e) => helper.setValue(e.target.value.trim())}
onBlur={field.onBlur}
{...props}
id={props.name}
placeholder={label.split('-')[0]}
defaultValue={field.value}
/>
<label
className="absolute -top-2.5 left-3 mt-1 inline-block bg-white text-xs text-gray-400"
htmlFor={field.name}
>
{label}
</label>
{meta.error && meta.touched && (
<p className="px-2 text-sm text-red-500">{meta.error}</p>
)}
</div>
)
}
I then use a simple onclick to set the fieldvalue.
onClick Function
function handleAdaSelect(e) {
console.log(e)
formik.setFieldValue('name', e.value)
}
Here is a sample of how the Textfield is invoked.
<TextField
label="Name"
name="name"
type="text"
disabled
className="border-0 focus:outline-none"
/>
const TextField = ({
label,
name,
...props
}) => {
const [field, meta, helper] = useField(name)
You should pass only the name property to the useField()
Related
I'm fairly new to React. Here I've made a small form component for a project (with a bit of tailwind included). Above the form proper is a hidden alert box that will show on submission (green for success and red for fail). The handler that I have attached to the form shows the correct alert, however it takes two clicks. In the handler validateFormData() I'm resetting state (isError). I'm aware the useState hook is asynchronous, so my isError variable is not updating properly before I render my alert box (right?). I've tried passing callbacks to my setIsError functions (after looking online for solutions) but I could not resolve the issue. Am I even in the right ball park here? Or am I missing something?
function ContactInsert() {
const contactForm = useRef(null);
const alertBox = useRef(null);
const [isError, setIsError] = useState(false);
function showAlertBox() {
alertBox.current.classList.add("alert-show");
setTimeout(() => {
alertBox.current.classList.remove("alert-show");
}, 3000);
}
async function validateFormData() {
// unpack and validate data
const name = contactForm.current.querySelector("[name=name]").value.trim();
const email = contactForm.current.querySelector("[name=email]").value.trim();
const comment = contactForm.current.querySelector("[name=comment]").value.trim();
if (name.length > 0 && email.length > 0 && comment.length > 0) {
setIsError(false);
} else {
setIsError(true);
}
showAlertBox();
}
return (
<div className="flex flex-col">
<div className="flex justify-center my-2">
<h1>Drop me a message!</h1>
</div>
{isError ?
<div ref={alertBox} className="flex h-12 w-2/3 justify-center bg-[#ff462e] invisible">hello</div> :
<div ref={alertBox} className="flex h-12 w-2/3 justify-center bg-[#77ff6e] invisible">hello</div>
}
<form
className="flex flex-col items-center justify-center md:h-full"
method="POST"
name="contact"
id="contact"
type="submit"
ref={contactForm}
onSubmit={(e) => {
e.preventDefault();
validateFormData();
}}
>
<div className="flex flex-col justify-between w-2/3">
<label>name</label>
<input type="text" name="name"/>
</div>
<br/>
<div className="flex flex-col justify-between w-2/3">
<label>email</label>
<input type="text" name="email"/>
</div>
<br/>
<div className="flex flex-col justify-between w-2/3 h-40">
<label>comment</label>
<textarea className="h-full" name="comment"/>
</div>
<div className="flex w-2/3 justify-start my-4">
<button className="p-1" form="contact">Submit</button>
</div>
</form>
</div>
);
}
It is always a bad idea to manipulate DOM directly as React will have a hard time to match the rendered output and defeats the purpose of using react. Better to store this info in the state and let react handle it.
Also setState is asynchronous, and calling that will force the react component to rerender. We can use useEffect to let React know that component has to do something on next render.
function ContactInsert() {
const [isError, setIsError] = useState(false);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [comment, setComment] = useState('');
const [displayAlert, setDisplayAlert] = useState(false);
const handleNameOnchangeHandler = (event) => {
setName(event.target.value);
};
const handleEmailOnchangeHandler = (event) => {
setName(event.target.value);
};
const handleCommentOnchangeHandler = (event) => {
setName(event.target.value);
};
const validateFormData = () => {
const hasError = !name || !email || !comment;
setIsError(hasError);
}
useEffect(() => {
if(isError) {
setDisplayAlert(true);
setTimeout(() => {
setDisplayAlert(false);
setIsError(false);
}, 3000);
}
}, [isError]);
return (
<div className="flex flex-col">
<div className="flex justify-center my-2">
<h1>Drop me a message!</h1>
</div>
{displayAlert ?
<div ref={alertBox} className="flex h-12 w-2/3 justify-center bg-[#ff462e] show-alert">hello</div> : null
}
<form
className="flex flex-col items-center justify-center md:h-full"
method="POST"
name="contact"
id="contact"
type="submit"
ref={contactForm}
onSubmit={(e) => {
e.preventDefault();
validateFormData();
}}
>
<div className="flex flex-col justify-between w-2/3">
<label>name</label>
<input type="text" name="name" onchange={handleNameOnchangeHandler}/>
</div>
<br/>
<div className="flex flex-col justify-between w-2/3">
<label>email</label>
<input type="text" name="email" onchange={handleEmailOnchangeHandler}/>
</div>
<br/>
<div className="flex flex-col justify-between w-2/3 h-40">
<label>comment</label>
<textarea className="h-full" name="comment" onchange={handleCommentOnchangeHandler}/>
</div>
<div className="flex w-2/3 justify-start my-4">
<button className="p-1" form="contact">Submit</button>
</div>
</form>
</div>
);
}
This is an example using useEffect. This can also be done without using it. You could also have a single onchange handler instead and set the value based on the target where the event was triggered.
you are using react completely wrong.
i highly recommend you to read react docs carefully and digging into the rendering concept.
the problems i saw in your code was :
the way you’re getting the input value is not good.
you should define a state (controlled component) or pass a ref to get the value (uncontrolled).
changing your css classes with a ref is not gonna work because it doesn’t trigger a rerender in your app (read useRef docs for more details)
you should define a new state and use that state for changing your className.
I am trying on a code using React with an input and a button. When I click on a button I would like to clear the input or it is not the case. Here is my code :
import { useState } from "react";
import "./styles.css";
const App = () => {
const [finalValue, setFinalValue] = useState(true);
const changevalue = () => {
setFinalValue(!finalValue);
};
return (
<div className="App">
{finalValue ? (
<input
type="text"
placeholder=" E-mail"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
) : (
<input
type="text"
placeholder=" Pseudo"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
)}
<button onClick={() => changevalue()}>Change input</button>
</div>
);
};
export default App;
Here is my code : https://codesandbox.io/s/wandering-fire-hj8d84?file=/src/App.js:0-823
Could you help me please ?
Thank you very much !
NB : I tried to use value but I was not able to type on the input and I also tried to use defaultValue without any success.
You have to use useRef I have implemented this below,
First, you have to import useRef Hook and then make a constant one
const one = useRef("") then in the input tag you have to add ref={one}.
Then in the changevalue function you have to write one.current.value = "";
import { useState ,useRef } from "react";
import "./styles.css";
const App = () => {
const [finalValue, setFinalValue] = useState(true);
const one = useRef("");
const changevalue = () => {
setFinalValue(!finalValue);
one.current.value = "";
};
return (
<div className="App">
{finalValue ? (
<input ref={one}
type="text"
placeholder=" E-mail"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
) : (
<input type="text" ref={one}
placeholder=" Pseudo"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
)}
<button onClick={changevalue}>Change input</button>
</div>
);
};
export default App;
Try this one
import { useState, useRef } from "react";
import "./styles.css";
const App = () => {
const [finalValue, setFinalValue] = useState(true);
const emailRef = useRef();
const changevalue = () => {
setFinalValue(!finalValue);
emailRef.current.value = "";
};
return (
<div className="App">
{finalValue ? (
<input
type="text"
ref={emailRef}
placeholder=" E-mail"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
) : (
<input
type="text"
ref={emailRef}
placeholder=" Pseudo"
className="mt-1 block w-full border-none bg-gray-100 h-11 rounded-xl shadow-lg hover:bg-blue-100 focus:bg-blue-100 focus:ring-0"
/>
)}
<button onClick={() => changevalue()}>Change input</button>
</div>
);
};
export default App;
First of all you could use const [inputValue, setInputValue] = useState(''); to use/change/clear input value;
Then just set value and onChange function into input tag (event.target.value will be your input string value). And with you button just set inputValue to default '';
Hope this help
I'm trying to get the task value from the button when pressed to show it in the side nav, but the value is always empty even though it's not. help appreciated.
See the comments in the code for more details:
import './index.css';
import React, {useState} from 'react';
function App() {
return (
<div>
<TodoListItem />
</div>
);
}
const TodoListItem = (props) => {
//for task list
const [tasks, setTasks] = useState(['']);
//toggling the side menu
const [toggle, setToggle]= useState(true);
//toggling grid layout
const [grid, setGrid]= useState('');
//getting the selected item !! not working
const [selectedTask, setSelectedTask]= useState('');
//brings out the side nav bar
const TodoItemDetails = () => {
setToggle(false)
setGrid("grid grid-cols-2")
}
const onFormSubmit = e => {
e.preventDefault();
}
return (
<div class="bg-gray-100 items-center justify-center min-h-screen min-w-screen p-4">
<div class={grid}>
{/* grid one */}
<div>
{/* task form */}
<form onSubmit={onFormSubmit} class="bg-white rounded py-4 px-2 my-4 h-16 shadow-sm">
<input
class="w-[92%] h-full float-left focus:outline-none placeholder-blue-500 ml-2 py-1 focus:placeholder-black"
type="text"
id="task"
placeholder="Add a task"
/>
{/* task add button*/}
<button
type="submit"
class="text-blue-500 float-right text-2xl -translate-y-0.5 -translate-x-1"
onClick={() => {
let taskdom = document.getElementById("task");
{/* creates individual task */}
let task = <button
onClick={() => {
{/* nav bar comes out whne the invidual task is pressed with the task value */}
TodoItemDetails();
{/* the below setSelectedTask should set */}
{/* the selected task and get the value from taskdom.value but its always empty (cont) */}
setSelectedTask(taskdom.value);
}}
{/* even though taskdom.value works properly right after that */}
class="bg-white w-full hover:bg-gray-200 p-4 my-1 rounded shadow-sm text-left">{taskdom.value}</button>
{/* adds the new task to the the task array */}
setTasks((oldTasks) => [ ...oldTasks, task ])
{/* empties the task text box */}
taskdom.value = "";
}}
>+</button>
</form>
{/* shows all the tasks */}
{tasks}
</div>
{/* grid two: side nav bar */}
<div hidden={toggle}>
{/* nav bar hides itself when this is pressed !!!! this value is supposed to be from the pressed value but its empty */}
<button onClick={() => {
setToggle(true)
setGrid("")
}}>{selectedTask}</button>
</div>
</div>
</div>
);
}
export default App;
sorry for bad formatting...this is the first time I'm posting a question even though I've been using stack overflow for 2 years and I don't know how to exactly ask this question...
The React way to do this is to use a controlled input element and store the value in local state on change. You can then get it from the state when the button is clicked.
You could use a useRef hook on your input
// logic
const [selectedTask, setSelectedTask]= useState('');
const inputValue = useRef('');
const handleInput = () => {
setSelectedTask(inputValue.current.focus());
}
// render
<input type="text" id="task" ref={inputValue} />
<button onClick={handleInput}>Click</button>
Then you should be able to use the state.
The "newTodo" state variable is empty in the first instance when i load the page, that's why the placeholder is not displaying, it act as if there is zero value in newTodo variable so placeholder is not showing up in the first instance, but after i enter a text then placeholder shows up.
import React, {useState} from 'react'
const ToDo = () => {
const [todo, setTodo] = useState([]);
const [newTodo, setNewTodo] = useState(" ");
let globalID = 0;
const handleSubmit = (e) => {
e.preventDefault();
setNewTodo("");
setTodo((oldTodo) => {
return [...oldTodo, newTodo]
});
}
const handleInput = (e) => {
setNewTodo(e.target.value);
}
return (
<>
<h1 className="header">Building To Do App</h1>
<section className='flex justify-center mt-8'>
<div className='border border-indigo-800 w-1/2 flex flex-col text-center h-96'>
<h2 className=' h-16 flex justify-center items-center bg-pink-600 text-white' >To-Do List</h2>
<form onSubmit={handleSubmit} className="mt-6">
<input placeholder='Add a Item' type="text" onChange={handleInput} name="todo" id="todo" value={newTodo} className='w-1/2 h-12 rounded-sm border-b-4 border-indigo-500 text-xl bg-gray-300 focus:outline-none text-black' />
<button className='bg-pink-600 rounded-full w-12 h-12 mx-4 hover:bg-green-700'>+</button>
</form>
<ol className='todoList'>
{todo.map((items) => {
return <li key={globalID++} >{items}</li>
} )}
</ol>
</div>
</section>
</>
)
}
export default ToDo
You set the default value newTodo value " " that's issue !
const [newTodo, setNewTodo] = useState();
Remove " " from useState(); solve the issue !
I have a ref to an <input type="checkbox"/> element, and when I programmatically set checked=false on the element, the element's onChange callback does not get called.
I tried using ref.dispatchEvent(new Event('input')) and ref.dispatchEvent(new Event('change')) and neither caused the React onChange callback to get executed.
All the questions and answers I could find on StackOverflow about this have to do with <input type="text"/> elements, none dealing with changing the checked property programmatically on an <input type="checkbox"/> element and its onChange handler not being invoked.
Here's a CodePen that demonstrates the issue:
https://codepen.io/dossy/pen/QWKVNzZ/left/?editors=0011
You can check and uncheck the checkbox, and the <div>Checked!</div> will appear and disappear as expected. However, clicking the <button>Reset</button> will uncheck the checkbox if it's checked, but since the input's onChange handler isn't being executed, the div isn't being hidden as it should be.
...
Yes, I know that I could do this as a Controlled Component but that's not the point: I have a use case where using refs is required so I must implement this as an Uncontrolled Component, and getting the onChange handler to execute when the DOM element changes is the problem I need to solve.
Thanks!
Here is the working code. working link https://codesandbox.io/s/blissful-wozniak-cc1sn?file=/src/Test.js:0-1753
import React, { useEffect, useRef } from "react";
export function Test() {
const ref_input = useRef(null);
const ref_text = useRef(null);
useEffect(() => {
ref_input.current.addEventListener("change", function (event) {
alert(event.target.checked);
});
}, []);
function triggerEvent(element, eventName) {
var event = document.createEvent("HTMLEvents");
event.initEvent(eventName, false, true);
element.dispatchEvent(event);
}
return (
<div className="h-screen flex bg-white text-gray-900 justify-center items-center">
<div className="flex items-start w-64">
<div className="flex items-center gap-4">
<button
className="inline-flex items-center px-4 py-2 border border-gray-500 rounded-md"
onClick={() => {
ref_input.current.checked = false;
triggerEvent(ref_input.current, "change");
//ref_input.dispatchEvent(new Event("input"));
//ref_input.current.dispatchEvent(new Event("onChange"));
}}
>
Reset
</button>
<div>Checkbox:</div>
<input
ref={ref_input}
type="checkbox"
className="h-4 w-4 border-gray-300 rounded"
// onChange={(e) => {
// console.log("onChange called", e.target.checked);
// e.target.checked
// ? ref_text.current.classList.remove("hidden")
// : ref_text.current.classList.add("hidden");
// }}
/>
<div ref={ref_text} className="hidden">
Checked!
</div>
</div>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
It's better to do things the "react way".
That means, instead of manipulating dom elements with imperative code (if you're using refs, you're using imperative code), do it declaratively with state/props:
function App() {
const [checked,setChecked] = React.useState(false);
return (
<div className="h-screen flex bg-white text-gray-900 justify-center items-center">
<div className="flex items-start w-64">
<div className="flex items-center gap-4">
<button
className="inline-flex items-center px-4 py-2 border border-gray-500 rounded-md"
onClick={() => setChecked(false)}
>
Reset
</button>
<div>Checkbox:</div>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
className="h-4 w-4 border-gray-300 rounded"
/>
<div className={checked ? '' : 'hidden'}>
Checked!
</div>
</div>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
Here's a link to the updated pen: https://codepen.io/zuze-lab/pen/QWKVEbY?editors=0011
EDIT: I want to be really clear, ref's aren't bad, not at all. Lots of react libraries expose refs because imperative code makes sense for those libraries APIs. When using refs to add event listeners, or imperatively manipulate elements, you're doing things wrong and you need to back up.