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
Related
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>
);
};
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!
I cannot figure out how to add 'active' into the state of users.
For the sake of posting this here I hardcoded some users in the state, but they're supposed to be fetched from an API - and this doesn't come with 'active'. I need to be able to mark the checkboxes so that the specific user becomes active, also if active - it has to stay active when doing searches in the list through the text-input, so it doesn't reset. With what I wrote I am getting undefined for user.active. Any suggestions?
App.js
import './App.css';
import UserList from './components/UserList';
import './style/style.css';
function App() {
return (
<div className="App">
<UserList />
</div>
);
}
export default App;
UserList.js
import React, { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([
{
id: 681,
first_name: 'James',
last_name: 'Smith',
email: 'example1',
gender: 'm',
},
{
id: 3123,
first_name: 'Richard',
last_name: 'Greene',
email: 'example2',
gender: 'm',
},
{
id: 512,
first_name: 'Denise',
last_name: 'Bank',
email: 'example3',
gender: 'f',
},
{
id: 654,
first_name: 'Carl',
last_name: 'Ross',
email: 'example4',
gender: 'm',
},
]);
const [search, setSearch] = useState('');
const [filteredUsers, setFilteredUsers] = useState();
const [checkedUsers, setCheckedUsers] = useState('');
useEffect(() => {
const fetchUsers = async () => {
try {
const result = await fetch(users);
result.sort(function (a, b) {
if (a.last_name.toLowerCase() < b.last_name.toLowerCase()) return -1;
if (a.last_name.toLowerCase() > b.last_name.toLowerCase()) return 1;
else return;
});
setUsers(result);
} catch (error) {
console.log(error);
}
};
fetchUsers();
}, []);
useEffect(() => {
setFilteredUsers(
users.filter(
(user) =>
user.first_name.toLowerCase().includes(search.toLowerCase()) ||
user.last_name.toLowerCase().includes(search.toLowerCase())
)
);
}, [users, search]);
useEffect(() => {
setCheckedUsers(users.filter((user) => user.active));
}, [users]);
const changeChecked = (id) => {
users.forEach((user) => {
if (user.id === id) {
console.log(user);
if (user.active === '') {
user.active = true;
} else user.active = false;
}
});
setUsers(users);
console.log(checkedUsers);
};
return (
<div className="list-container">
<div className="search">
<input
type="text"
placeholder="Search for users"
onChange={(event) => setSearch(event.target.value)}
/>
</div>
{filteredUsers &&
filteredUsers.map((user) => (
<div
className="user-block"
key={user.id}
onClick={() => console.log(user.id, user.active)}>
<div className="details">
<h5>
{user.first_name} {user.last_name}
</h5>
<p>{user.email}</p>
</div>
<input
type="checkbox"
checked={user.active}
onClick={(event) => changeChecked(user.id)}
/>
</div>
))}
</div>
);
}
export default UserList;
A few things here:
I think you should map the user after the fetch to add the active with a default value, so it isn't undefined in any case:
useEffect(() => {
const fetchUsers = async () => {
try {
const request = await fetch(users);
const response = request.map(data => ({...data, active: true})).sort(function (a, b) {
if (a.last_name.toLowerCase() < b.last_name.toLowerCase()) return -1;
if (a.last_name.toLowerCase() > b.last_name.toLowerCase()) return 1;
else return;
});
setUsers(response);
} catch (error) {
console.log(error);
}
};
fetchUsers();
}, []);
Your filtered users is absolutely useless, you can do that with the user itself and lose the useEffect and the state variables entirely:
users.filter(user =>
user.first_name.toLowerCase().includes(search.toLowerCase()) ||
user.last_name.toLowerCase().includes(search.toLowerCase())
).map((user) => (
<div
className="user-block"
key={user.id}
onClick={() => console.log(user.id, user.active)}>
<div className="details">
<h5>
{user.first_name} {user.last_name}
</h5>
<p>{user.email}</p>
</div>
<input
type="checkbox"
checked={user.active}
onClick={(event) => changeChecked(user.id)}
/>
</div>
)
And last but not least, your changeChecked function is mutating the array. I would use .map as well instead of forEach:
const changeChecked = (id) => {
setUsers(
users.map(user => {
if (user.id === id) {
return {
...user,
active: !user.active
};
} else {
return user;
}
})
);
console.log(checkedUsers);
};
how I can update price value when update quantity value automatically ??
page design
interface ui
print values on the console:
print values on the console:
This sentence needs to be modified
{quantity[initQ] == 1 ? data.price : initP[initQ]}
i use setState to save multiple values
export default function Contaner({ setPressed, getPressed }) {
const [products, setProducts] = useState([]);
const [layout, setLayout] = useState('list');
let initQ = 1;
const [initP,setInitP] = useState({ [initQ]: 1 }) ;
const [quantity, setQuantity] = useState({ [initQ]: 1 });
function checkQuantity(e, data) {
if (e.value <= data.quantity) {
initQ = data.name;
setQuantity({ ...quantity, [data.name]: e.value});
setInitP( { ...quantity, [data.name]: data.price * e.value});
console.log(initP );
setCart(current => [...current, data.name]);
}
else {
showError();
}
}
const renderListItem = (data) => {
return (
<div style={{ display: "flex" }}>
<button className="button_color" onClick={() => removeItem(data)}>
<i className="pi pi-trash"></i>
</button>
<h6>{quantity[initQ] == 1 ? data.price : initP[initQ] }</h6>
<InputNumber id="stacked" showButtons min={1} value={quantity[initQ]}
onValueChange={(e) => checkQuantity(e, data)} />
<InputText disabled={true} value={"₪ " + data.price} />
<h6>{data.name}</h6>
</div>
);
}
const itemTemplate = (product, layout) => {
if (!product) {
return <></>;
}
if (layout === 'list') {
return renderListItem(product);
}
}
return(
<DataView value={products} layout={layout} itemTemplate={itemTemplate} rows={1} />
);
}
I am getting datas from my database using three different functions, but as I've seen componentWillReceiveProps is rerendering for three times in this case, which cause duplicating my elements in the frontend. How can I render it just once, or only object's props really change. Till now, my follows[] array objects are duplicating
class UserDashboard extends React.Component {
state = {
uid: this.props.userDetails.uid,
page: 1,
redirect: false,
target: 15,
selectedRole: 4,
selectedSourceRole: 1,
quote_nr: '',
source_id: '',
status_id: '',
cost: '',
rebate: '',
pageLoading: false,
date: '2222-01-02',
therapists:[],
globalTargets:[],
follows:[],
utc: new Date().toISOString().slice(0, 19).replace('T', ' '),
}
topProfilesUrl = 'therapists/top/profiles';
getGlobalTargets = 'owner/targets';
followActivities = 'user/follow/activities';
componentDidMount = () => {
const { getActivities,getFollowActivities,getTarget } = this;
getActivities();
getFollowActivities();
getTarget();
window.scrollTo(0, 0);
}
UNSAFE_componentWillReceiveProps = (newProps) => {
let apiDat = newProps.apiDat;
let apiData = newProps.apiData;
if (apiData.activities && apiData.activities.success ) {
let therapists = apiData.activities.therapists;
let hasMore = true;
console.log("unu")
if (therapists.length < 10) {
hasMore = false;
}
this.setState(() => ({
therapists: this.state.therapists.concat(therapists),
hasMore: hasMore,
pageLoading: false
}))
}
if (apiDat.targets && apiDat.targets.success) {
console.log("doi")
let globalTargets = apiDat.targets.globals;
let hasMore = true;
if (globalTargets.length < 10) {
hasMore = false;
}
this.setState(() => ({
globalTargets: this.state.globalTargets.concat(globalTargets),
}))
}
if (apiData.followActivities && apiData.followActivities.success) {
console.log("trei")
let follows = apiData.followActivities.follows;
let hasMore = true;
if (follows.length < 10) {
hasMore = false;
}
this.setState(() => ({
follows: this.state.follows.concat(follows),
}))
}
}
getTarget = () => {
this.setState({pageLoading: true}, () => { this.loadTargets() })
}
loadTargets = () => {
console.log("load")
this.props.actions.reqGetGlobalTargets({
body: {},
headers: null,
resource: `${this.getGlobalTargets}?page=${this.state.page}`
})
}
getFollowActivities= () => {
this.setState({pageLoading: true}, () => { this.loadFollowActivities() })
}
loadFollowActivities = () => {
console.log("load")
this.props.actions.reqGetFollowActivities({
body: {},
headers: null,
resource: `${this.followActivities}?page=${this.state.page}`
})
}
renderLeads = () => {
return (
this.state.globalTargets.slice(0,1).map( (t, idx) => (
t.daily_leads
))
)
}
renderSales = () => {
return (
this.state.globalTargets.slice(0,1).map( (t, idx) => (
t.daily_sales
))
)
}
renderRatio = () => {
return (
this.state.globalTargets.slice(0,1).map( (t, idx) => (
t.close_ratio
))
)
}
getActivities = () => {
this.setState({pageLoading: true}, () => { this.loadActivities() })
}
loadActivities = () => {
this.props.actions.reqGetTherapistsTopProfiles({
body: {},
headers: null,
resource: `${this.topProfilesUrl}?page=${this.state.page}`
})
}
renderActivities = () => {
const items = this.state.therapists.map( (t, idx) => (
<tr key={t.id} className="activity-display-table">
<td>Quote Nr.: {t.quote_nr}</td>
<td>Source: {t.source_id}</td>
<td>Status: {t.status_id}</td>
<td>Cost: ${t.cost}</td>
<td>Rebate: ${t.rebate}</td>
<td>Date: {t.date.slice(0,10).replace(/-/g,'-')}</td>
</tr>
))
return (
<div ref={0} className="therapist-list">
<h2>Your Past Entries: </h2>
{ items }
</div>
)
}
renderFollowActivities = () => {
const items = this.state.follows.map( (t, idx) => (
<tr key={t.id} className="activity-display-table">
<td>Quote Nr.: {t.quote_nr}</td>
<td>Source: {t.source_id}</td>
<td>Status: {t.status_id}</td>
<td>Cost: ${t.cost}</td>
<td>Rebate: ${t.rebate}</td>
<td>Date: {t.date.slice(0,10).replace(/-/g,'-')}</td>
</tr>
))
return (
<div ref={0} className="therapist-list">
{ items }
</div>
)
}
submitUrl = 'registerActivities';
handleChange = (eve) => {
let inputName = eve.target.name,
value = eve.target.value;
this.setState(() => {
return {[inputName]: value}
})
}
handleSubmit = () => {
this.setState(() => {
const acBody = {
quote_nr: this.state.quote_nr,
cost: this.state.cost,
source_id: this.state.selectedSourceRole,
status_id: this.state.selectedRole,
date: this.state.utc,
rebate: this.state.rebate,
user_id:this.state.uid,
}
this.props.actions.reqActionsUsers(acBody, this.submitUrl);
})
}
handleStatusChange = (event) => {
let statusId = event.target.value;
this.setState(() => ({
selectedRole: statusId
}))
}
handleSourceChange = (ev) => {
let statusId = ev.target.value;
this.setState(() => ({
selectedSourceRole: statusId
}))
}
render () {
console.log(this.state.follows);
return (
<MainWrapper>
<div id="user-dashboard">
<HeaderUser logoutRedirect="/signin"/>
<div className="page-background">
<SidebarUser page="dashboard"/>
{/* Page Content */}
<div className="inner-content">
<div className="top-row">
<h1>Salesperson Dashboard</h1>
</div>
<div className="second-row">
</div>
<div className="activity-table">
<table className="a">
<tr>
<th>Today's Targets ({this.state.utc.slice(0,10).replace(/-/g,'-')})</th>
<th>Weekly Targets</th>
<th>Bonus So Far This Week</th>
</tr>
<tr>
<td>0/{this.renderLeads()} Leads Handled</td>
<td>0/{this.renderLeads()*5} Leads Handled</td>
<td>$0 From Leads</td>
</tr>
<tr>
<td>0/{this.renderSales()} Sales</td>
<td>0/{this.renderSales()*5} Sales</td>
<td>$0 From Sales</td>
</tr>
<tr>
<td>0/{this.renderRatio()} Close Ratio</td>
<td>0/{this.renderRatio()*5} Close Ratio</td>
<td>$0 From Profit Share</td>
</tr>
</table>
</div>
<div>
<h2>Leads Due For A Followup</h2>
{ this.renderFollowActivities() }
</div>
<h2 className="activity">Add Activity</h2>
<div className="activity-menu">
<input type="text"
placeholder="Quote Number"
name="quote_nr"
onChange={this.handleChange}
/>
<select onChange={this.handleSourceChange}>
<option value="1">Phone</option>
<option value="2">Email</option>
<option value="3">Live Chat</option>
</select>
<select onChange={this.handleStatusChange}>
<option value="4">Lead</option>
<option value="5">Sold</option>
</select>
<input type="text"
placeholder="Cost"
name="cost"
onChange={this.handleChange}
/>
<input type="text"
placeholder={this.state.cost/20||("Recom. Rebate" + " $")}
name="recRebate"
readOnly
/>
<input type="text"
placeholder={this.state.cost/10||("Max Possible Rebate" + " $")}
name="maxRebate"
readOnly
/>
<input type="text"
placeholder="Final Rebate $"
name="rebate"
onChange={this.handleChange}
/>
</div>
<ButtonRoundGradient className="activity_button" text="Add Activity" onClick={this.handleSubmit}/>
{ this.renderActivities() }
</div>
</div>
</div>
</MainWrapper>
)
}
}
const mapStateToProps = state => ({
apiData: state.activities,
apiDat: state.targets,
userDetails: state.userDetails
})
function mapDispatchToProps(dispatch) {
return {
actions: {
reqGetGlobalTargets: bindActionCreators(reqGetGlobalTargets, dispatch),
reqGetFollowActivities: bindActionCreators(reqGetFollowActivities, dispatch),
reqGetTherapistsTopProfiles: bindActionCreators(reqGetTherapistsTopProfiles, dispatch),
reqFetchUserDetails: bindActionCreators(reqFetchUserDetails, dispatch),
reqActionsUsers: bindActionCreators(reqActionsUsers, dispatch),
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(UserDashboard)
ComponentWillRecieveProps would be called every time your state changes or your state changes, so if you want to stop duplicating, you should do this :
componentWillReceiveProps(nextProps, nextContext) {
if (JSON.stringify(nextProps.someProps.items) !== JSON.stringift(this.state.items)){
// do something
}
}
basically you should check if props and state of your component should react to the situation, then really render your application.
hope that helps