I am creating a "smart shopping list", which sees how often I buy products and when I should buy next. At my current state, I want to keep the checkbox checked for 24 hours, so I am not able to uncheck it and it should be unchecked automatically after 24 hours (this is a criterion that I have to follow). How can I write something like setTimeout in my onChange function? I really could need some help here, thank you very much for responding.
Here is my code:
import React from 'react';
import firebase from '../lib/firebase';
import Nav from './Nav';
import { useCollectionData } from 'react-firebase-hooks/firestore';
const db = firebase.firestore().collection('shopping_list');
const ItemsList = () => {
const userToken = localStorage.getItem('token');
const [shoppingList, loading, error] = useCollectionData(
db.where('token', '==', userToken),
{ idField: 'documentId' },
);
const markItemAsPurchased = (index) => {
const { items, documentId } = shoppingList[0];
const shoppingListObject = items[index];
if (shoppingListObject.lastPurchasedOn !== null) {
return;
}
shoppingListObject.lastPurchasedOn = Date.now();
items[index] = shoppingListObject;
db.doc(documentId)
.update({
items: items,
})
.then(() => console.log('Successfully updated item'))
.catch((e) => console.log('error', e));
};
return (
<div>
<h1>Your Shopping List</h1>
<div>
{loading && <p>Loading...</p>}
{error && <p>An error has occured...</p>}
{shoppingList && !shoppingList.length && (
<p>You haven't created a shopping list yet...</p>
)}
<ul>
{shoppingList &&
shoppingList[0] &&
shoppingList[0].items.map((shoppingItemObject, index) => {
return (
<li key={shoppingItemObject.shoppingListItemName + index}>
<label>
{shoppingItemObject.shoppingListItemName}
<input
type="checkbox"
onChange={() => markItemAsPurchased(index)}
checked={
shoppingItemObject.lastPurchasedOn === null
? false
: true
}
/>
</label>
</li>
);
})}
</ul>
</div>
<Nav />
</div>
);
};
export default ItemsList;
Thanks to everyone who tried to help me. I already got the answer, pls check it out in case you need this function too :)
I created a new function for the time:
const wasItemPurchasedWithinLastOneDay = (lastPurchasedOn) => {
const oneDayInMilliseconds = 24 * 60 * 60 * 1000;
return (Date.now() - lastPurchasedOn) <= oneDayInMilliseconds;
}
I used Date.now() in the if/else statement, to set the value of the actual date the item was purchased:
const markItemAsPurchased = (index) => {
const { items, documentId } = shoppingList[0];
const shoppingListObject = items[index];
if (shoppingListObject.lastPurchasedOn === null) {
shoppingListObject.lastPurchasedOn = Date.now();
} else {
shoppingListObject.lastPurchasedOn = null;
}
I changed the function on checked as followed:
checked={
shoppingItemObject.lastPurchasedOn === null
? false : wasItemPurchasedWithinLastOneDay(shoppingItemObject.lastPurchasedOn) }
It now checks if the date of the last value is more than 24 hours and unchecks the checkbox if it is.
Related
I'm new to REACT.. I'm trying todo list websites based on the video for beginner react. But some function that has been use in REACT, which is id, I cannot understand
todo listList.length === 0 ? 1 : todoList[todoList.length -1].id + 1,
this function is assigned to add tasks in add list, but I don't know how it works using this code. If someone can kindly explain this to me, it will be a great of help. I keep on repeating on this part in the video, but still, it is hard to understand. Thank you.
This is the full code for app.js
import "./App.css";
import {useState} from "react";
import { Task } from "./Task"
function App() {
const [todoList, setTodoList] = useState([]);
const [newTask, setNewTask] = useState("");
const handleChange = (event) => {
setNewTask(event.target.value);
};
const addTask = () => {
const task = {
id: todoList.length === 0 ? 1 : todoList[todoList.length -1].id + 1,
taskName: newTask,
completed: false,
};
setTodoList([...todoList, task]);
};
const deleteTask = (id) => {
setTodoList(todoList.filter((task) => task.id !== id));
};
const completeTask = (id) => {
setTodoList(
todoList.map((task)=> {
if(task.id === id) {
return { ...task, completed: true };
} else {
return task;
}
})
);
};
return (
<div className="App">
<div className="addTask">
<input onChange={handleChange} />
<button onClick={addTask}> Add task</button>
</div>
<div cl assName="list">
{todoList.map((task) => {
return (
<Task
taskName={task.taskName}
id={task.id}
completed={task.completed}
deleteTask={deleteTask}
completeTask={completeTask}
/>
);
})}
</div>
</div>
);
}
export default App;
This is for task.js
export const Task = (props) => {
return (
<div
className="task"
style={{ backgroundColor: props.completed ? "pink" : "white"}}
>
<h1>{props.taskName}</h1>
<button Onclick={() => props.completeTask(props.id)}> Complete </button>
<button onClick={() => props.deleteTask(props.id)}> X </button>
</div>
);
}
Okay, it's really simple. You need to generate a special id for every task. one of the easiest ways you can do that is to generate an id from 1 to how many you want or need. here todoList.length === 0 ? 1 : todoList[todoList.length -1].id + 1 you first check if there is any task assigned or not. if not you generate the first id and it's 1. from here each task you add will get the last task id and add 1 number to it. so by this definition, the first task will have 1 as id and the second will have 2 as id.
import React, {useState} from 'react'
import Result from './Result'
import './Exam.css'
function Exam() {
const data = JSON.parse(localStorage.getItem('Que'))
const selectedAns = JSON.parse(localStorage.getItem('Ans'))
const [current, setCurrent] = useState(0)
const [ans, setAns] = useState({selectedAns:""})
const [correct, setCorrect] = useState(0)
const [exit, setExit] = useState(false)
const input = (e) => {
//setAns((ans) => ans, e.target.value)
setAns({...ans,
[e.target.name]: e.target.value})
}
console.log(ans);
const handlePrevious = (e) => {
setCurrent(current - 1)
let allRadio = document.querySelectorAll('.radioButton')
console.log(selectedAns[(current)].selectedAns);
allRadio.forEach(value => {
if(selectedAns[current].selectedAns === value.value){
value.checked = true
}
})
}
const handlerSave = () => {
if (data[current].answer === ans) {
setCorrect(correct + 1)
}
if ((current + 1) === data.length){
setExit(true)
}
else {
setCurrent(current + 1)
}
let allRadio = document.querySelectorAll('.radioButton')
allRadio.forEach(value => value.checked = false)
const answers = JSON.parse(localStorage.getItem("Ans") || "[]")
answers.push(ans)
localStorage.setItem('Ans', JSON.stringify(answers))
}
return(
<div>
{ exit === false ?
<div className='mcq'>
<div>
Question :<span className='span'>{current + 1}</span>
{data[current].question}?
</div><br/>
<div>
<div className='option'><input type='radio' value='A' name='selectedAns' onChange={input} className='radioButton'/> {data[current].A} </div>
<div className='option'><input type='radio' value='B' name='selectedAns' onChange={input} className='radioButton'/> {data[current].B} </div>
<div className='option'><input type='radio' value='C' name='selectedAns' onChange={input} className='radioButton'/> {data[current].C} </div>
<div className='option'><input type='radio' value='D' name='selectedAns' onChange={input} className='radioButton'/> {data[current].D} </div>
</div><br/>
<div>
{ current !== 0 && <button onClick={handlePrevious}>Previous</button>}
<button onClick={handlerSave}>Save & Next</button>
</div>
</div> :
<Result props={{correct}}/>
}
</div>
)
}
export default Exam;
I am making a quiz app in react js. I am facing an issue which is that I have hundreds of questions with four options, and also next and previous buttons in that. For options, I am using radio buttons. I want that after the user selects an option for the first question, goes to the next question and selects an option for the second question, he will be able to go to the previous question, and there the radio button will be checked with the option the user has been selected earlier.
i have tried this function.
but it gives me diffrent button checked
const handlePrevious = (e) => { setCurrent(current - 1) let allRadio = document.querySelectorAll('.radioButton') console.log(ans); allRadio.forEach(value => { if(ans === value.value){ value.checked = true } }) }
You can create a hook with the help of useState and add default type as object.
At question -1 index you can store your value. OnSelectRadio function will accept event and index which will be called when you click on any of the radio button.
const[ans, setAns] = useState({}):
onSelectRadio(event, idx){
let temp = [..ans];
temp[idx+1] = event.target.value;
setAns(temp);
}
I have made another function to check status of my radio button...
if have any other query please check my full code first and then my solution.
const handlePrevious = (e) => {
setCurrent(current - 1)
Previous()
}
const Previous = () => {
let allRadio = document.querySelectorAll('.radioButton')
allRadio.forEach(value => {
if(selectedAns[current-1].index === value.value){
value.checked = true
}
})
}
I made my basic ToDo list site after learning react. I wanted to add a function of saving the items on reload. I am a beginner in react so I am facing difficulty in this. I tried the following code:
import React from "react";
import ToDoList from "./components/ToDoList";
import Navbar from './components/Navbar'
import '../src/App.css'
export default function TodoInput() {
const saveLocalTasks = () => {
let savedTasks = localStorage.getItem('tasks')
console.log(savedTasks)
if (savedTasks) {
return JSON.parse(localStorage.getItem('tasks'))
} else {
return []
}
}
const [task, setTask] = React.useState('')
const [count, setCount] = React.useState(0)
const [taskList, setTaskList] = React.useState([saveLocalTasks()])
const [disable, setDisable] = React.useState(true)
const [viewTaskList, setViewTaskList] = React.useState(true)
const updateTaskList = () => {
setTaskList([...taskList, {object: task, key: Date.now()}])
setTask('')
setViewTaskList(false)
setCount(count + 1)
setDisable(true)
}
const inputValue = e => {
setTask(e.target.value)
e.target.value === '' || task === '' || task.length === 0
?
setDisable(true)
:
setDisable(false)
}
// console.log(task.length)
React.useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(taskList.object))
}, [taskList])
return (
<div>
<Navbar />
<header>
<div className="todolist-border">
<div className="todo-input-form">
<input
className = "inputText"
placeholder="Add a Task"
value={task}
onChange = {inputValue}
/>
<button disabled = {disable} onClick = {updateTaskList} className="todo-add-button">+</button>
</div>
{
viewTaskList || count === 0
?
<div className="pendingTasks-div">
<img className = "pending-task-image"
src= "https://dm0qx8t0i9gc9.cloudfront.net/watermarks/image/rDtN98Qoishumwih/task-pending-cartoon-business-vector-illustrations_zJCs81OO_SB_PM.jpg"
alt="pending-tasks" />
<p className="no-task-message">There are no pending tasks!! #Enjoy🥳🥳</p>
</div>
:
<ToDoList count = {count} setCount = {setCount} task = {task} taskList = {taskList} setTaskList = {setTaskList}/>
}
</div>
</header>
</div>
)
}
But the following error is coming up:
The following is the code for ToDoList component:
import React from "react";
export default function ToDoList(props) {
const deleteTaskListItem = (key) => {
const updatedList = props.taskList.filter((item) => {
return (
item.key !== key
)
})
props.setTaskList(updatedList)
props.setCount(props.count - 1)
}
return(
<div>
{props.taskList.map((item) => {
return (
<div key = {item.key} className="todolist-div">
<input type="checkbox" className="list-checkbox">
</input>
<p>{item.object}</p>
<button onClick={()=>deleteTaskListItem(item.key)} className="delete-button">X</button>
</div>
)
})}
</div>
)
}
Kindly suggest a method to add this feature.
The above error happens when you try to JSON.parse undefined. Check this link. Here, I tried to do some changes in your code on CodeSandbox. There you can find some changes I have made.
Firstly, you shouldn't try to set data in this useState const [taskList, setTaskList] = React.useState([saveLocalTasks()]). You should set data in useEffect.
In the following code, you are trying to save taskList.object but taskList is an array. The below code will throw an error.
React.useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(taskList.object))
}, [taskList])
As you asked in your question, you want to try to save data when the user reloads the window. You can achieve this by using window.onbeforeunload event (line 48).
Hope the above will help you.
P.S: The codesandbox code I shared isn't fully functional. I have made just some changes that will help you to go ahead with your coding. Thank you.
could you please help me,
I add item by clicking "On" button but instead of adding 1 item it add 2 items at once, video: https://www.loom.com/share/2f9d0b48817b4480934de63a781ed6a9
Could you please help why and how to add just 1 item?
StepForm.js:
import React from 'react';
import { useState } from "react";
import OneDay from "./OneDay";
function StepsForm () {
const [form, setForm] = useState ({
date: '',
distance: ''
});
const [dataList, setList] = useState([
{id: '1613847600000', date: '21.01.2021', distance: '3.8'},
{id: '1624579200000', date: '25.05.2021', distance: '5.7'},
{id: '1642723200000', date: '21.12.2021', distance: '1.8'}
]);
const handleChange = ({target}) => {
const name = target.name;
const value = target.value;
setForm(prevForm => ({...prevForm, [name]: value}));
}
function dateValue (data) {
const year = data.substring(6,10);
const month = data.substring(3,5);
const day = data.substring(0,2);
const id = new Date(year, month, day).valueOf();
return `${id}`;
}
const handleSubmit = evt => {
evt.preventDefault();
const newData = {
id: dateValue (form.date),
date: form.date,
distance: form.distance
}
const index = dataList.findIndex((item) => item.id === newData.id);
setList(prevList => {
if (index === -1) {
prevList.push(newData);
prevList
.sort((a, b) => {return a.id - b.id})
.reverse();
} else {
prevList[index].distance = String(prevList[index].distance * 1 + newData.distance * 1)
}
return [...prevList];
})
setForm({date: '', distance: ''});
}
const clickToDelete = (evt) => {
const index = dataList.findIndex((item) => item.id === evt.target.dataset.id);
if (index === -1) {
console.log('Что-то пошло не так')
return;
}
setList(prevList => {
prevList.splice(index, 1);
return [...prevList];
})
}
return (
<div className="box">
<form onSubmit={handleSubmit}>
<div className="formBox">
<div className="inputBox">
<label htmlFor="date">Дата (ДД.ММ.ГГ)</label>
<input id="date" name="date" onChange={handleChange} value={form.date} />
</div>
<div className="inputBox">
<label htmlFor="distance">Пройдено км</label>
<input id="distance" name="distance" onChange={handleChange} value={form.distance} />
</div>
<button className="btn" type="submit">Ok</button>
</div>
</form>
<div className="headings">
<div className="heading">Дата (ДД.ММ.ГГ)</div>
<div className="heading">Пройдено км</div>
<div className="heading">Действия</div>
</div>
<div className="table">
{dataList.map(
o => <OneDay key={o.id} item={o} delDay={clickToDelete} />
)}
</div>
</div>
)
}
export default StepsForm;
App.js:
import React from 'react';
import StepsForm from './StepsForm';
import './style.css';
export default function App() {
return (
<div className="App">
<StepsForm />
</div>
);
}
export default App;
I add item by clicking "On" button but instead of adding 1 item it add 2 items at once, video: https://www.loom.com/share/2f9d0b48817b4480934de63a781ed6a9
Could you please help why and how to add just 1 item?
adding elements into the array seems working.
but, there might be an issue with dateValue(data) as it can returns NaN as an id for the element which might cause a problem with the rendering element on the screen.
function dateValue(data) {
const year = data.substring(6, 10);
const month = data.substring(3, 5);
const day = data.substring(0, 2);
const id = new Date(year, month, day).valueOf();
return `${id}`; // this return id as NaN
}
// You can create Uid like code example below
function dateValue() {
const id = new Date().valueOf();
return `${id}`;
}
Hope this solves the problem.
I'm pulling countries from the Restcountries API and if the current state of the array has more than one or less than or equal to ten countries, I want to list the country names along with a 'show' button next to each one. The show button should display what's in the return (render) of my Country function. In the App function, I wrote a handler for the button named handleViewButton. I'm confused on how to filter the element in the Countries function in the else conditional statement in order to display the Country. I tried passing handleViewButton to the Button function, but I get an error 'Uncaught TypeError: newSearch.toLowerCase is not a function'. I really just want to fire the Country function to display the country button that was pressed.
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import axios from 'axios';
const Country = ({country}) => {
return (
<>
<h2>{country.name}</h2>
<p>capital {country.capital}</p>
<p>population {country.population}</p>
<br/>
<h3>languages</h3>
{country.languages.map(language => <li key={language.name}>{language.name}</li>)}
<br/>
<img src={country.flag} alt="country flag" style={{ width: '250px'}}/>
</>
);
}
const Countries = ({countries, handleViewButton}) => {
const countriesLen = countries.length;
console.log(countriesLen)
if (countriesLen === 0) {
return (
<p>Please try another search...</p>
);
} else if (countriesLen === 1) {
return (
<ul>
{countries.map((country, i) => <Country key={i} countriesLen={countriesLen} country={country}/>)}
</ul>
);
} else if (countriesLen > 10) {
return (
<div>
<p>Too many matches, specify another filter</p>
</div>
);
} else {
return (
<ul>
{countries.map((country, i) => <li key={i}>{country.name}<Button handleViewButton={handleViewButton}/></li>)}
</ul>
)
};
};
const Button = ({handleViewButton}) => {
return (
<button onClick={handleViewButton}>Show</button>
);
};
const Input = ({newSearch, handleSearch}) => {
return (
<div>
find countries <input value={newSearch} onChange={handleSearch}/>
</div>
);
};
function App() {
const [countries, setCountries] = useState([]);
const [newSearch, setNewSearch] = useState('');
const handleSearch = (event) => {
const search = event.target.value;
setNewSearch(search);
};
const handleViewButton = (event) => {
const search = event.target.value;
setNewSearch(countries.filter(country => country === search));
};
const showCountrySearch = newSearch
? countries.filter(country => country.name.toLowerCase().includes(newSearch.toLowerCase()))
: countries;
useEffect(() => {
axios
.get('https://restcountries.eu/rest/v2/all')
.then(res => {
setCountries(res.data);
console.log('Countries array loaded');
})
.catch(error => {
console.log('Error: ', error);
})
}, []);
return (
<div>
<Input newSearch={newSearch} handleSearch={handleSearch}/>
<Countries countries={showCountrySearch} handleViewButton={handleViewButton}/>
</div>
);
};
export default App;
you can use a displayCountry to handle the country that should be displayed. Most often you would use an id, but I'm using here country.name since it should be unique.
Then you would use matchedCountry to find against your list of countries.
After that, a onHandleSelectCountry to select a given country. if it's already selected then you could set to null to unselect.
Finally, you would render conditionally your matchedCountry:
const Countries = ({countries}) => {
const [displayCountry, setDisplayCountry] = useState(null);
const countriesLen = countries.length;
const matchedCountry = countries.find(({ name }) => name === displayCountry);
const onHandleSelectCountry = (country) => {
setDisplayCountry(selected => {
return selected !== country.name ? country.name : null
})
}
if (countriesLen === 0) {
return (
<p>Please try another search...</p>
);
} else if (countriesLen === 1) {
return (
<ul>
{countries.map((country, i) => <Country key={i} countriesLen={countriesLen} country={country}/>)}
</ul>
);
} else if (countriesLen > 10) {
return (
<div>
<p>Too many matches, specify another filter</p>
</div>
);
} else {
return (
<>
<ul>
{countries.map((country, i) => <li key={i}>{country.name}<Button handleViewButton={() => onHandleSelectCountry(country)}/></li>)}
</ul>
{ matchedCountry && <Country countriesLen={countriesLen} country={matchedCountry}/> }
</>
)
};
};
I can only help to point out some guidelines.
First: The button does not have value attribute. Hence what you will get from event.target.value is always blank.
const Button = ({handleViewButton}) => {
return (
<button onClick={handleViewButton}>Show</button>
);};
First->Suggestion: Add value to the button, of course you need to pass the value in.
const Button = ({handleViewButton, value}) => {
return (
<button onClick={handleViewButton} value={value}>Show</button>
);};
Second: To your problem 'Uncaught TypeError: newSearch.toLowerCase is not a function'. Filter always returns an array, not a single value. if you do with console or some sandbox [1,2,3].filter(x=>x===2) you will get [2] not 2.
const handleViewButton = (event) => {
const search = event.target.value;
setNewSearch(countries.filter(country => country === search));
};
Second->Suggestion: To change it to get the first element in array, since country(logically) is unique.
const result = countries.filter(country => country === search)
setNewSearch(result.length>0?result[0]:"");
A better approach for array is find, which always return first result and as a value. E.g. [1,2,2,3].find(x=>x===2) you will get 2 not [2,2] or [2].
countries.find(country => country === search)