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]);
};
Related
I'm getting this error
and here's my code:
const Search = () => {
const [searchBox, setSearchBox] = useState(false)
const [searchIcon, setSearchIcon] = useState(true)
const {searchText, setSearchText} = useState('')
const [searchChanged, setSearchChanged] = useState(false)
const searchBoxRef = useRef()
const history = useHistory()
useEffect(() => {
document.addEventListener('mousedown', outsideSearchClickListener, false)
return () => {
document.removeEventListener('mousedown', outsideSearchClickListener, false)
}
}, [])
useEffect(() => {
if (searchText.length > 0) {
history.push({
pathname: '/search',
search: `?q=${searchText}`
})
} else if (searchChanged && searchText.length === 0) {
history.replace({ pathname: '/browse' })
}
}, [history, searchText, searchChanged])
const searchClickHandler = () => {
setSearchBox(true)
}
const outsideSearchClickListener = event => {
if (searchBoxRef.current && !searchBoxRef.current.contains(event.target)) {
setSearchBox(false)
}
}
const searchTextChangeHandler = event => {
const textValue = event.target.value
setSearchText(textValue)
setSearchChanged(true)
}
const clickCrossHandler = () => {
setSearchText('')
}
const searchBar = (
<CSSTransition in={searchBox} classNames="search-animation" timeout={500} unmountOnExit
onEnter={() => setSearchIcon(false)}
onExiting={() => setSearchBox(false)}
onExited={() => setSearchIcon(true)}>
<div className="Holder">
<FontAwesomeIcon className="Icon" size="lg" icon={faSearch} />
<input autoFocus placeholder="Titles, people, genres"
onChange={searchTextChangeHandler} value={searchText} />
{searchText.length > 0 ?
<FontAwesomeIcon onClick={clickCrossHandler} size="lg" icon={faTimes} /> : null
}
</div>
</CSSTransition>
)
return (
<div className="SearchBox" onClick={searchClickHandler} ref={searchBoxRef}>
{searchIcon && <FontAwesomeIcon size="lg" icon={faSearch} />}
{searchBar}
</div>
)
}
I rechecked my source code and compare mine with the original code for this but still no clue on this error. The console output is kind of ambiguous. FYI: the original one is work, but it's not work for me
Maybe change your handler like this so you ensure the value is always a string
const searchTextChangeHandler = event => {
const textValue = event.target.value || ""
setSearchText(textValue)
setSearchChanged(true)
}
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:
This is my component of imageslider with button next and previous
I need som help how i can get individual img_src values and add them into another array and them use them in my image slider.
I welcome every solution corresponding to my aproach
const ImageSlider = () => {
const dispatch = useDispatch();
const ImageList = useSelector((state) => state.ImageList);
const { loading, error, Images } = ImageList;
useEffect(() => {
dispatch(ListImages());
}, [dispatch]);
var items = [Images.photos];
console.log(Images);
const classes = useStyles();
function Item(props) {
return (
<Paper>
{props.item.map(data => (
<img src={data.img_src} />
))}
{({ onClick, className, style, next, prev }) => {
return (
<Button onClick={onClick} className={classes.button} style={style}>
{next && "Next"}
{prev && "Previous"}
</Button>
);
}}
</Paper>
);
}
return (
<>
{loading ? (
<Loader />
) : error ? (
<h1>{error}</h1>
) : (
<Carousel>
{items.map((item, i) => (
<Item key={i} item={item} />
))}
</Carousel>
)}
</>
);
};
export default ImageSlider;
```
First of all you should move the Item component out of the ImageSlider. It is being redefined every render. You can use localState to keep track of the index.
const useImageIndexer = (maxIndex) => {
const [index, setIndex] = useState(0);
const nextImage = () => {
setIndex((current) => Math.min(maxIndex, current + 1));
};
const prevImage = () => {
setIndex((current) => Math.max(0, current - 1));
};
return [index, nextImage, prevImage];
}
Then to use inside the slider
const ImageSlider = () => {
const dispatch = useDispatch();
const ImageList = useSelector((state) => state.ImageList);
const photos = ImageList.Images.photos;
const [index, nextImage, prevImage] = useImageIndexer(photos.length);
const currentPhoto = photos[index];
// Further down in the code
if(loading) {
return (<Loader />);
}
if (error) {
return (<div>Oh no!</div>);
}
return (<div>
<img src={img.src} />
<button onClick={prevImage}>Previous</button>
<button onClick={nextImage}>Next</button>
</div>);
It seemed like you were wrapping photos which sounds like an array inside another array, that doesn't look right.
var items = [Images.photos];
I want to add startClick to BackButton as in NextButton. In other words, when the BackButton is clicked, the startClick function should work first, then the dispatch (giveForm2PreviousStep (props.currentStep, props.goToStep)) method should work in order. How can I do that?
Question JS
const Question = props => {
const dispatch = useDispatch()
const loading = useSelector(state => state.app.isLoading)
const error = useSelector(state => state.app.error)
const reduxF2 = useSelector(state => state.app.forms.f2)
const [input, setInput] = useState({
value: reduxF2.PastReceivables.value,
valid: true,
})
const changeSelected = val => {
setInput({ ...input, value: val })
}
useEffect(() => {
setInput({ ...input, value: reduxF2.PastReceivables.value })
}, [reduxF2.PastReceivables.value])
useEffect(() => {
if (reduxF2.bulkSaved && props.currentStep === 2) {
dispatch(giveForm2NextStep(props.currentStep, props.goToStep))
dispatch(resetForm2SavedStatus())
}
}, [reduxF2.bulkSaved])
const startClick = e => {
if (input.value === null || input.value === '') {
setInput({ ...input, valid: false })
} else {
setInput({ ...input, valid: true })
const questions = getPastReceivablesArray('PastReceivables', input.value, reduxF2)
if (questions.length == 0) {
dispatch(giveForm2NextStep(props.currentStep, props.goToStep))
} else {
dispatch(updateForm2(questions))
}
}
}
return (
<>
<MyProgressBar now='8' />
<Question>Question here</Question>
<QuestionForm>
<NumericInput
valid={input.valid}
onChange={changeSelected}
value={input.value}
/>
</QuestionForm>
<div className='d-flex justify-content-between'>
<BackButton onClick={() => dispatch(giveForm2PreviousStep(props.currentStep, props.goToStep))} />
<NextButton onClick={startClick} loading={loading} />
</div>
<Warning error={error} />
</>
)
}
BackButton JS
const BackButton = ({ text = 'Back', onClick = null, loading = false, width = '7.5rem' }) => {
return (
<Button
variant='secondary'
className='back-button'
onClick={onClick}
disabled={loading}
style={{width}}
>
<MySpinner loading={loading} />
{!loading && <>{text}</>}
</Button>
)
}
You can call multiple functions in onClick event like below
<BackButton
onClick={(e) => {
startClick(e);
dispatch(giveForm2PreviousStep(props.currentStep, props.goToStep))
}}
/>
You can call multiple function in onclick or else you can send call backs to startclick.
so the call backs will be executed after startclick.
easy to give all the fucntions in onClick itself.
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>