In my project, there is an input field to add email tags. which I created using react js.
That file working properly, when I convert the file into type script I can't type or paste values to the input field, but there is no error showing in the terminal
js file
import React from "react";
import ReactDOM from "react-dom";
import Chip from "#material-ui/core/Chip";
import "./styles.css";
import TextField from "#material-ui/core/TextField";
class App extends React.Component {
state = {
items: [],
value: "",
error: null,
};
handleKeyDown = evt => {
if (["Enter", "Tab", ","].includes(evt.key)) {
evt.preventDefault();
var value = this.state.value.trim();
if (value && this.isValid(value)) {
this.setState({
items: [...this.state.items, this.state.value],
value: ""
});
}
}
};
handleChange = evt => {
this.setState({
value: evt.target.value,
error: null
});
};
handleDelete = (item) => {
this.setState({
items: this.state.items.filter(i => i !== item),
});
};
handleItemEdit = item =>{
const result = this.state.items.filter(values=>values!==item)
this.setState({
value: item,
error: null,
items: result
});
}
handlePaste = evt => {
evt.preventDefault();
var paste = evt.clipboardData.getData("text");
var emails = paste.match(/[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/g);
if (emails) {
var toBeAdded = emails.filter(email => !this.isInList(email));
this.setState({
items: [...this.state.items, ...toBeAdded]
});
}
};
isValid(email) {
let error = null;
if (this.isInList(email)) {
error = `${email} has already been added.`;
}
if (!this.isEmail(email)) {
error = `${email} is not a valid email address.`;
}
if (error) {
this.setState({ error });
return false;
}
return true;
}
isInList(email) {
return this.state.items.includes(email);
}
isEmail(email) {
return /[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/.test(email);
}
render() {
return (
<>
{/* {this.state.items.map(item => (
<div className="tag-item" key={item} onClick={() => this.handleItemEdit(item)}>
{item}
<button
type="button"
className="button"
onClick={(e) => this.handleDelete(e,item)}
>
×
</button>
</div>
))} */}
<TextField id="outlined-basic" variant="outlined"
InputProps={{
startAdornment: this.state.items.map(item => (
<Chip
key={item}
tabIndex={-1}
label={item}
onDelete={() => this.handleDelete(item)}
onClick={() => this.handleItemEdit(item)}
/>
)),
}}
ref={this.state.items}
className={"input " + (this.state.error && " has-error")}
value={this.state.value}
placeholder="Type or paste email addresses and press `Enter`..."
onKeyDown={this.handleKeyDown}
onChange={this.handleChange}
onPaste={this.handlePaste}
/>
{this.state.error && <p className="error">{this.state.error}</p>}
</>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
ts file
import React, { useRef, useState } from "react";
import ReactDOM from "react-dom";
import Chip from "#material-ui/core/Chip";
import TextField from "#material-ui/core/TextField";
interface details{
item: string
}
// type Props = {
// test: string,
// // error: string,
// };
export const TagActions = () => {
// var { test } = props
const [items, setItem] = useState<string[]>([]);
const [value, setValue] = useState('')
const [error, setError]= useState('')
const divRef = useRef<HTMLDivElement>(null)
const handleDelete = (item:any) => {
const result = items.filter(i => i !== item)
setItem(result)
};
const handleItemEdit = (item:any) =>{
const result = items.filter(i => i !== item)
setItem(result)
setValue(value)
};
const handleKeyDown = (evt:any) => {
if (["Enter", "Tab", ","].includes(evt.key)) {
evt.preventDefault();
var test = value.trim();
if (test && isValid(test)) {
items.push(test)
setValue("")
}
}
};
const isValid = (email:any)=> {
let error = null;
if (isInList(email)) {
error = `${email} has already been added.`;
}
if (!isEmail(email)) {
error = `${email} is not a valid email address.`;
}
if (error) {
setError(error);
return false;
}
return true;
}
const isInList = (email:any)=> {
return items.includes(email);
}
const isEmail = (email:any)=> {
return /[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/.test(email);
}
const handleChange = (evt:any) => {
setValue(evt.target.value)
// setError("")
};
const handlePaste = (evt:any) => {
evt.preventDefault();
var paste = evt.clipboardData.getData("text");
var emails = paste.match(/[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/g);
if (emails) {
var toBeAdded = emails.filter((email:any) => !isInList(email));
setItem(toBeAdded)
}
};
return (
<>
<TextField id="outlined-basic" variant="outlined"
InputProps={{
startAdornment: items.map(item => (
<Chip
key={item}
tabIndex={-1}
label={item}
onDelete={() => handleDelete(item)}
onClick={() => handleItemEdit(item)}
/>
)),
}}
ref={divRef}
value={value}
placeholder="Type or paste email addresses and press `Enter`..."
onKeyDown={(e) => handleKeyDown}
onChange={(e) => handleChange}
onPaste={(e) => handlePaste}
/>
{error && <p className="error">{error}</p>}
</>
);
}
I am a beginner in react typescript. So I don't know what's going on. At first, I referred some documents and applied some solutions, but the expected result did not come. Please share some suggestions to solve this problem
That's a very good start - but there are a few simple issues.
The first big difference between JS and TS is of course the typing - so your functions should definetely be checking that the correct values are being passed! For example:
const isEmail = (email:any)=> {
return /[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/.test(email);
}
should be:
const isEmail = (email:string)=> {
return /[\w\d\.-]+#[\w\d\.-]+\.[\w\d\.-]+/.test(email);
}
This ensures that you can only pass a string to isEmail function.
The same goes for the event handlers - one trick I like to use when figuring out the function signature is hovering over the declaration in TSX:
If you hover over onChange in TSX, it will tell you that its of type React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>, so to get the type of event evt in your function, just remove the handler bit:
const handleChange = (evt: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
// code here
}
Another thing ythat's wrong currently is the way you call the function. In your code you do:
<TextField
id="outlined-basic"
variant="outlined"
...
onChange={e => handleChange}
/>
You aren't actually invoking the handleChange function, so you could do this:
<TextField
id="outlined-basic"
variant="outlined"
...
onChange={e => handleChange(e)}
/>
Or this instead:
<TextField
id="outlined-basic"
variant="outlined"
...
onChange={handleChange}
/>
I made a codesandbox with a working example (but I omitted some parts for simplicity).
But it definetely works, you're on the right tracks:
Related
So I was trying to update the value I got by the Addlist and I tried this but this isn;t working. Also when I click on the '+' button without writing anything, an empty list is created. How should I stop it. I've attached a code below.
import React from "react";
import "./App.css";
import { useState } from "react";
import TodoList from "./components/TodoList";
function App() {
const [input, setInput] = useState("");
const [list, setList] = useState([]);
const updateList = (e) => {
setInput(e.target.value);
};
const AddList = () => {
console.log("value added")
setList((addValue) => {
return [...addValue, input];
});
setInput("");
};
const updateItems=(id)=>{
const newValue=[...list].map((newVal)=>{
if(input.id===id){
input.text='';
}
return newVal;
})
setList(newValue);
}
const deleteItems = (id) => {
console.log("deleted");
setList((addValue) => {
return addValue.filter((element, index) => {
return index !== id;
});
});
};
return (
<div className="todo-app">
<h1> Enter Anything</h1>
<input
type="text"
placeholder="Add anything"
value={input}
onChange={updateList}
/>
<button onClick={AddList}>+</button>
<ul>
{list.map((itemsvalue, id) => {
return (
<TodoList
itemsValue={itemsvalue}
key={id}
onSelect={deleteItems}
id={id}
onUpdate={updateItems}
/>
);
})}
</ul>
</div>
);
}
export default App;
Any kind of help would be appreciated. Also if I want to split this into multiple components is there a way to do.
When user clicks on the add button there is the check for empty String AddList method
for ex:- User updates second index value, second position value will get updated.
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [index, setIndex] = useState(null);
const updateList = (e) => {
setInput(e.target.value);
};
useEffect(() => {
setList(list);
console.log(list, '<>?');
}, [index]);
const AddList = () => {
if (input.trim() !== '') {
setList([...list, input]);
}
setInput('');
};
const updateValue = (index) => {
console.log(list[index]);
setIndex(index);
if (list[index].trim() !== '') {
setInput(list[index]);
}
};
const UpdateList = () => {
list[index] = input;
console.log(list, 'before <>?');
setIndex(null);
setInput('');
};
return (
<div>
<input type="text" placeholder="Add anything" value={input} onChange={updateList} />
<button disabled={!index && !list.length === 0} onClick={AddList}>
Add
</button>
<button disabled={input.trim() === ''} onClick={UpdateList}>
Update
</button>
{list.map((m, index) => (
<h1 style={{ border: '1px solid black' }} onClick={() => updateValue(index)}>
{m}
</h1>
))}
</div>
);
My edit function is overriding the local storage when I try to save and even messing with my delete function. not sure where is the issue. I tried finding the solution in similar posts but could not figure it out. GitHub repo for full code: https://github.com/ottowoolf/NotesApp.
import React, { useState, useEffect } from "react";
import { Form, Container, Button } from "react-bootstrap";
import { useParams } from "react-router-dom";
export const Edit = ({ notes, setNotes }) => {
const [form, setForm] = useState({
title: "",
text: "",
id: "",
});
const { id } = useParams();
useEffect(() => {
let index = notes.find((n) => n.id === id);
let selectedNote = notes[index];
console.log(notes[index]);
console.log(id);
console.log(selectedNote);
setForm(selectedNote);
}, [id, notes]);
const handleChange = (e) => {
const { value, name } = e.target;
setForm({ ...form, [name]: value, id });
console.log(form);
};
const saveNote = () => {
if (form.title.trim() !== "" || form.text.trim() !== "") {
setNotes((note) => {
// return array - with the object of /id/ modified
let index = note.find((n) => n.id === id);
let newNotes = [...note];
newNotes[index] = form;
console.log(newNotes[index]);
return newNotes[index];
});
}
};
return (
<React.Fragment>
<Container>
<Form className='mt-3 w-50'>
<Form.Group controlId='formBasicEmail'>
<Form.Control
onChange={handleChange}
value={form.title}
name='title'
type='text'
/>
</Form.Group>
<Form.Group controlId='formBasicPassword'>
<Form.Control
onChange={handleChange}
value={form.text}
name='text'
as='textarea'
cols='30'
rows='10'
/>
</Form.Group>
</Form>
<Button onClick={saveNote} className='btn btn-success'>
Save
</Button>
</Container>
</React.Fragment>
);
};
Forked your GitHub repo and edited it using codesandbox
Here is the link
https://codesandbox.io/s/aged-dawn-bnv6v?file=/src/pages/Edit.js
Tell me if anything is missing.
Also, I'll suggest you clean up your code a bit. It looks a lot for a Notes App.
Full error message:
Error: Element type is invalid: expected a string (for built-in
components) or a class/function (for composite components) but got:
object. You likely forgot to export your component from the file it's
defined in, or you might have mixed up default and named imports.
usually this is something to do with components not being imported or exported correctly but everything looks fine to me. Any ideas what the issue could be.
import React, { useRef, useState, useEffect } from 'react';
import Form from 'react-bootstrap/Form';
import ScrollToTop from './ScollToTop';
const StoreDirectory = (props) => {
const [filteredValues, setValues] = useState(props.storesData);
const [initialValues, setInitialValues] = useState(props.storesData);
const [storeCategorys, setStoreCategories] = useState(props.storesCategorysData);
const alphabet = "abcdefghijklmnopqrstuvwxyz";
const alphabetIntoArray = alphabet.split("");
const itemsRef = useRef([]);
useEffect(() => {
itemsRef.current = itemsRef.current.slice(0, alphabetIntoArray.length);
}, [alphabetIntoArray])
const AlphaButtons = () => {
const goToSelection = (event, index) => {
if(itemsRef.current[index]) {
itemsRef.current[index].scrollIntoView({
behaviour: "smooth",
block: "nearest"
})
}
}
return (
<>
{alphabet.split("").map((item, index) => (
<button key={index} onClick={(e) => goToSelection(e, index)}>{item}</button>
))}
</>
)
}
const DropDown = () => {
const handleOnchange = (event) => {
const filter = event.target.id;
const initialState = [...initialValues]
setValues(initialState.filter(store =>
{return (store.store-category.indexOf(filter) >= 0)}
));
}
const defaultSelectMessage = 'select a category';
return (
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Custom select</Form.Label>
<Form.Control defaultValue={defaultSelectMessage} onChange={(e) => handleOnchange(e)} as="select" custom>
<option hidden disabled value={defaultSelectMessage}>{defaultSelectMessage}</option>
{storeCategorys.map((item, index) => (
<option id={item.id} key={index}>{item.name}</option>
))}
</Form.Control>
</Form.Group>
</Form>
);
}
const Filter = () => {
return (
<div>
{alphabet.split("").map((c, i) => {
return (
<>
{filteredValues
.filter(store => store.title.rendered.startsWith(c)).length === 0
? <h1 ref={ el => itemsRef.current[i] = el } className={'Grey'}>{c}</h1>
: <h1 ref={ el => itemsRef.current[i] = el } className={'notGrey'}>{c}</h1>
}
{filteredValues
.filter(store => store.title.rendered.startsWith(c))
.map((item, index) => (
<li key={index}>{item.title.rendered}</li>
))}
</>
);
})}
</div>
);
}
return (
<>
<DropDown />
<AlphaButtons />
<Filter />
<ScrollToTop />
</>
)
}
export default StoreDirectory;
I'm a bit new to React. I'm trying to make simple To do list with react hooks and struggling to make "delete all button". I thought it could be work to using setState [] or return []
but it didn't work...
and also it's showing an error.
TypeError: tasks.map is not a function
Does anyone know how it figure out?
Here is my code
import React, {useState} from 'react'
let INITIAL_TASK = {
title: 'React',
doing: false,
}
const App = () => {
const [tasks, setTasks] = useState([INITIAL_TASK])
const [task_title, setTask_title] = useState('')
const handleTextFieldChanges = e => {
setTask_title(e.target.value)
}
const resetTextField = () => {
setTask_title('')
}
const isTaskInclude = () => {
return tasks.some(task => task.title === task_title)
}
const addTask = () => {
setTasks([...tasks, {
title: task_title,
doing: false,
}])
resetTextField()
}
const deleteTask = (task) => {
setTasks(tasks.filter(x => x !== task))
}
const deleteAllTask = () => {
-------------
}
const handleCheckboxChanges = task => {
setTasks(tasks.filter(x => {
if (x === task) x.doing = !x.doing
return x
}))
}
return (
<React.Fragment>
<Container component='main' maxWidth='xs'>
<CssBaseline/>
<Box
mt={5}
display='flex'
justifyContent='space-around'
>
<TextField
label='title'
value={task_title}
onChange={handleTextFieldChanges}
/>
<Button
disabled={task_title === '' || isTaskInclude()}
variant='contained'
color='primary'
onClick={addTask}
href=''
>
add
</Button>
<Button
// disabled={task_title === '' || isTaskInclude()}
variant='contained'
color='secondary'
onClick={deleteAllTask}
href=''
>
all delete
</Button>
</Box>
<List
style={{marginTop: '48px'}}
component='ul'>
{tasks.map(task => (
<ListItem key={task.title} component='li'>
<Checkbox
checked={task.doing}
value='primary'
onChange={() => handleCheckboxChanges(task)}
/>
<ListItemText>{task.title}</ListItemText>
<Button
href=''
onClick={() => deleteTask(task)}
>
delete
</Button>
</ListItem>
))}
</List>
</Container>
</React.Fragment>
)
}
export default App
You can try doing below
const deleteAllTask = () => {
setTasks([]);
};
or if you want it to set to initial value, you can do below
const deleteAllTask = () => {
setTasks([INITIAL_TASK]);
};
I'm trying to add a whole array to useState variable
import React, { Fragment, useState, useEffect } from 'react';
import { Form, Button, Popover, OverlayTrigger } from 'react-bootstrap';
const Filter = props => {
const [formData, setFormData] = useState({
filter: ''
});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci","paris", "london"];
console.log(retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const { filter } = formData;
const onChange = e => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
props.filterData(filter);
//Close the Popover
document.body.click();
};
const popover = (
<Popover id="popover-basic">
<Form>
<Form.Group controlId="formGroupEmail">
<Form.Label>Add New Filter</Form.Label>
<Form.Control
type="text"
placeholder="New Filter"
name="filter"
onChange={e => onChange(e)}
/>
</Form.Group>
<Button variant="dark" type="submit" onClick={e => onSubmit(e)}>
Add
</Button>
</Form>
</Popover>
);
return (
<Fragment>
<label>
<p className="filter-title">{props.title}</p>
</label>
<div className={props.className ? props.className : 'filter'}>
{!props.retrievedFilter
? items.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})
: props.retrievedFilter.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})}
<OverlayTrigger
trigger="click"
placement="right"
rootClose
overlay={popover}
>
<p className="text-field">Type new one</p>
</OverlayTrigger>
</div>
</Fragment>
);
};
export default Filter;
however retrievedItems shows as an empty array in the console.
any help would be appreciated.
setState is async. You have to console.log inside an effect hook with the array as a parameter.
useEffect(() => console.log(retrieved_items), [ retrievedItems ])
The second parameter ensures that the effect fires in repose to a change in the values passed to it.
Per my comment, here is a code snippet that I think does what you want.
I couldn't get it running in SO but here's a codepen: https://codepen.io/anon/pen/PrYYmz?editors=1010 (watch the chrome console as you add items)
import React, {
Fragment,
useState,
useEffect
} from 'react';
const Filter = props => {
const [formData, setFormData] = useState({filter: ''});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci", "paris", "london"];
console.log('add', retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const {filter} = formData;
const onChange = e => {
setFormData({ ...formData,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
//props.filterData(filter);
//Close the Popover
document.body.click();
};
return (
<Fragment >
<label >
<p className = "filter-title" > {
props.title
} </p> </label> <
div className = {
props.className ? props.className : 'filter'
} > {!props.retrievedFilter ?
items.map((item, index) => {
return ( <
div className = "filter-text"
key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)}>X</button></div>
);
}) :
props.retrievedFilter.map((item, index) => {
return ( <div className = "filter-text" key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)} >X</button></div>);})} <input type = "text" placeholder = "New Filter" name = "filter" onChange = {e => onChange(e) }/>
<button variant = "dark" type = "submit" onClick = {e => onSubmit(e)} >Add</button>
</div>
</Fragment>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>