How to create dynamic (conditional) placeholder in React.js - javascript

So I have a submit form where the user needs to create a task by typing in a task name. I want it to be empty at the beginning and have a placeholder of "you must enter a task" when the user click add without entering anything. Now I can achieve it to display the placeholder but it's either always there or I encounter unreachable code. I know how to clean the submission & return to the add function, just need to be able to display the placeholder conditionally. Here's what my code looks like atm:
import { useState } from "react";
export default function Todos() {
const [todos, setTodos] = useState([{ text: "hey" }]);
const [todoText, setTodoText] = useState("");
const [isEmpty, setEmpty] = useState("false");
const addTodo = (e) => {
e.preventDefault();
if (todoText){
setTodos([...todos, { text: todoText }]);
setTodoText("");
} else {
setEmpty(true)
setTodoText("");
return
}
}
return (
<div>
{todos.map((todo, index) => (
<div key={index}>
<input type="checkbox" />
<label>{todo.text}</label>
</div>
))}
<br />
<form onSubmit={addTodo}>
<input
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
type="text"
></input>
<button type="submit">Add</button>
{isEmpty &&<span style={{ color: "red" }}>Enter a task</span>}
</form>
</div>
);
}

I could change your code with the following:
You need to initialize isEmpty by false instead of string "false".
And you can use this flag on showing placeholder texts.
Note that I renamed isEmpty by showError.
import { useState } from "react";
export default function Todos() {
const [todos, setTodos] = useState([{text: "hey"}]);
const [todoText, setTodoText] = useState("");
const [showError, setShowError] = useState(false);
// #ts-ignore
const addTodo = (e) => {
e.preventDefault();
if (todoText) {
setTodos([...todos, {text: todoText}]);
setTodoText("");
setShowError(false);
} else {
setTodoText("");
setShowError(true);
return
}
}
return (
<div>
{todos.map((todo, index) => (
<div key={index}>
<input type="checkbox"/>
<label>{todo.text}</label>
</div>
))}
<br/>
<form onSubmit={addTodo}>
<input
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
type="text"
></input>
<button type="submit">Add</button>
{(showError && !todoText) && <span style={{color: "red"}}>Enter a task</span>}
</form>
</div>
);
}

Related

Failed to get state by useLocation while it has Object data in state

First.js
import { useState } from "react";
import { Link } from "react-router-dom";
const First = () => {
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
}
return (
<div className="First">
<h1>This is First Input Page</h1>
<form onSubmit={handleSubmit}>
<dd>data 1</dd>
<input
type="text"
value={name}
onChange={(e) =>
setName(e.target.value)
}
required
></input>
<dd>data 2</dd>
<input
type="text"
value={phone}
onChange={(e) =>
setPhone(e.target.value)
}
required
></input><br/>
<Link to={'/second'} state={{ state: { name : name , phone : phone } }}><button>submit</button></Link>
</form>
</div>
);
}
export default First;
I try to send Object data using Link/state to another component.
Second.js
import {useLocation} from 'react-router-dom';
const Second = () => {
const location = useLocation();
console.log(location.state);
console.log(location.state.name);
return (
<div className="Second">
<h1>This is Second Input Page</h1>
<form>
<dd>data 3</dd>
<input></input>
<dd>data 4</dd>
<input></input><br/>
<button>submit</button>
</form>
</div>
);
}
export default Second;
However, while I can access to (location.state), I can not access to (location.state.name). Why is that?
Output
state: {name: 'Myname', phone: 'myphone'}
[[Prototype]]: Object
--------------------
undefined
The output shows that the code line "console.log(location.state);" works, but to the "console.log(location.state.name);", it shows undefined.
It's because you passed an object with state as the root property, i.e.
state={{ state: { name: name, phone: phone } }}
so to access it in the receiving route it is location.state.state.name.
You really don't need to nest the data you want to pass under a state property when using the Link component, it's not quite the same as when using the navigate function.
<Link to="/second" state={{ name, phone }}>
<button>submit</button>
</Link>
It may also be considered semantically incorrect HTML to nest a button element within an anchor tag (via Link) element. Use the useNavigate hook and issue an imperative navigation action from the form's submit handler. In this case the navigate function's second argument, the options object, *does* expect the state to be passed under the state` property.
Example:
import { Link, useNavigate } from "react-router-dom";
const First = () => {
const navigate = useNavigate();
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
navigate("/second", { state: { name, phone } });
};
return (
<div className="First">
<h1>This is First Input Page</h1>
<form onSubmit={handleSubmit}>
<dd>data 1</dd>
<input
type="text"
value={name}
onChange={(e) =>
setName(e.target.value)
}
required
/>
<dd>data 2</dd>
<input
type="text"
value={phone}
onChange={(e) =>
setPhone(e.target.value)
}
required
/>
<br/>
<button>submit</button>
</form>
</div>
);
};
export default First;

Check if html input tag is required

In my react js application i have the next input:
<input required type="text" id="fname" name="fname" value=""/>
In the case above if the form will be submitted the without input value the will appear a message: Please fill in this field, because it is required. Instead of this text i want to add a custom html element with a different content and style.
I want to check after the form is submitted that this input is required and there is no value. So if there is that scenario then to show a custom element bellow like: <div>No data here</div>
I need to achieve this without using the form tag just to create a custom input component in React js and to do something like:
export default function Input() {
const ref = React.useRef();
return (
<div className="App">
<input ref={ref} required type="text" id="fname" name="fname" value=""/>
{ref.current.isError ? <div>Message</div> : null}
</div>
);
}
Is this possible to check?
You can use onInvalid method on input e.g:
function Input() {
const [invalid, setInvalid] = useState<boolean>(false);
const onSubmit = (data: any) => {
setInvalid(false);
console.log(data)
}
return (
<div style={{ margin: '60px'}}>
<form onSubmit={onSubmit}>
<input
id="fname"
name="fname"
required
type="text"
onInvalid={(e) => {
e.preventDefault();
// if you have ref you can obtain reason why this input is invalid
// console.log(ref.current?.validity.valueMissing);
// or just use e.target.validity
setInvalid(true);
}}
/>
<button type="submit">submit</button>
</form>
{invalid && "invalid"}
</div>
);
}
useState is used here to cause re-render component
Edit: if you don't want to have form inside Input component then just move state to parent component e.g:
function Input(props: { invalid: boolean; setInvalid: () => void }) {
const { invalid, setInvalid } = props;
return (
<div style={{ margin: '60px'}}>
<input
id="fname"
name="fname"
required
type="text"
onInvalid={(e) => {
e.preventDefault();
setInvalid();
}}
/>
{invalid && "invalid"}
</div>
);
}
function App() {
const [invalid, setInvalid] = useState<Record<string, boolean>>({});
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
setInvalid({});
console.log(event);
}
return (
<div className="App">
<form onSubmit={handleSubmit}>
<Input invalid={invalid.fname} setInvalid={() => setInvalid(state => ({...state, fname: true}))}/>
<button type="submit">submit</button>
</form>
</div>
);
}
Edit2: If you want make it valid again you can use onChange:
function Input() {
const [invalid, setInvalid] = React.useState(false);
const updateInvalid = (e) => setInvalid(!e.nativeEvent.target.validity.valid);
return (
<div>
<input
id="fname"
name="fname"
required
type="text"
onChange={updateInvalid}
onInvalid={(e) => {
e.preventDefault();
updateInvalid(e);
}}
/>
<div>{invalid && "Invalid"}</div>
</div>
);
}
Sandbox
All you need is to check if the input is empty or not and based on result we are making a decision for printing message.
All we need is useState hook, onSubmit form validation.
index.js
import React from "react";
import ReactDOM from "react-dom";
import MyInput from "./MyInput.js";
class App extends React.Component {
render() {
return (
<div>
// validate attribute to defind which validation we need
<MyInput validate="email" />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
MyInput.js
import React, { useState } from "react";
const MyInput = ({ validate }) => {
const [userInput, setUserInput] = useState("");
const [isError, setIsError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const handleInput = (e) => {
setUserInput(e.target.value);
setIsError(false);
};
function validateEmail(email) {
var re = /\S+#\S+\.\S+/;
return re.test(email);
}
//you can defind more states such as for password or as per your requirement.
const validateField = (fieldType) => {
switch (fieldType) {
case "email":
if (validateEmail(userInput)) {
setIsError(false);
} else {
setErrorMessage("Need proper email..");
setIsError(true);
}
break;
default:
setIsError(false);
}
};
const handleSubmit = (e) => {
e.preventDefault();
validateField(validate);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" value={userInput} onChange={handleInput} />
{isError ? <p>{errorMessage}</p> : ""}
<input type="submit" />
</form>
);
};
export default MyInput;
Here is working example.
<iframe src="https://codesandbox.io/embed/react-playground-forked-ozgle5?fontsize=14&hidenavigation=1&theme=dark&view=preview" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" title="React PlayGround (forked)" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>

Text input field won't submit when pressing enter

I have a text input that I am trying to use to create new tags via hooks. However, when I enter in the value and press enter, nothing happens. I Googled it and was told to make the input type=text but this has not fixed it. Here is the code:
import React, { useState } from 'react';
import AddIcon from '#mui/icons-material/Add';
import MaximizeIcon from '#mui/icons-material/Maximize';
import './styles.css';
const Card = ({ id, pic, name, email, company, skill, average, grades }) => {
const [clicked, setClicked] = useState(false);
const [tags, setTags] = useState([]);
return (
<div className='card' id={id}>
<img src={pic} alt='avatar' className='img' />
<div className='list'>
<h1>{name}</h1>
<div className='properties'>
<p>Email: {email}</p>
<p>Company: {company}</p>
<p>Skill: {skill}</p>
<p>Average: {average}</p>
</div>
<input
type='text'
placeholder='Add a tag'
onSubmit={(e) => {
setTags(...tags, e.target.value);
console.log(tags);
}}
/>
{clicked && (
<div className='grades'>
{grades.map((grade, index) => (
<p>{`Test ${index}: ${grade}%`}</p>
))}
</div>
)}
</div>
<button onClick={() => setClicked(!clicked)}>
{!clicked ? (
<AddIcon fontSize='large' />
) : (
<MaximizeIcon fontSize='large' />
)}
</button>
</div>
);
};
export default Card;
How do I make it submit a new tags to the setTags hook?
You can use keyDown Event
onKeyDown = {
(e) => {
if (e.key === 'Enter') {
setTags(...tags, e.target.value);
}
}
}

How to reset input field from useRef in React?

I have a text input. If I click on a specific button in the page, I want to reset the value of the input. Here is my code:
const inputRef = useRef()
const handleClick= () => {
inputRef.current.value.reset();
return "hello world"
}
return (
<>
<input type="text" ref={inputRef}/>
<button onClick={()=> handleClick}>delete all</button>
</>
)
It doesn't work. How to fix this?
reset is available on form element.
You can wrap your input with a form, and trigger reset on it.
const {useRef} = React;
const App = () => {
const formRef = useRef();
const handleClick = () => {
formRef.current.reset();
};
return (
<form ref={formRef}>
<input type="text" />
<input type="password" />
<input type="checkbox" />
<textarea></textarea>
<button onClick={handleClick} type="button">
clear form
</button>
</form>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can clear the value in the input field like below.
const handleClick= () => {
inputRef.current.value = "";
return "hello world"
}
and change onClick call in the button like below
onClick={handleClick}
//or
onClick={()=> handleClick()}
If you want complete reset of a form having multiple inputs, you can follow the below approach.
In below example, form will reset after submit
const formRef = useRef();
const handleClick = () => {
formRef.current.reset();
}
render() {
return (
<form ref={formRef}>
<input />
<input />
...
<input />
</form>
);
}
if you don't want to use Ref
const handleSubmit = e => {
e.preventDefault();
e.target.reset();
}
<form onSubmit={handleSubmit}>
...
</form>
You can clear the text input field by setting its value to an empty string. You can do that like this inputref.current.value = "" if you want to use uncontrolled inputs.
However, if you want to use controlled inputs you can create a state variable to track the value of the input field. For example,
const SomeComponent = () => {
const [text, setText] = useState('')
return (
<>
<input type="text" value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={() => setText('')}>delete all</button>
</>
);
};
Here is a codesandbox with both implementation.
You have two problems, one you are passing a function that calls a function into your onClick handler -- which isn't needed. If you define the function before your render, you do not need to pass an anonymous function to the onClick handler.
// Before
<button onClick={()=> handleClick}>delete all</button>
// After
<button onClick={handleClick}>delete all</button>
The other problem is that your handleClick function calls reset, which is not a function on an input. To reset the value of the referenced input, you can set it to an empty string (or whatever you want the "default" value to be).
const handleClick = e => {
inputRef.current.value = "";
return "hello world";
};
rest value in input
import { useRef } from 'react'
const Test = () => {
const testRef = useRef(null)
return (
<div>
<input type="text" ref={testRef} />
<button onClick={() => inputSearch.current.value = ''}>×</button>
</div>
)
}
export default Test

using a .put to edit item from an API-generated array

I'm attempting to use a .put call to edit a color from an array of colors that I am pulling from an API. However, my .put call is not working for some reason. I am unable to get the response from the .put call to log to the console. When I try to submit by clicking the save button, I get an error that says colors.map is not a function. Does anyone know how I can resolve this?
import React, { useState } from "react";
import axios from "axios";
import { axiosWithAuth } from "../utils/axiosWithAuth";
const initialColor = {
color: "",
code: { hex: "" },
};
const ColorList = ({ colors, updateColors }) => {
console.log(colors);
const [editing, setEditing] = useState(false);
const [colorToEdit, setColorToEdit] = useState(initialColor);
const editColor = color => {
setEditing(true);
setColorToEdit(color);
};
const saveEdit = e => {
e.preventDefault();
// Make a put request to save your updated color
// think about where will you get the id from...
// where is is saved right now?
axiosWithAuth().put(`/colors/${colorToEdit.id}`, colorToEdit)
.then(res => {
console.log(res);
updateColors(res.data);
})
};
const deleteColor = color => {
// make a delete request to delete this color
};
return (
<div className="colors-wrap">
<p>colors</p>
<ul>
{colors.map(color => (
<li key={color.color} onClick={() => editColor(color)}>
<span>
<span className="delete" onClick={e => {
e.stopPropagation();
deleteColor(color)
}
}>
x
</span>{" "}
{color.color}
</span>
<div
className="color-box"
style={{ backgroundColor: color.code.hex }}
/>
</li>
))}
</ul>
{editing && (
<form onSubmit={saveEdit}>
<legend>edit color</legend>
<label>
color name:
<input
onChange={e =>
setColorToEdit({ ...colorToEdit, color: e.target.value })
}
value={colorToEdit.color}
/>
</label>
<label>
hex code:
<input
onChange={e =>
setColorToEdit({
...colorToEdit,
code: { hex: e.target.value }
})
}
value={colorToEdit.code.hex}
/>
</label>
<div className="button-row">
<button type="submit">save</button>
<button onClick={() => setEditing(false)}>cancel</button>
</div>
</form>
)}
<div className="spacer" />
{/* stretch - build another form here to add a color */}
</div>
);
};
export default ColorList;
if you are getting error "that says colors.map is not a function" mean colors in not an array type. You may check using Array.isArray(colors). It will return true if colors will array type.

Categories