Sort tasks after filtering in React Firebase to do list app - reactjs

I can't figure out how to sort tasks by the "created" value after filtering them in a React Firebase to-do list app I'm building. I've tried adding the following line to the database query but it's not working:
const q = query(collection(db, 'tasks'), where("uid", "==", user?.uid), orderBy("created"));
Here is the full code, minus the imports:
export default function Todo() {
const [name, setName] = useState("");
const [tasks, setTasks] = useState([]);
const [user, loading, error] = useAuthState(auth);
const [searchInput, setSearchInput] = useState("");
const [filteredTasks, setFilteredTasks] = useState([]);
//function for automatically retrieving items
useEffect(() => {
const q = query(collection(db, 'tasks'), where("uid", "==", user?.uid));
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
}, [])
//function for loading filtered tasks
useEffect(() => {
setFilteredTasks(tasks
.filter(task => task.data.name.match(searchInput))
);
}, [searchInput, tasks])
//function for getting the value of the main input
function handleChange(e) {
e.preventDefault();
setName(e.target.value);
console.log(name);
}
//function for getting the value of the search input
function handleSearchChange(e) {
e.preventDefault();
setSearchInput(e.target.value)
}
//function for adding items to firestore
const handleAdd = async (e) => {
e.preventDefault();
if (name === "") {
alert("Please enter some text");
clearInput();
return;
}
try {
await addDoc(collection(db, 'tasks'), {
name: name,
completed: false,
created: Timestamp.now(),
uid: user?.uid,
})
clearInput();
} catch (err) {
alert(err)
}
}
//function for adding strikethrough to an item
function handleClick(e) {
if (e.detail === 2) {
console.log("double click");
e.currentTarget.classList.toggle('double-clicked');
}
}
//function for updating an item
const handleUpdate = async (taskName, id) => {
let name = prompt("Please enter a new name", taskName);
if (name === null) {
return;
}
const taskDocRef = doc(db, 'tasks', id)
try {
await updateDoc(taskDocRef, {
name: name,
})
clearInput();
} catch (err) {
alert(err)
}
}
//function for deleting an item
const handleDelete = async (id) => {
console.log(id);
const taskDocRef = doc(db, 'tasks', id)
try {
await deleteDoc(taskDocRef)
clearInput();
} catch (err) {
alert(err)
}
}
//function for clearing and focusing the input
function clearInput() {
let input = document.querySelector("input");
input.value = '';
input.focus();
setName("");
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off.</p>
</div>
<input
id="input"
type="text"
value={name}
onChange={handleChange}
autoFocus
/>
<button
className="add-button"
type="submit"
onClick={handleAdd}
>
<IoMdAddCircle />
</button>
</div>
<ol>
{filteredTasks.map(task => (
<li
className="task-list-items"
id={task.id}
key={task.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.data.name}
<button
className="edit-button"
onClick={() => handleUpdate(task.data.name, task.id)}
>
<BsFillPencilFill />
</button>
<button
className="delete-button"
onClick={() => handleDelete(task.id)}
>
<BsFillTrashFill />
</button>
</li>
))}
</ol>
<div>
<h5>Search for an item by name</h5>
<input
id="search-bar"
type="text"
value={searchInput}
onChange={handleSearchChange}
/>
</div>
</div>
);
};
When I add new tasks they are appearing in the wrong order even if they are not filtered.
This is what the database looks like:

I figured out how to sort the tasks after filtering. Here is my updated code:
export default function Todo() {
const [name, setName] = useState("");
const [tasks, setTasks] = useState([]);
const [user, loading, error] = useAuthState(auth);
const [searchInput, setSearchInput] = useState("");
const [filteredTasks, setFilteredTasks] = useState([]);
//function for automatically retrieving items
useEffect(() => {
const q = query(collection(db, 'tasks'), where("uid", "==", user?.uid));
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
}, [])
//function for loading filtered tasks
useEffect(() => {
setFilteredTasks(tasks
.filter(task => task.data.name.match(searchInput))
);
}, [searchInput, tasks])
//function for getting the value of the main input
function handleChange(e) {
e.preventDefault();
setName(e.target.value);
console.log(name);
}
//function for getting the value of the search input
function handleSearchChange(e) {
e.preventDefault();
setSearchInput(e.target.value)
}
//function for adding items to firestore
const handleAdd = async (e) => {
e.preventDefault();
if (name === "") {
alert("Please enter some text");
clearInput();
return;
}
try {
await addDoc(collection(db, 'tasks'), {
name: name,
completed: false,
created: Timestamp.now(),
uid: user?.uid,
})
clearInput();
} catch (err) {
alert(err)
}
}
//function for adding strikethrough to an item
function handleClick(e) {
if (e.detail === 2) {
console.log("double click");
e.currentTarget.classList.toggle('double-clicked');
}
}
//function for updating an item
const handleUpdate = async (taskName, id) => {
let name = prompt("Please enter a new name", taskName);
if (name === null) {
return;
}
const taskDocRef = doc(db, 'tasks', id)
try {
await updateDoc(taskDocRef, {
name: name,
})
clearInput();
} catch (err) {
alert(err)
}
}
//function for deleting an item
const handleDelete = async (id) => {
console.log(id);
const taskDocRef = doc(db, 'tasks', id)
try {
await deleteDoc(taskDocRef)
clearInput();
} catch (err) {
alert(err)
}
}
//function for clearing and focusing the input
function clearInput() {
let input = document.querySelector("input");
input.value = '';
input.focus();
setName("");
}
//function for sorting tasks after they are filtered
function sortTasks(array) {
array.sort((a, b) => (a.data.created < b.data.created ? -1 : 1));
return array;
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off.</p>
</div>
<input
id="input"
type="text"
value={name}
onChange={handleChange}
autoFocus
/>
<button
className="add-button"
type="submit"
onClick={handleAdd}
>
<IoMdAddCircle />
</button>
</div>
<ol>
{sortTasks(filteredTasks).map(task => (
<li
className="task-list-items"
id={task.id}
key={task.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.data.name}
<button
className="edit-button"
onClick={() => handleUpdate(task.data.name, task.id)}
>
<BsFillPencilFill />
</button>
<button
className="delete-button"
onClick={() => handleDelete(task.id)}
>
<BsFillTrashFill />
</button>
</li>
))}
</ol>
<div>
<h5>Search for an item by name</h5>
<input
id="search-bar"
type="text"
value={searchInput}
onChange={handleSearchChange}
/>
</div>
</div>
);
};

Related

Is assigned a value but never used

My code keeps bringing torusPlugin is assigned a value but never used, LoggedInView is assigned a value but never used and I don't know where I went wrong. I have checked the code and the torusPlugin function was used, same with the loggedInView.
import { useEffect, useState } from "react";
import { Card, Form } from "react-bootstrap";
import { FaComment, FaRecycle, FaRetweet, FaThumbsUp } from "react-icons/fa";
import { Web3AuthCore } from "#web3auth/core";
import {
WALLET_ADAPTERS,
CHAIN_NAMESPACES,
SafeEventEmitterProvider,
} from "#web3auth/base";
import { OpenloginAdapter } from "#web3auth/openlogin-adapter";
import { TorusWalletConnectorPlugin } from "#web3auth/torus-wallet-connector-plugin";
import Twitter from "./twitter";
import RPC from "./evm";
import { APP_CONSTANTS } from "./constants";
import "./App.css";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
const clientId = APP_CONSTANTS.CLIENT_ID; // get from https://dashboard.web3auth.io
function App() {
const [web3auth, setWeb3auth] = useState<Web3AuthCore | null>(null);
const [provider, setProvider] = useState<SafeEventEmitterProvider | null>(
null
);
const [tweets, setTweets] = useState<Array<any> | null>(null);
const [comment, setComment] = useState<string | "">("");
const [userName, setUserName] = useState<string | "">("");
const [profileImage, setProfileImage] = useState<string | "">("");
const [newTweetName, setNewTweetName] = useState<string | "">("");
const [newTweetDescription, setNewTweetDescription] = useState<string | "">(
""
);
const refreshTime = APP_CONSTANTS.REACT_APP_REFRESH_TIMER * 1000
const [torusPlugin, setTorusPlugin] =
useState<TorusWalletConnectorPlugin | null>(null);
useEffect(() => {
const init = async () => {
try {
const web3auth = new Web3AuthCore({
clientId,
chainConfig: {
chainNamespace: CHAIN_NAMESPACES.EIP155,
chainId: "0x13881",
rpcTarget: APP_CONSTANTS.RPC_TARGET, // This is the mainnet RPC we have added, please pass on your own endpoint while creating an app
},
});
const openloginAdapter = new OpenloginAdapter({
adapterSettings: {
clientId,
network: "testnet",
uxMode: "popup",
whiteLabel: {
name: "Twitter DApp",
logoLight: APP_CONSTANTS.APP_LOGO,
logoDark: APP_CONSTANTS.APP_LOGO,
defaultLanguage: "en",
dark: true, // whether to enable dark mode. defaultValue: false
},
loginConfig: {
// Add login configs corresponding to the providers on modal
// Twitter login
jwt: {
name: "Custom Auth Login",
verifier: APP_CONSTANTS.ADAPTER_TWITTER_CLIENT_VERIFIER, // Please create a verifier on the developer dashboard and pass the name here
typeOfLogin: "twitter", // Pass on the login provider of the verifier you've created
clientId: APP_CONSTANTS.ADAPTER_TWITTER_CLIENT_ID, // Pass on the clientId of the login provider here - Please note this differs from the Web3Auth ClientID. This is the JWT Client ID
},
// Add other login providers here
},
},
});
const torusPlugin = new TorusWalletConnectorPlugin({
torusWalletOpts: {},
walletInitOptions: {
whiteLabel: {
theme: { isDark: true, colors: { primary: "#ffffff" } },
logoDark:
"https://i.ibb.co/kDNCfC9/reshot-icon-wallet-9-H3-QMSDLFR.png",
logoLight:
"https://i.ibb.co/kDNCfC9/reshot-icon-wallet-9-H3-QMSDLFR.png",
},
useWalletConnect: true,
enableLogging: true,
},
});
await web3auth.addPlugin(torusPlugin);
setTorusPlugin(torusPlugin);
await web3auth.configureAdapter(openloginAdapter);
setWeb3auth(web3auth);
await web3auth.init();
if (web3auth.provider) {
await setProvider(web3auth.provider);
let user = await web3auth.getUserInfo();
console.log('user ', user)
if(user.name && user.name !== null && user.name !== " " && user.name !== "")
setUserName(user.name)
if(user.profileImage && user.profileImage !== null && user.profileImage !== " " && user.profileImage !== "")
setProfileImage(user.profileImage)
}
await fetchAllTweets();
//eslint-disable-next-line react-hooks/exhaustive-deps
} catch (error) {
console.error(error);
}
};
init();
}, []);
const logout = async () => {
if (!web3auth) {
console.log("web3auth not initialized yet");
return;
}
await web3auth.logout();
setProvider(null);
};
const login = async () => {
if (!web3auth) {
console.log("web3auth not initialized yet");
return;
}
const web3authProvider = await web3auth.connectTo(
WALLET_ADAPTERS.OPENLOGIN,
{
loginProvider: "jwt",
extraLoginOptions: {
domain: APP_CONSTANTS.AUTH0_DOMAIN, // Please append "https://" before your domain
verifierIdField: "sub",
},
}
);
setProvider(web3authProvider);
if(web3authProvider){
let user = await web3auth.getUserInfo();
if(user.name && user.name !== null && user.name !== " " && user.name !== "")
setUserName(user.name)
if(user.profileImage && user.profileImage !== null && user.profileImage !== " " && user.profileImage !== "")
setProfileImage(user.profileImage)
}
};
/*
const getAccounts = async () => {
if (!provider) {
console.log("provider not initialized yet");
return;
}
const rpc = new RPC(provider);
const userAccount = await rpc.getAccounts();
return userAccount;
};
*/
const refresh = (e: any) => {
e.preventDefault();
fetchAllTweets();
};
const fetchAllTweets = async () => {
console.log("fetchalltweetsrunning");
if (!provider) {
console.log("provider not initialized yet");
return;
}
const rpc = new RPC(provider);
try {
let fetchedTweets = await rpc.getAllTweets();
let tweets = [...fetchedTweets];
setTweets(tweets.reverse());
} catch (error) {
console.log("error in fetching tweets", error);
}
};
const upVote = async (tweetIndex: any) => {
if (!provider) {
console.log("provider not initialized yet");
return;
}
try {
const rpc = new RPC(provider);
await rpc.sendUpVoteTransaction(tweetIndex);
fetchAllTweets();
} catch (error) {
console.log("failed to execute upvote transaction", error);
}
};
const addNewTweet = (e: any) => {
e.preventDefault();
if (!provider) {
console.log("provider not initialized yet");
return;
}
try {
const rpc = new RPC(provider);
toast.success("Tweet added successfully", {
position: toast.POSITION.TOP_CENTER,
});
rpc.sendWriteTweetTransaction(newTweetName, newTweetDescription);
setTimeout(function () {
fetchAllTweets();
}, refreshTime);
fetchAllTweets();
} catch (error) {
toast.error("Something went wrong", {
position: toast.POSITION.TOP_LEFT,
});
console.log("failed to execute new tweet transaction", error);
}
};
const addComment = async (event: any, tweetIndex: any) => {
event.preventDefault();
if (!provider) {
console.log("provider not initialized yet");
return;
}
try {
const rpc = new RPC(provider);
toast.success("Comment added successfully - refresh after 30 sec", {
position: toast.POSITION.TOP_CENTER,
});
await rpc.sendAddCommentTransaction(tweetIndex, comment);
fetchAllTweets();
} catch (error) {
toast.error("Something went wrong", {
position: toast.POSITION.TOP_LEFT,
});
console.log("failed to execute add comment transaction", error);
}
};
// Event handlers
const handleCommentChange = async (event: any) => {
setComment(event.target.value);
};
const handleNewTweetNameChange = async (event: any) => {
setNewTweetName(event.target.value);
};
const handleNewTweetDescriptionChange = async (event: any) => {
setNewTweetDescription(event.target.value);
};
const loggedInView = (
<>
<button className="button" onClick={logout}>
Logout
</button>
<div>
<h1>New Tweet</h1>
<Card>
<Card.Body>
<Card.Title>What are you thinking? Tweet it out!</Card.Title>
<Card.Text></Card.Text>
<Form.Control
as="input"
onChange={handleNewTweetNameChange}
placeholder="Tweet Name"
/>
<br></br>
<br></br>
<Form.Control
as="textarea"
onChange={handleNewTweetDescriptionChange}
placeholder="Description"
/>
<br></br>
<FaRetweet onClick={addNewTweet} />
</Card.Body>
</Card>
</div>
<div>
<h1>
All Tweets <FaRecycle onClick={fetchAllTweets} />
</h1>
{(tweets || []).map((tweet: any, i) => (
<div key={i}>
<div>
<Card>
<Card.Body>
<Card.Title>
<FaThumbsUp onClick={(event) => upVote(i)} /> {tweet.name}
</Card.Title>
<p>Total Upvotes: {tweet.upvotes}</p>
<p>Tweeted by: {tweet.fromAddress}</p>
<Card.Text>{tweet.description}</Card.Text>
<div>
<h3>All Comments</h3>
{tweet.comments.map((comment: any, j: any) => (
<div key={j}>
Comment {j + 1}: {comment}
</div>
))}
<h3>New Comment</h3>
<span>
<Form.Control
as="input"
onChange={handleCommentChange}
placeholder="Your comment..."
/>
</span>
<span>
<FaComment onClick={(event) => addComment(event, i)} />
</span>
</div>
</Card.Body>
<a
href={
APP_CONSTANTS.OPENSEA_ASSETS_URL +
"/" +
APP_CONSTANTS.CONTRACT_ADDRESS +
"/" +
i
}
rel="opener"
>
Buy Now
</a>
</Card>
</div>
</div>
))}
</div>
<div></div>
<div id="console" style={{ whiteSpace: "pre-line" }}>
<p style={{ whiteSpace: "pre-line" }}></p>
</div>
</>
);
const unloggedInView = (
<>
<div className="login-account">
<button className="twitter-bg btn" onClick={login}>
<img src="images/twitter-white.png" alt=""></img>
Login to your Twitter account
</button>
</div>
</>
);
return (
<div className="grid">
{provider ? (
<Twitter
logoutButton={logout}
handleNewTweetDescriptionChange={handleNewTweetDescriptionChange}
handleNewTweetNameChange={handleNewTweetNameChange}
addNewTweet={addNewTweet}
fetchAllTweets={fetchAllTweets}
tweets={tweets}
upVote={upVote}
handleCommentChange={handleCommentChange}
addComment={addComment}
refresh={refresh}
username={userName}
profileimage={profileImage}
/>
) : (
unloggedInView
)}{" "}
<ToastContainer />
</div>
// <div className="grid">{provider
// ? loggedInView
// : unloggedInView}</div>
// {/* <div className="grid">{loggedInView}</div> */}
);
}
export default App;
enter image description here
this is a screenshot of my code terminal
You are actually not using torusPlugin (line 38 screenshot) anywhere. Inside your useEffect you created a new const torusPlugin and then you are calling that const instead of the [torusPlugin, ...] state you declared on line 38 of the screenshot. Try to differentiate your variables / constants names otherwise would be difficult for you to identify mistakes.

once deleting out of input, focus previous input react

I am having issues trying to focus on a previous input once I delete out of the input that I am in. I have 6 inputs, all with a maxLength of 1. When I press the backspace key I would like to delete the value and move to the previous input. I have tried a variety of things but nothing seems to work how I need.
Here is my code
This is the Auth component which is passing props to verify (the page with the inputs)
const Auth = ({ sub }) => {
let params = useParams();
const navigate = useNavigate();
const [rec, setRec] = useState({
accept: false,
email: '',
phone: '',
pin: ['', '', '', '', '', '']
})
const onPaste = (event) => {
event.preventDefault()
const pasted = event.clipboardData.getData("text/plain")
setRec({ ...rec, pin: pasted.split("").slice(0, rec.pin.length) })
// target event last sibling
event.target.parentNode.lastChild.focus()
}
function update(event, index) {
event.preventDefault()
setRec({
...rec, pin: [
...rec.pin.slice(0, index),
event.target.value,
...rec.pin.slice(index + 1)
]
})
}
const handleFocus = (event) => {
if (event.target.nextSibling)
event.target.nextSibling.focus();
// if value is deleted, focus previous sibling
// if all siblings are empty, focus first sibling
if (event.target.value === '' && event.target.previousSibling === null)
event.target.parentNode.firstChild.focus()
}
const onKeyPress = (event) => {
// if backspace is clicked, go to previous input
if (event.key === 'Backspace' && event.target.value === '') {
event.target.previousSibling.focus()
}
}
const handleChange = (name, value) => {
if (name === 'mobile') name = 'phone'
const recState = {
...rec,
[name]: value,
}
setRec(recState)
}
const toggleAccept = () => {
if (!rec.accept) {
setRec({
...rec,
accept: true
});
} else {
setRec({
...rec,
accept: false
});
}
}
const handleSubmit = async () => {
const { accept, email, phone } = rec
if (params.method === 'email' && !email) return app.actions.setError('Enter an email')
else if (params.method !== 'email' && !phone) return app.actions.setError('Enter a phone number')
const send = params.method === 'email' ? { email } : { phone }
if (!accept) return app.actions.setError('Please accept the terms & conditions')
try {
await app.actions.setLoading(true)
// if there is already a user, just login, else create user first
await user.actions.login(send)
await app.actions.setLoading(false)
navigate(`/auth/verify/login/${params.method || 'phone'}`)
} catch (e) {
if (e.response && e.response.status === 404) {
// try join
await user.actions.join(send)
await app.actions.setLoading(false)
return navigate(`/auth/verify/join/${params.method || 'phone'}`)
}
await app.actions.setLoading(false)
await app.actions.setError(e)
}
}
const handleVerify = async ({ context, method }) => {
const { email, phone, pin } = rec
const joinPin = pin.join('');
if (method === 'email' && !email) return app.actions.setError('Enter an email')
else if (method !== 'email' && !phone) return app.actions.setError('Enter a phone number')
const send = method === 'email' ? { email } : { phone }
await app.actions.setError(null)
try {
await app.actions.setLoading(true)
if (context === 'login') {
await user.actions.loginVerify({
...send,
pin: joinPin,
})
} else {
await user.actions.verify({
...send,
pin: joinPin,
})
}
await app.actions.init()
await app.actions.setLoading(false)
navigate('/')
} catch (e) {
await app.actions.setLoading(false)
await app.actions.setError(e)
}
}
return (
<>
{sub ?
<Verify
context={params.context}
handleChange={handleChange}
handleFocus={handleFocus}
handleSubmit={() => handleVerify({ context: params.context, method: params.method })}
onKeyPress={onKeyPress}
method={params.method}
paste={onPaste}
update={update}
rec={rec} />
:
<Jogin
handleChange={handleChange}
handleSubmit={handleSubmit}
method={params.method}
toggleAccept={toggleAccept}
rec={rec} />
}
</>
)
}
export default Auth
and here is the verify component
import React from 'react'
import { onFormSubmit } from '../../utility/form'
import { Navigate, useNavigate } from 'react-router-dom'
import user from '../../model/user'
import app from '../../model/app'
const Verify = ({ handleSubmit, method, rec, paste, update, handleFocus, onKeyPress }) => {
const navigate = useNavigate();
const { email, phone } = rec
const send = method === 'email' ? { email: email } : { phone: phone }
const resendPin = async () => {
try {
await app.actions.setLoading(true)
// if there is already a user, just login, else create user first
await user.actions.login(send)
await app.actions.setLoading(false)
navigate(`/auth/verify/login/${method || 'phone'}`)
} catch (e) {
if (e.response && e.response.status === 404) {
// try join
await user.actions.join(send)
await app.actions.setLoading(false)
return navigate(`/auth/verify/join/${method || 'phone'}`)
}
await app.actions.setLoading(false)
await app.actions.setError(e)
}
}
const renderContent = () => {
const methodLabel = method === 'email' ? 'email address' : 'phone number'
return (
<>
<div id="back-grad" />
<div className="col-md-8">
<div className='verify-text-section'>
<p className="verify-text pb-5">
Enter the PIN number we just sent to your {methodLabel}.
</p>
</div>
<div className="flex">
{rec.pin.map((s, key) => (
<input key={key} className='code-input mr-2' value={s} maxLength='1' onPaste={(e) => paste(e)} onKeyDown={e => onKeyPress(e)} onInput={(e) => update(e, key)} onChange={(e) => handleFocus(e)} inputMode='numeric' autoFocus={key === 0} />
))}
</div>
<div className="verify-resend mt-3" onClick={resendPin}>Resend Code</div>
<div className="flex margin-set text-center">
<button className="btn btn-block text-white verify-btn">
<p className="verify-btn-text">VERIFY</p>
</button>
</div>
</div>
</>
)
}
return (
<>
{rec.email.length < 1 && rec.phone.length < 1 ? <Navigate to={'/auth'} />
:
<div className="d-flex align-items-center h-full">
<form className="container" onSubmit={e => onFormSubmit(e, handleSubmit)}>
<div className="row justify-content-center">
{renderContent()}
</div>
</form>
</div>
}
</>
)
}
export default Verify
any help is appreciated!

Unhandled Rejection (TypeError): Cannot read properties of undefined (reading 'reduce')

I am working on yup validation. Whenever it comes to FileList validation, if I left the input blank, I got the following error:
enter image description here
Here is my code:
import React, { useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router';
import { useDispatch, useSelector } from '#app/hooks';
import { Button, Dimmer, Dropdown, DropdownItemProps, DropdownProps, Form, Header, Loader, Modal } from 'semantic-ui-react';
import { NewBook, TestBook, UpdateParam } from '#app/reducers/master-data/book/book.model';
import * as yup from "yup";
import { useForm } from 'react-hook-form';
import { yupResolver } from '#hookform/resolvers/yup';
import { useHistory } from 'react-router-dom';
import { _getAllGenres } from '#app/reducers/master-data/genre/genre.slice';
import { _getAllPublisher } from '#app/reducers/master-data/publisher/publisher.slice';
import { _getAllAuthors } from '#app/reducers/master-data/author/author.slice';
import { _getAllDepartment } from '#app/reducers/master-data/department/department.slice';
import { _updateBook, _getAllBooks, _getBookDetail, _uploadBookFile } from '#app/reducers/master-data/book/book.slice';
import { Wrapper } from '#app/components/data-table/StyledComponent';
import { IBookParam } from '#app/reducers/master-data/book/book.service';
interface IProps {
}
interface ErrMessage {
type: string,
message: string
}
const schema = yup.object().shape({
title: yup.string().required('Title cannot blank'),
publishYear: yup.number(),
isPublic: yup.boolean(),
language: yup.string(),
authorIDs: yup.array(),
genreIDs: yup.array(),
description: yup.string(),
// departmentIDs: yup.array(),
publisherID: yup.number(),
file: yup
.mixed()
.required("A random message")
.nullable(false)
.test("Empty file", "You need to provide a file", (value: FileList) => {
let x = value.length !== 0
debugger
return value.length !== 0;
})
.test("type", "Only support PDF file", (value: FileList) => {
return value[0].type === "application/pdf"
})
});
const UpdateBookPage: React.FC<IProps> = (props) => {
const dispath = useDispatch()
const history = useHistory();
useEffect(() => {
dispath(_getAllGenres());
dispath(_getAllPublisher());
dispath(_getAllAuthors());
dispath(_getAllDepartment());
// dispath(_getAllBooks());
dispath(_getBookDetail(id));
}, [])
const search = useLocation().search;
const query = new URLSearchParams(search);
const id = query.get('id') || '';
const genres = useSelector(state => state.genre).genres;
const publishers = useSelector(state => state.publisher).publishers;
const authors = useSelector(state => state.author).authors;
const departments = useSelector(state => state.department).departments;
const book = useSelector(state => state.book.selectedBook);
const isLoading = useSelector(state => state.book.isLoading);
const statusCode = useSelector(state => state.book.statusCode);
// console.log(book)
const defaultDepartments = useMemo<number[]>(() => {
let temp: number[] = []
for (let x of book.departments) {
temp.push(x.id)
};
return temp;
}, [book.departments])
const defaultAuthors = useMemo<number[]>(() => {
let temp: number[] = []
for (let x of book.authors) {
temp.push(Number(x.id))
};
return temp;
}, [book.authors])
const defaultGenres = useMemo<number[]>(() => {
let temp: number[] = []
for (let x of book.genres) {
temp.push(Number(x.id))
};
return temp;
}, [book.genres])
const departmentOptions = useMemo<DropdownItemProps[]>(() => {
let temp: DropdownItemProps[] = []
for (let x of departments) {
temp.push({
key: x.id,
text: x.name,
value: x.id
})
};
return temp;
}, [departments])
const genreOptions = useMemo<DropdownItemProps[]>(() => {
let temp: DropdownItemProps[] = []
for (let x of genres) {
temp.push({
key: x.id,
text: x.name,
value: x.id
})
};
return temp;
}, [genres])
const authorOptions = useMemo<DropdownItemProps[]>(() => {
let temp: DropdownItemProps[] = []
for (let x of authors) {
temp.push({
key: x.id,
text: x.fullName,
value: x.id
})
};
return temp;
}, [authors])
const [openForm, setOpenForm] = useState<boolean>(false)
const [authorSelected, setAuthorSelected] = useState<Number[]>([...defaultAuthors]);
const [genreSelected, setGenreSelected] = useState<Number[]>([...defaultGenres]);
const [departmentSelected, setDepartmentSelected] = useState<Number[]>([...defaultDepartments]);
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [fileErrorText, setFileErrorText] = useState<string>('');
// const [selectedFile, setSelectedFile] = useState<File|null>(null);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [alertText, setAlertText] = useState<string>('');
const [isFailed, setIsFailed] = useState<boolean>(false);
const [errMessage, setErrMessage] = useState<ErrMessage>({
type: '',
message: ''
})
const { register, handleSubmit, formState: { errors }, setValue, trigger } = useForm<TestBook>({
resolver: yupResolver(schema),
// defaultValues: defaultBook
});
useEffect(() => {
if (book) {
setInitValue()
}
}, [book])
const setInitValue = () => {
setValue('title', book.title)
setValue('publishYear', book.publishYear)
setValue('authorIDs', [...defaultAuthors])
setValue('language', 'vi')
setValue('departmentIDs', [...defaultDepartments])
setValue('description', book.description)
setValue('genreIDs', [...defaultGenres])
setValue('isPublic', book.isPublic)
setValue('publisherID', Number(book.publisher.id))
}
// console.log(defaultAuthors, defaultDepartments, defaultGenres)
const handleSubmitBtn = async (data: NewBook) => {
debugger
console.log(data);
const valid = validateData();
debugger
if (valid) {
data.authorIDs = authorSelected.concat(defaultAuthors);
data.genreIDs = genreSelected.concat(defaultGenres)
data.departmentIDs = departmentSelected.concat(defaultDepartments)
const updateParam: UpdateParam = {
id: id,
newBook: data
}
console.log(data);
console.log(selectedFile);
try {
debugger
await dispath(_updateBook(updateParam)).then(() => {
if (statusCode == 200) {
if (selectedFile != null) {
let formData = new FormData();
formData.append("FILE", selectedFile);
formData.append("NAME", selectedFile.name);
const param: IBookParam = {
id: localStorage.getItem("BOOKID") || '',
file: formData
}
console.log(param.file);
console.log(param.id);
dispath(_uploadBookFile(param)).then(() => {
if (statusCode == 200) {
setIsOpen(true);
console.log(isOpen);
setAlertText("Update successfully")
} else {
setIsOpen(true)
setAlertText("An error has occured. Please try again later");
setIsFailed(true);
}
})
} else {
return;
}
}
console.log(data);
})
}
catch {
setIsOpen(true)
setAlertText("An error has occured. Please try again later");
setIsFailed(true);
}
}
}
const validateData = (): boolean => {
let x = authorSelected.concat(defaultAuthors)
let y = genreSelected.concat(defaultGenres)
let z = departmentSelected.concat(defaultDepartments)
debugger
if (authorSelected.concat(defaultAuthors).includes(0)) {
setErrMessage({
type: "AUTHOR",
message: "At least one author must be selected"
});
return false;
} else if (genreSelected.concat(defaultGenres).includes(0)) {
setErrMessage({
type: "GENRE",
message: "At least one genre must be selected",
});
return false;
} else if (departmentSelected.concat(defaultDepartments).includes(0)) {
setErrMessage({
type: "DEPARTMENT",
message: "At least one department must be selected",
});
return false;
}
return true;
}
const handleAddItem = (data: DropdownProps, type: string) => {
let val = JSON.stringify(data.value)
let numArr: Number[] = []
val = val.substring(1, val.length - 1)
let valArr = val.split(',');
// console.log(valArr);
for (let x of valArr) {
numArr.push(Number(x))
// console.log(numArr)
}
console.log(numArr)
// const y: Number[] = numArr
switch (type) {
case "AUTHOR":
setValue("authorIDs", numArr)
setAuthorSelected(numArr)
break;
case "GENRE":
setValue("genreIDs", numArr)
setGenreSelected(numArr);
break;
case "DEPARTMENT":
// if(numArr.length > 5){
// setErrMessage({
// type: "Department",
// message: "You can only select maximum of 5 departments"
// })
// break;
// }
setValue("departmentIDs", numArr)
setDepartmentSelected(numArr);
break;
}
}
const handleSelectFile = (event: any) => {
console.log(event.target.files[0]);
setSelectedFile(event.target.files[0]);
}
const handleCancelAction = () => {
history.push('/master-data/manage-book')
}
useEffect(() => {
console.log(authorSelected)
console.log(genreSelected)
console.log(departmentSelected)
}, [authorSelected, genreSelected, departmentSelected])
return (
<div className="form-create-book">
<Header>Update book</Header>
{
isLoading && (
<Wrapper style={{ padding: '40px 0' }}>
<Dimmer inverted active={true}>
<Loader>Loading</Loader>
</Dimmer>
</Wrapper>)
}
{
!isLoading && (
<div>
<Modal
size={"tiny"}
open={isOpen}
>
<Modal.Content>
<h2 style={{textAlign: 'center'}}>{alertText}</h2>
</Modal.Content>
<Modal.Actions>
{!isFailed &&
<Button negative onClick={() => setIsOpen(false)}>
Close
</Button>
}
<Button positive onClick={() => history.push("/librarian/master-data/manage-book")}>
Ok
</Button>
</Modal.Actions>
</Modal>
<Form className="create-book-form" onSubmit={handleSubmit(handleSubmitBtn)}>
<Form.Field>
<label className='dot-required'>Title</label>
<input {...register('title')} className="text-field" />
<p>{errors.title?.message}</p>
</Form.Field>
<Form.Field>
<label>Description</label>
<textarea {...register('description')} className="textarea-field" />
<p>{errors.description?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Publish Year</label>
<input className="text-field" {...register('publishYear')} />
<p>{errors.publishYear?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>PDF File</label>
<input {...register('file')} className="text-field" accept="application/pdf" type="file" name="file" onChange={handleSelectFile} />
<p>{errors.file?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Access Permission</label>
<select defaultValue={book.isPublic ? 1 : 0} className="text-field" {...register('isPublic')}>
<option value={1}>Public</option>
<option value={0}>Restricted</option>
</select>
<p>{errors.isPublic?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Publisher</label>
<select className="text-field" {...register('publisherID')} >
{
publishers.map((e) => {
if (e.id === book.publisher.id)
return (<option selected value={e.id}>{e.name}</option>)
else
return (<option value={e.id}>{e.name}</option>)
}
)
}
</select>
<p>{errors.publisherID?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Authors</label>
<Dropdown className="text-field" onChange={(e, data) => handleAddItem(data, "AUTHOR")} placeholder='Authors' fluid multiple selection options={authorOptions} defaultValue={defaultAuthors} />
<p>{errMessage.type == "AUTHOR" ? errMessage.message : ''}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Genre</label>
<Dropdown className="text-field" onChange={(e, data) => handleAddItem(data, "GENRE")} placeholder='Genres' fluid multiple selection options={genreOptions} defaultValue={defaultGenres} />
<p>{errMessage.type == "GENRE" ? errMessage.message : ''}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Department</label>
<Dropdown className="text-field" onChange={(e, data) => handleAddItem(data, "DEPARTMENT")} placeholder='Genres' fluid multiple selection options={departmentOptions} defaultValue={defaultDepartments} />
<p>{errMessage.type == "DEPARTMENT" ? errMessage.message : ''}</p>
</Form.Field>
<Button
content="Submit"
// onClick={handleSubmit(handleSubmitBtn)}
className="ui inverted green button"
/>
<Button
color="black"
content="Cancel"
onClick={() => handleCancelAction()}
className="negative ui button"
/>
</Form>
</div>
)
}
</div>
);
};
export default UpdateBookPage;
When I check the source tab, I find out that the problem comes from here:
But I don't know how to solve it.
replace
.required("A random message")
.nullable(false)
to
.test("fileLength", "A random message", (value) => {
return !!value.length
})

React-bootstrap table not refreshed after axios call

I have a table that workig perfectly on page load, that page also include a modal component to update a line of the table, I'm using a custom hook to makes API calls and it works well.
The problem is after a line modification, I dont find the way to refresh the table. The DB get updated, the Toast shows the update success, the call for the new list is fired and got the new list in DBHook with console.log('Result', result) but, the useEffect with heats dependency never get fired.
If I go to another page and come back or do F5, the list refresh properly.
dbHook :
export const useAxios = (url, method, data) => {
const [responseData, setResponseData] = useState(undefined)
const [status, setStatus] = useState(undefined)
const [error, setError] = useState('')
const [loading, setLoading] = useState(true)
const params = {
method: method,
url: url,
headers: { accept: '*/*' },
data: data,
}
const fetchData = async (params) => {
//console.log('in axios', method, url, data)
try {
const result = await axios.request(params)
//console.log('Result', result)
setResponseData(result.data)
setStatus(result.status)
//console.log('resultStatus', result.status)
} catch (error) {
setError(error.response)
//console.log('Error', error)
} finally {
setLoading(false)
//console.log('axios finished', url)
}
}
useEffect(() => {
fetchData(params)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url])
return { responseData, error, loading, status }
}
Modal :
export default function HeatModal({
isShow,
triggerModal,
activeHeat,
setActiveHeat,
saveHeat,
}) {
function save() {
saveHeat()
triggerModal()
}
return (
<>
<Modal show={isShow} onHide={triggerModal}>
<Modal.Header>
<Modal.Title>{activeHeat.name}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<FormGroup as={Row}>
<FormLabel column>
Nouvelle date de chaleur
</FormLabel>
<Col>
<MyDatePicker
field={'date'}
value={activeHeat.date}
setter={setActiveHeat}
/>
</Col>
</FormGroup>
</Form>
</Modal.Body>
<Modal.Footer as={NavContainer}>
<Button variant="outline-success" onClick={triggerModal}>
Annuler
</Button>
<Button variant="success" onClick={save}>
Confirmer
</Button>
</Modal.Footer>
</Modal>
</>
)
}
List :
const HeatList = () => {
const [heatsUrl, setHeatsUrl] = useState('/heats')
const heat = {
idHeat: -1,
name: '',
date: new Date(),
idDog: -1,
}
const [heatsWithName, setHeatsWithNames] = useState([])
const [heatPerPage, setHeatPerPage] = useState(10)
const [firstHeat, setFirstHeat] = useState(0)
const [showModal, setShowModal] = useState(false)
const [activeHeat, setActiveHeat] = useState(heat)
const [editUrl, setEditUrl] = useState('')
const {
responseData: heats,
loading: heatsIsLoading,
error: heatsIsError,
} = useAxios(heatsUrl, 'GET', null)
const { responseData: dogs, loading: dogsIsLoading } = useAxios(
'/dogs',
'GET',
null
)
const { responseData: editResponse, loading: editLoading } = useAxios(
editUrl,
'PUT',
activeHeat
)
useEffect(() => {
console.log('activeHeat', activeHeat.idHeat)
editResponse && console.log('editResponse', editResponse.idHeat)
if (editResponse && activeHeat.idHeat === editResponse.idHeat) {
console.log('in use Effect2')
setActiveHeat(editResponse)
toastSuccess(SAVE_SUCCESS)
setHeatsUrl('/heats')
} else if (editResponse === '') {
console.log('in use Effect3')
toastError(SAVE_ERROR)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editResponse])
useEffect(() => {
console.log('in new heats array')
let newHeatsArr = []
if (heats && dogs) {
console.log('in if', heats)
heats.map((h) => newHeatsArr.push(buildHeat(h, true)))
}
setHeatsWithNames(newHeatsArr)
console.log('newHeatsArray', newHeatsArr)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [heats, dogs])
const { items, requestSort, sortConfig } = useSortableData(heatsWithName)
const getClassNamesFor = (name) => {
if (!sortConfig) {
return
}
return sortConfig.key === name ? sortConfig.direction : undefined
}
const triggerModal = () => {
setShowModal((prev) => !prev)
}
function buildHeat(origHeat, withName) {
const newHeat = {
idHeat: origHeat.idHeat,
date: origHeat.date,
idDog: origHeat.idDog,
}
if (withName) {
let dogName =
dogs && dogs.find((d) => d.idDog === origHeat.idDog).name
Object.assign(newHeat, dogName && { name: dogName })
}
return newHeat
}
const modifyHeat = (h) => {
setActiveHeat(h)
triggerModal()
}
const saveHeat = () => {
console.log('aH', activeHeat)
setEditUrl('/heat/' + activeHeat.idHeat)
}
return (
<Container>
{heatsIsLoading || dogsIsLoading ? (
<Spinner animation="border" variant="primary" />
) : (
<div className="table-container">
<h1 className="text-center">Liste des chaleurs</h1>
<HeatModal
isShow={showModal}
triggerModal={triggerModal}
activeHeat={activeHeat}
setActiveHeat={setActiveHeat}
saveHeat={saveHeat}
/>
<Paginator
items={items}
dogPerPage={heatPerPage}
setDogPerPage={setHeatPerPage}
firstDog={firstHeat}
setFirstDog={setFirstHeat}
/>
<Table
striped
bordered
hover
responsive
className="table-fixed"
>
<thead>
<tr>
<th>
<button
type="buttton"
onClick={() => requestSort('name')}
className={getClassNamesFor('name')}
>
Nom
</button>
</th>
<th>
<button
type="buttton"
onClick={() => requestSort('date')}
className={getClassNamesFor('date')}
>
Date dernière chaleur
</button>
</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{items &&
items
.slice(
firstHeat,
Number(firstHeat) + Number(heatPerPage)
)
.map((heat) => (
<tr key={heat.idHeat}>
<td>{heat.name}</td>
<td>
{formatDate().format(
new Date(heat.date)
)}
</td>
<td>
<Button className="noBackground noBorder">
<ActionLogoStyle
src={AddLogo}
/>
</Button>
<Button
className="noBackground noBorder"
onClick={() =>
modifyHeat(heat)
}
>
<ActionLogoStyle
src={ModifyLogo}
/>
</Button>
</td>
</tr>
))}
</tbody>
</Table>
</div>
)}
</Container>
)
}
export default HeatList
Thanks for your help.
Thanks Brendan,
This is a real good post that helped me find the solution.
Final hooks :
const dataFetchReducer = (state, action) => {
switch (action.type) {
case 'FETCH_INIT':
return {
...state,
isLoading: true,
isError: false,
}
case 'FETCH_SUCCESS':
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
}
case 'FETCH_FAILURE':
return {
...state,
isLoading: false,
isError: true,
}
default:
throw new Error()
}
}
export const useAxios = (initialUrl, method, initialData) => {
const [url, setUrl] = useState(initialUrl)
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initialData,
})
useEffect(() => {
let didCancel = false
const params = {
method: method,
url: url,
headers: { accept: '*/*' },
data: initialData,
}
const fetchData = async (params) => {
dispatch({ type: 'FETCH_INIT' })
console.log('in axios', method, url, params)
try {
const result = await axios(params)
// console.log('result', result)
if (!didCancel) {
dispatch({ type: 'FETCH_SUCCESS', payload: result.data })
}
} catch (error) {
if (!didCancel) {
dispatch({ type: 'FETCH_FAILURE' })
}
}
}
url && fetchData(params)
return () => {
didCancel = true
}
}, [initialData, method, url])
return [state, setUrl]
}
final Modal:
export default function HeatModal({
isShow,
triggerModal,
activeHeat,
setActiveHeat,
saveHeat,
}) {
function save() {
saveHeat()
triggerModal()
}
return (
<>
<Modal show={isShow} onHide={triggerModal}>
<Modal.Header>
<Modal.Title>{activeHeat.name}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<FormGroup as={Row}>
<FormLabel column>
Nouvelle date de chaleur
</FormLabel>
<Col>
<MyDatePicker
field={'date'}
value={activeHeat.date}
setter={setActiveHeat}
/>
</Col>
</FormGroup>
</Form>
</Modal.Body>
<Modal.Footer as={NavContainer}>
<Button variant="outline-success" onClick={triggerModal}>
Annuler
</Button>
<Button variant="success" onClick={save}>
Confirmer
</Button>
</Modal.Footer>
</Modal>
</>
)
}
final List:
const HeatList = () => {
const heat = {
idHeat: -1,
name: '',
date: new Date(),
idDog: -1,
}
const [heatsWithName, setHeatsWithNames] = useState([])
const [heatPerPage, setHeatPerPage] = useState(10)
const [firstHeat, setFirstHeat] = useState(0)
const [showModal, setShowModal] = useState(false)
const [activeHeat, setActiveHeat] = useState(heat)
const [heatToSave, setHeatToSave] = useState(undefined)
const [{ data: dogs, isLoading: dogsIsLoading }] = useAxios(
'/dogs',
'GET',
null
)
const [{ data: editResponse, isLoading: editLoading }, setEditUrl] =
useAxios('', 'PUT', heatToSave)
const [{ data: heats, isLoading: heatsIsLoading }] = useAxios(
'/heats',
'GET',
editResponse
)
useEffect(() => {
if (
editResponse &&
activeHeat.idHeat === editResponse.idHeat &&
editResponse > 0
) {
console.log('in use Effect2')
toastSuccess(SAVE_SUCCESS)
} else if (editResponse === '') {
console.log('in use Effect3')
toastError(SAVE_ERROR)
}
}, [activeHeat.idHeat, editResponse])
useEffect(() => {
console.log('in new heats array', heats, 'dogs', dogs)
let newHeatsArr = []
let dogList = []
if (heats && dogs && Array.isArray(heats)) {
dogList = dogs
.filter(({ gender }) => gender === 'Femelle')
.filter(({ status }) => status === 'Élevage')
.filter(({ state }) => state === 'Vivant')
// console.log('in if', dogList)
dogList.map((d) =>
newHeatsArr.push({
idDog: d.idDog,
name: d.name,
idHeat: heats.find((h) => d.idDog === h.idDog)
? heats.find((h) => d.idDog === h.idDog).idHeat
: -1,
date: heats.find((h) => d.idDog === h.idDog)
? heats.find((h) => d.idDog === h.idDog).date
: 'Jamais',
})
)
}
setHeatsWithNames(newHeatsArr)
console.log('newHeatsArray', newHeatsArr)
}, [heats, dogs])
const { items, requestSort, sortConfig } = useSortableData(heatsWithName)
const getClassNamesFor = (name) => {
if (!sortConfig) {
return
}
return sortConfig.key === name ? sortConfig.direction : undefined
}
const triggerModal = () => {
setShowModal((prev) => !prev)
}
const modifyHeat = (h) => {
setActiveHeat(h)
triggerModal()
}
const saveHeat = () => {
console.log('aH', activeHeat)
setEditUrl('/heat/' + activeHeat.idHeat)
setHeatToSave(activeHeat)
}
return (
<Container>
{heatsIsLoading || dogsIsLoading ? (
<Spinner animation="border" variant="primary" />
) : (
<div className="table-container">
<h1 className="text-center">Liste des chaleurs</h1>
<HeatModal
isShow={showModal}
triggerModal={triggerModal}
activeHeat={activeHeat}
setActiveHeat={setActiveHeat}
saveHeat={saveHeat}
/>
<Paginator
items={items}
dogPerPage={heatPerPage}
setDogPerPage={setHeatPerPage}
firstDog={firstHeat}
setFirstDog={setFirstHeat}
/>
<Table
striped
bordered
hover
responsive
className="table-fixed"
>
<thead>
<tr>
<th>
<button
type="buttton"
onClick={() => requestSort('name')}
className={getClassNamesFor('name')}
>
Nom
</button>
</th>
<th>
<button
type="buttton"
onClick={() => requestSort('date')}
className={getClassNamesFor('date')}
>
Date dernière chaleur
</button>
</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{items &&
items
.slice(
firstHeat,
Number(firstHeat) + Number(heatPerPage)
)
.map((heat) => (
<tr key={heat.idDog}>
<td>{heat.name}</td>
<td>
{heat.date === 'Jamais'
? 'Jamais'
: formatDate().format(
new Date(heat.date)
)}
</td>
<td>
<Button className="noBackground noBorder">
<ActionLogoStyle
src={AddLogo}
/>
</Button>
<Button
className="noBackground noBorder"
disabled={
heat.date === 'Jamais'
}
onClick={() =>
modifyHeat(heat)
}
>
<ActionLogoStyle
src={ModifyLogo}
/>
</Button>
</td>
</tr>
))}
</tbody>
</Table>
</div>
)}
</Container>
)
}
export default HeatList

Taking the initial values of an object using useState hook in React

I am printing array of menu items, each has input field type number with quantity to enter for each menu item.
I'm using map function to print put all items, within is the form for each item. The only problem is when I am trying to retrieve the input value of item, the first time is taking the empty object of the useState hook that is setting the value of each item. I don't want that but only, when the value is changed. Beside that when I am clicking on arrows up and down the value of input filed is not increasing.
Can somebody help?
Here is my code of Meals.js:
import {useState, useEffect, useContext} from 'react';
import CartContext from "./store/CartContext";
const Meals = () => {
const [meals, setMeals] = useState([]);
const [meal, setMeal] = useState({});
const cartCtx = useContext(CartContext);
const fetchMeals = () => {
fetch('https://food-order-7d9f9-default-rtdb.firebaseio.com/meals.json')
.then(response => response.json() )
.then(data => {
const loadedMeals = [];
for(let key in data){
loadedMeals.push({
id: key,
meal: data[key].name,
description: data[key].description,
price: data[key].price,
quantity: 1
})
}
setMeals(loadedMeals);
})
}
useEffect(() => {
fetchMeals();
}, [])
const submitHandler = (e) => {
e.preventDefault();
cartCtx.addItem(meal)
console.log("Iz submitHandler:")
console.log(cartCtx.items)
}
const handleChange = (e, id, inputMeal, description, price) =>{
let updatedMeal = {
id: id,
meal: inputMeal,
description: description,
price: price,
quantity: +e.target.value
}
setMeal(updatedMeal)
console.log("Meal object value");
console.log(meal);
const updatedItemIndex = meals.findIndex(
i => i.id === id
)
const updatedMeals = [
...meals.slice(0, updatedItemIndex),
updatedMeal,
...meals.slice(updatedItemIndex + 1)
]
console.log("From handleChange:");
console.log(updatedMeals)
updatedMeal = {
id: "",
meal: "",
description: "",
price: "",
quantity: ""
}
}
return(
<>
{
meals.map(meal => {
return(
<div key={meal.id}>
<div>
<div>{meal.meal}</div>
<div>{meal.description}</div>
<div>{meal.price}€</div>
</div>
<div className="form-wrapper">
<form onSubmit={ e=> submitHandler(e, meal.id, meal.meal, meal.description, meal.price)}>
<input
label="Amount"
name={`${meal.id}`}
min="1"
max="5"
type="number"
value={meal.quantity}
onChange={e => handleChange(e, meal.id, meal.meal, meal.description, meal.price)}
/>
<button>+ Add</button>
</form>
</div>
</div>
)
}
)
}
</>
)
}
export default Meals;
and my demo uploaded on codesandbox.io:
https://codesandbox.io/s/wonderful-williamson-puptl?file=/src/store/CartProvider.js
Your handleChange function is wrong, On click of up and down arrow, just get the item that is being updated and update its count property.
I also updated the submitHandler.
Here is the updated Code- I hope it solves your problem .
import { useState, useEffect, useContext } from "react";
import CartContext from "./store/CartContext";
const Meals = () => {
const [meals, setMeals] = useState([]);
const cartCtx = useContext(CartContext);
const fetchMeals = () => {
fetch("https://food-order-7d9f9-default-rtdb.firebaseio.com/meals.json")
.then((response) => response.json())
.then((data) => {
const loadedMeals = [];
for (let key in data) {
loadedMeals.push({
id: key,
meal: data[key].name,
description: data[key].description,
price: data[key].price,
quantity: 1
});
}
setMeals(loadedMeals);
});
};
useEffect(() => {
fetchMeals();
}, []);
const submitHandler = (e, mealId) => {
e.preventDefault();
const updatedItemIndex = meals.findIndex((i) => i.id === mealId);
const meal = meals[updatedItemIndex];
console.log(cartCtx.items);
cartCtx.addItem({ ...meal });
};
const handleChange = (e, id, inputMeal, description, price) => {
let updatedMeal = {
id: id,
meal: inputMeal,
description: description,
price: price,
quantity: +e.target.value
};
const updatedItemIndex = meals.findIndex((i) => i.id === id);
const newMeals = [...meals];
newMeals[updatedItemIndex] = updatedMeal;
setMeals(newMeals);
};
return (
<>
{meals.map((meal) => {
return (
<div key={meal.id}>
<div>
<div>{meal.meal}</div>
<div>{meal.description}</div>
<div>{meal.price}€</div>
</div>
<div className="form-wrapper">
<form
onSubmit={(e) =>
submitHandler(
e,
meal.id,
meal.meal,
meal.description,
meal.price
)
}
>
<input
label="Amount"
name={`${meal.id}`}
min="1"
max="5"
type="number"
value={meal.quantity}
onChange={(e) =>
handleChange(
e,
meal.id,
meal.meal,
meal.description,
meal.price
)
}
/>
<button>+ Add</button>
</form>
</div>
</div>
);
})}
</>
);
};
export default Meals;

Resources