Dynamic react hooks to trigger button - javascript

I have a react app which getting a data from the server, and I want to add each data a state. Let's see my design app :
NB : the red line are each data that I have requested (the white row is the data attribute).
So each data should be able to be clicked and have own state to show the proposal and laporan akhir attribute. I'm using React hooks, so how am I supposed to do that?
this is my code so far :
function RekapAngkatan({
auth,
getProposalRecapByYear,
getReportRecapByYear,
posts,
StickyNav
}) {
const postingan = posts.posts
const blankPanduan = {id: "", value: false};
const [panduan, setPanduan] = React.useState([{...blankPanduan.value}])
let content;
React.useEffect(() => {
if (auth.isAuthenticated) {
// Server request
const getProposals = async () => await getProposalRecapByYear()
const getReports = async () => await getReportRecapByYear()
getProposals()
getReports()
}
}, [auth])
if (loading) {
content = <Loading />
} else {
// trying to dynamically setState
setPanduan([
...panduan,
{
id: postingan.data.length,
value: false
}
])
content = postingan.data.map((post, i) => {
<Section bg="transparent" margin="0 1.5em" padding="0 2em" key={i}>
<div className="info-color">
{post.value}
<FakeButton
bg="#8C489F"
type="button"
onClick={() => setPanduan([...panduan, {...!blankPanduan}])}
>
Caret Down
</FakeButton>
{/* showing attributes*/}
<Attribute show={panduan.value}>
{post.amount}
</Attribute>
</div>
</Section>
})
}
return content
}

Related

React update view after submitting form data using react-query in NextJS

I'm working on a NextJS project, and am running into an issue where my view isn't updating after a form submission. I'm using react-query to both fetch the data and do mutations.
I have a form called 'Add Day' which adds a day object to the database through an endpoint. But after making this update, my UI does not refresh to reflect this new object.
Here is a video that demonstrates it:
https://imgur.com/a/KIcQOfs
As you can see, when I refresh the page, the view updates. But I want it to update as soon as I submit the form and it is a success. Here's how my code is set up:
TripDetail
const TripDetailPage = () => {
const router = useRouter()
const queryClient = useQueryClient()
const tripId = router.query.tripId as string
const { data, isError, isLoading } = useQuery({
queryKey: ['trips', tripId],
queryFn: () => getTrip(tripId),
})
const [tabState, setTabState] = useState(1)
const toggleTab = (index: number) => {
setTabState(index)
}
const [modalVisible, setModalVisible] = useState<boolean>(false)
const toggleModalVisible = () => {
setModalVisible(!modalVisible)
}
const onDayAdded = () => {
queryClient.invalidateQueries({ queryKey: ['trips', tripId] })
console.log('refetching')
console.log('day was added')
}
console.log('new data?')
console.log(data)
if (isLoading) {
return <span>Loading...</span>
}
if (isError) {
return <span>Error</span>
}
if (!data || !data.data || !data.data.name || !data.data.days) {
return <span>Error</span>
}
return (
<div className="ml-12 mr-12 mt-6">
<div className="flex flex-row justify-between">
<div className="order-first flex items-center">
<h1 className="text-2xl font-semibold text-slate-800">{data.data.name}</h1>
<Button className="btn btn-primary ml-8">Invite</Button>
</div>
<div className="order-last flex">
<Button className="btn btn-primary mr-4">Refresh</Button>
<Button className="btn btn-primary" onClick={toggleModalVisible}>
Add Day
</Button>
</div>
<AddDayModal
onSuccess={onDayAdded}
modalVisible={modalVisible}
toggleModalVisible={toggleModalVisible}
trip={data.data}
/>
</div>
<div id="tabs-nav" className="font-medium text-lg border-separate pb-2 border-b-2 border-gray-200 mt-6">
<TabButton tabId={1} currentTabState={tabState} name="Plans" toggleTab={() => toggleTab(1)}></TabButton>
<TabButton
tabId={2}
currentTabState={tabState}
name="Calendar"
toggleTab={() => toggleTab(2)}
></TabButton>
<TabButton tabId={3} currentTabState={tabState} name="Ideas" toggleTab={() => toggleTab(3)}></TabButton>
</div>
<div id="content">
{tabState === 1 && <PlannerBoard days={data.data.days} tripId={tripId}></PlannerBoard>}
{tabState === 2 && <h1>Tab 2</h1>}
{tabState === 3 && <h1>Tab 3</h1>}
</div>
</div>
)
}
export default TripDetailPage
I'm calling invalidateQueries in onDayAdded which should refetch my data and update my view, but it doesn't seem to be. PlannerBoard is where I pass the data and is the component that renders the columns. It basically iterates through the days that is passed to it and renders them.
Planner Board:
type TDndPlannerProps = {
days: TDay[]
tripId: string
}
const PlannerBoard = (props: TDndPlannerProps) => {
// ...
props.days.forEach((day) => {
// lots of processing, creates a data object which has the objects to make this view
})
const [state, setState] = useState<IData>(data) // after above processing
// removed a bunch of code
return (
<div className="overflow-auto">
<div>
{day.map((day) => {
return <DayColumn key={day.id} day={day} />
})}
</div>
</div>
)
}
export default PlannerBoard
AddDay Modal:
type TModalProps = {
trip: TTrip
modalVisible: boolean
toggleModalVisible: () => void
onSuccess: () => void
}
const formArr: TFormField[] = [
{
label: 'Day name',
name: 'name',
type: 'text',
},
{
label: 'Day date',
name: 'date',
type: 'date',
},
]
const AddDayModal = (props: TModalProps) => {
const [day, setDay] = useState<TDay>({
name: '',
tripId: props.trip.id!,
})
const onSuccess = () => {
props.toggleModalVisible()
props.onSuccess()
}
const { mutate, isLoading, isError, error } = useMutation((day: TDay) => postDay(day), { onSuccess })
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
mutate(day)
}
const onUpdate = (update: TFormFieldValue) => {
setDay((prev) => {
if (update.type == 'date') {
return { ...prev, [update.name]: new Date(update.value) }
} else {
return { ...prev, [update.name]: update.value }
}
})
}
const formModalProps: TFormModalProps = {
title: 'Add Day',
formArr,
submitBtn: 'Submit',
submittingBtn: 'Adding',
toggleVisible: props.toggleModalVisible,
visible: props.modalVisible,
onSubmit,
isLoading,
isError,
error,
onUpdate,
}
return (
<div>
<FormModal {...formModalProps} />
</div>
)
}
export default AddDayModal
The mutate here makes the axios call to my API which updates the db with this form data. Once we hit onSuccess the API call has succeeded and it call's TripDetailPage's onDayAdded().
Any idea how to make my UI update when the refetch completes? Do I need to use useState somehow?
I believe this is the expected behaviour with react-query.
You can either use queryClient.invalidateQueries to refetch single or multiple queries in the cache or use queryClient.setQueryData to immediately update a query's cached data.
You can use this directly in the onSuccess callback if you wish.
See documentation.
I believe I have a fix. In my PlannerBoard I needed to detect the change in props through useEffect and then manually set the state again as:
useEffect(() => {
const data: IData = createBoardData(props.days)
setState(data)
}, [props])
This forces the view to re-render.
There is likely no need to copy data from props into state in the PlannerBoard component. As you have found out, this is your issue, because you create a "copy" of your state. This is usually an anti-pattern, and the fix you found (syncing with useEffect) is not a good idea either. I have a whole blogpost series around that topic:
https://tkdodo.eu/blog/dont-over-use-state
https://tkdodo.eu/blog/putting-props-to-use-state
the easiest fix would be to move from useState to useMemo, unless you really need the setState method, which you haven't shown in your example:
const PlannerBoard = (props: TDndPlannerProps) => {
const state = React.useMemo(() => {
// lots of processing
}, [props.days])

Trigger useEffect with anotherComponents

I have 2 components, the Favorites component, makes a request to the api and maps the data to Card.
I also have a BtnFav button, which receives an individual item, and renders a full or empty heart according to a boolean.
Clicking on the BtnFav render removes a certain item from the favorites database.
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The data flow for now would be something like this:
Favorites component fetches all the complete data and passes it to the Card component, the Card component passes individual data to the BtnFavs component.
Favorites Component:
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>Mis favoritos</h1>
<Card listWines={vinosFavs} />
</div>
);
BtnFavs:
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The pattern you want is called a callback function, just like the onClick of a button. You pass a function to your components that get executed given a condition. If you want fetchWines to be called again, then just pass the function in as a prop.
Favorites Component:
<Card listWines={vinosFavs} refresh={fetchWines} />
Card Component
<BtnFavs onDelete={refresh} ... />
BtnFavs Component
onDelete();
You can name it whatever you want, but generally callbacks will be named like on<condition>.
If you really wanted useEffect to be triggered then you would pass a setState function that set one of the dependencies, but I don't see a point in this case.
I will share code, because this problem its normal for me, i really want to learn and improve that.
const Favorites = () => {
const { favoritesUser } = useFavoritesContext();
const user = useSelector((state) => state.user);
const id = user.id;
const [vinosFavs, setVinosFavs] = useState([]);
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>My favorits</h1>
<Grid listVinos={vinosFavs} />
</div>
);
};
export default Favorites
Grid
export default function Grid({ listVinos }) {
return (
<div>
<ul className={styles.layoutDeVinos}>
{listVinos?.map((element) => {
return <WineCard key={element.id} vino={element} />;
})}
</ul>
</div>
);
}
Card
export default function WineCard({ vino }) {
return (
<>
<div>
<Link to={`/products/${vino.id}`}>
<li>
<div className={styles.card}>
<div
className={styles.img1}
style={{
backgroundImage: `url(${vino.images})`,
}}
></div>
<div className={styles.text}>{vino.descripcion}</div>
<div className={styles.catagory}>
{vino.nombre}
<i className="fas fa-film"></i>
</div>
<div className={styles.views}>
{vino.bodega}
<i className="far fa-eye"></i>{" "}
</div>
</div>
</li>
</Link>
<div className="botonesUsuario">
<BtnFavs vino={vino} />
</div>
</div>
</>
);
}
BTN FAVS
export default function BtnFavs({ vino }) {
const { setFavoritesUser } = useFavoritesContext();
const [boton, setBoton] = useState(false);
const user = useSelector((state) => state.user);
const userId = user.id;
const productId = vino.id;
useEffect(() => {
axios
.post("/api/favoritos/verify", { userId, productId })
.then((bool) => setBoton(bool.data));
}, []);
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
return (
<>
{!user.id ? (
<div></div>
) : boton ? (
<span
class="favIcons material-symbols-rounded"
onClick={handleClickFav}
>
favorite
</span>
) : (
<span className="material-symbols-rounded" onClick={handleClickFav}>
favorite
</span>
)}
</>
);
}

Component data was gone after re rendering, even though Component was react.memo already

I have two components.
First is called: BucketTabs
Second is called:BucketForms
To have a better idea. Below pictures illustrate it.
When I switching tab, different form will be showed below.
Q: Whenever I switch from one tab to other tab, and then switch back, the content in the previous BucketForms will be gone. But, gone data are supposed to be stored into a state of that BucketForms.
In fact, I've memo the BucketForms already, so I've expected the content(data) would not be gone.
What's the problem and how could I prevent the data to be gone after switching tab.
My BucketTabs:
import { BucketForms } from '~components/BucketForms/BuckForms'
export const BucketTabs: React.FC = () => {
const items = useMemo<ContentTabsItem[]>((): ContentTabsItem[] => {
return [
{
title: '1',
renderContent: () => <BucketForms key="1" bucketCategory="1" />,
},
{
title: '2',
renderContent: () => <BucketForms key="2" bucketCategory="2" />,
},
]
}, [])
return (
<div className="row">
<div className="col">
<ContentTabs items={tabs} kind="tabs" />
</div>
</div>
)
}
BucketForms
function PropsAreEqual(prev, next) {
const result = prev.bucketCategory === next.bucketCategory;
return result;
}
interface IData {
portfolioValue?: number
}
export const BucketForms: React.FC<IProps> = React.memo(props => {
const { bucketCategory } = props
const [data, setData] = useState<IData>({
})
const view = ({
portfolioValue,
}: IData) => {
return (
<>
<div className="row portfolio">
<FormNumericInput
key="input-portfolio-value"
name="portfolioValue"
required
value={portfolioValue}
/>
</div>
</>
)
}
return (
<Form
onChange={e => {
setData({ ...data, ...e, })
}}
>
{view(data)}
</Form>
)
}, PropsAreEqual)

React / JS function fails first time after refresh - then works

I added useEffect to the xml processing function and it fixed it right up.
useEffect(() => {
if (inputValue.length > 50) {
setXmlValue((xmlValue) => loadPoBXml(inputValue));
}
}, [inputValue])
I am rather stumped with this problem.
I am writing a web app that converts a base64 string into a xml file and extracts data from that xml file.
When I first run the app after a npm start or after a browser refresh the function that converts the string does not complete correctly. If I trigger the function a second time, it works, and will continue to work until I do a refresh. I believe the problem lies within the populateData function and have tried to make it work as async, but that made the problem considerably worse.
I have included a screenshot of my console log and the code is detailed below (please note I have for brevity removed some parts of the code):
Buffer.from('anything','base64');
export default function Home() {
// State to store value from the input field
const [xmlValue, setXmlValue] = useState([]);
const [buildValue, setBuildValue] = useState([]);
const [buildData, setBuildData] = useState([]);
const [mainSkill, setMainSkill] = useState("");
const [inputValue, setInputValue] = useState("");
const xmlData = useSelector(state => state)
// redux data management
const dispatch = useDispatch();
function populateData(str) {
let data = ""
if (str.includes("https://")) {
data = httpGet(str)
}
else {
data = str
}
if(typeof xmlData !== "unknown") {
console.log("decode was called")
// get xml data as an array
console.log("pob data was called")
setXmlValue((xmlValue) => loadPoBXml(data))
// populate character data
console.log("handle character called")
handleChar(xmlValue)
// find the main dps skill
findMainSkill(xmlValue)
// populate the store
dispatch(populateGems(xmlValue))
console.log("store loaded")
}
else {
console.log("no decode called")
handleChar(xmlData)
findMainSkill(xmlData)
}
}
function findMainSkill(event) {
// removed code
};
function populateBuildData(data) {
//removed code
}
function handleChar(event) {
console.log(event)
let temp = event.getElementsByTagName('Build')
setBuildValue((buildValue) => temp);
populateBuildData(temp)
};
// Input Field handler
const handleUserInput = (e) => {
setInputValue(e.target.value);
};
// Reset Input Field handler
const resetInputField = () => {
setInputValue("");
setBuildValue([])
setBuildData([])
};
return (
<>
<div className="InsideContent">
<Input className="input1"
placeholder="Paste PoB Code"
name="pobCode"
value={inputValue}
onChange={handleUserInput} />
</div>
<div className="InsideContent">
<Button1 onClick={() => populateData(inputValue)}>Confirm</Button1>
<Button2 onClick={resetInputField}>Reset</Button2>
</div>
{buildValue.length > 0 && (
<div className="card">
<div className="divider"></div>
{buildValue.map((item, i) => (
<ul key={`item${i}`}>
<div className="acend">
<img src={require("../images/" + item.attributes.ascendClassName +".png")}></img>
</div>
<div className="leftInfo">
<p>{item.attributes.ascendClassName} Level: {item.attributes.level}</p>
<div className="leftInfoSmall">
<p>{mainSkill} {buildData.CombinedDPS} DPS</p>
<p>Life: <span className="redText">{buildData.Life}</span> ES: <span className="blueText">{buildData.EnergyShield}</span> Ward: <span className="grayText">{buildData.Ward}</span></p>
</div>
</div>
<div className="rightInfo">
<p>EHP: {buildData.TotalEHP} Max Hit: {buildData.SecondMinimalMaximumHitTaken == 0 ? buildData.PhysicalMaximumHitTaken : buildData.SecondMinimalMaximumHitTaken}</p>
<p>Phy Reduction: {buildData.PhysicalDamageReduction}% Evasion: {buildData.MeleeEvadeChance}% Spell Supression: {buildData.SpellSuppressionChance}%</p>
<p>Resistances: <span className="redText">{buildData.FireResist}</span>/<span className="blueText">{buildData.ColdResist}</span>/<span className="yellowText">{buildData.LightningResist}</span>/<span className="purpleText">{buildData.ChaosResist}</span></p>
</div>
</ul>
))}
</div>
)}
</>
)
}
function loadPoBXml(str) {
var res = decodePoBString(str);
var xml = new XMLParser().parseFromString(res);
console.log("pob data was loaded")
return xml
}
function decodePoBString(str) {
return inflateSync(new Buffer(str, "base64")).toString()
}

React not rendering what is being returned by a function component

I have a "Tasks" function component that has a "tasks" state which is an array of tasks that is loaded from the backend using useEffect. The render method renders a list of the tasks in the state as "Task" components, among other things.
The issue I am having is that when I delete a task, the backend is updated, the tasks component updates its "tasks" state using data grabbed from the backend, and it should then re render with the newly updated tasks array, but it does not re render properly. The last element in the tasks state array is no longer visible, however it is not deleted from the backend, while the deleted task is deleted from the backend but is still visible. I have confirmed as best I can that the Task components being returned by the Tasks component are correct, and the state of Tasks is definitely correct.
Tasks Component:
import Task from './Task'
import React, { useEffect, useState } from 'react'
import { Redirect } from 'react-router';
import { getLoggedIn, getToken } from '../../context/loggedInState';
import { get } from "../../tools/request";
import AddTask from '../Tasking/AddTask';
import { useAddTask, useAddTaskUpdate } from '../../context/AddTaskContext'
import { FaAngleRight, FaAngleDoubleRight, FaCommentsDollar } from 'react-icons/fa';
import { useToDoContext, useUpdateToDoContext } from '../../context/ToDoContext';
const Tasks = () => {
const [reload, setReload] = useState(true);
const [tasks, setTasks] = useState([])
const [hover, setHover] = useState(false);
const loggedIn = getLoggedIn()
const showAddTask = useAddTask();
const toggleAddTask = useAddTaskUpdate();
const toDoContext = useToDoContext();
useEffect(() => {
// Fetch todos
if (loggedIn === true && reload === true) {
console.log("refreshing tasks");
get("/api/task/getTasks")
.then((resJson) => {
if (resJson.tasks !== undefined) {
console.log("setting task")
setTasks(resJson.tasks);
for (const task in tasks) {
console.log(`fetched tasks: ${task.title}`)
}
setReload(false);
}
})
} else {
return (
<Redirect to="/login" />
)
}
}, [reload])
const handleHover = () => {
setHover(!hover)
}
var test = `
<div>
<div name='addTaskCtn' className=${showAddTask ? 'invisible add-task-container' : 'visible add-task-container'}
onMouseEnter=${handleHover}
onMouseLeave=${handleHover}>
<FaAngleRight name='angleRight' className=${hover ? 'invisible add-task-angle' : 'add-task-angle'} />
<FaAngleDoubleRight name='angleRightDouble' className=${hover ? 'add-task-angle hover' : 'invisible add-task-angle'} />
<button name='addTask' onClick=${toggleAddTask}
className=${hover ? 'add-task-btn hover' : 'add-task-btn'}
>Add Task</button>
</div>
<div name='AddTaskForm' className=${showAddTask ? 'visible' : 'invisible'}>
<AddTask setReload=${setReload} />
</div>
<div name='taskList' className='task-list'>
${tasks.map((task, index) => {
if (toDoContext.currentBucket == "" || task.buckets.includes(toDoContext.currentBucket)) {
return task.title
}
})
}
</div>
</div>
`
console.log(`Test: ${test}`)
return (
<div>
<div name='addTaskCtn' className={showAddTask ? 'invisible add-task-container' : 'visible add-task-container'}
onMouseEnter={handleHover}
onMouseLeave={handleHover}>
<FaAngleRight name='angleRight' className={hover ? 'invisible add-task-angle' : 'add-task-angle'} />
<FaAngleDoubleRight name='angleRightDouble' className={hover ? 'add-task-angle hover' : 'invisible add-task-angle'} />
<button name='addTask' onClick={toggleAddTask}
className={hover ? 'add-task-btn hover' : 'add-task-btn'}
>Add Task</button>
</div>
<div name='AddTaskForm' className={showAddTask ? 'visible' : 'invisible'}>
<AddTask setReload={setReload} />
</div>
<div name='taskList' className='task-list'>
{tasks.map((task, index) => {
if (toDoContext.currentBucket == "" || task.buckets.includes(toDoContext.currentBucket)) {
return <Task key={index} task={task} setReload={setReload} />
}
})
}
</div>
</div>
)
}
export default Tasks;
Task Component:
import React from 'react'
import { FaSun, FaTimes, FaEdit, FaCalendar } from 'react-icons/fa'
import { useState } from 'react'
import { post } from "../../tools/request";
import { getToken } from '../../context/loggedInState';
import { useToDoContext, useUpdateToDoContext } from '../../context/ToDoContext';
const Schedule = ({}) => {
return (
<div className='test'>test</div>
)
}
const Task = ({ task, setReload }) => {
const [title, setTitle] = useState(task.title)
const [buckets, setBuckets] = useState(task.buckets)
const [body, setBody] = useState(task.body)
const [id, setId] = useState(task._id);
const [reminder, setReminder] = useState(task.reminder)
const [dueDate, setDueDate] = useState(task.dueDate)
const [calendar, setCalendar] = useState(false);
const toDoContext = useToDoContext();
const updateToDoContext = useUpdateToDoContext();
function editTask() {
post("/api/task/editTask", {
"id": id,
title,
body
})
.then((resJson) => {
if (resJson.error === true) {
console.log("Error submiting new task");
} else {
updateToDoContext({...toDoContext, reloadBuckets: true})
setReload(true);
}
})
}
function deleteTask() {
post("/api/task/deleteTask", { "id": id, })
.then((resJson) => {
if (resJson.error === true) {
console.log("Error submiting new task");
}
console.log("deleting and reloading")
setReload(true);
})
}
function onBucket(e) {
setBuckets(e.target.value)
}
function onTitle(e) {
setTitle(e.target.value)
}
function onBody(e) {
setBody(e.target.value);
}
function onReminder(e) {
setReminder(e.target.value);
}
function moveTomorrow() {
setDueDate(dueDate + 1);
}
function parseBody() {
let track = false;
var newBody = '';
for (const char in body) {
if (body[char] == "#") {
track = true;
continue
}
if (body[char] == " " && track == true) {
track = false;
continue
}
if (!track) {
newBody = newBody.concat(body[char]);
}
}
return newBody;
}
function toggleCalendar(e) {
console.log("what happened")
setCalendar(!calendar);
}
return (
<div className='task-item' >
<div className='task-header'>
<h3 className='task-element task-title'>{title}</h3>
<div className='fade-out'></div>
<span className='task-icons'>
<FaSun className='task-icon' onClick={moveTomorrow} />
{/* Placeholder for "Move to tomorrow" icon */}
<FaEdit className='task-edit task-icon' onClick={editTask} />
{/* Edit Task */}
<FaTimes className='task-delete task-icon' onClick={deleteTask} />
{/* Delete */}
<FaCalendar className='task-delete task-icon' onClick={toggleCalendar}/>
</span>
</div>
<div className='task-body'>
<p className='task-element'>{parseBody()}</p>
{buckets.map((bucket, index) => (
<span className='task-bucket task-element'key={index}>#{bucket}</span>
))}
</div>
<span>
{dueDate}
</span>
{calendar ? <Schedule/> : null}
</div>
)
}
export default Task
/*
Notes:
This will be the area where I add features to the a given task's template.
This will edit the format of all Tasks that will be mapped in Tasks.jsx file.a
*/
This is an example of before I try and delete anything. All tasks are rendered properly.
Before Image
This is after I delete the task titled "Task to be deleted":
After Image
Here you can see that in fact the task that was deleted is still being showed, while the last task was removed. The "Tasks" component tasks state is being showed on the right, it does not contain the deleted task, and does contain the last task that is no longer being showed.
I have printed in the console what should be returned by the "Tasks" component, it also shows the correct tasks. It is like react is not rendering what is being returned by the "Tasks" component. I am not sure what is causing this issue, any help would be greatly appreciated.
You should setReload is false before call api in deleteTask. It make sure reload will change after fetch API success and useEffect will be called.
function deleteTask() {
setReload(false);
post("/api/task/deleteTask", { id: id }).then((resJson) => {
if (resJson.error === true) {
console.log("Error submiting new task");
}
console.log("deleting and reloading");
setReload(true);
});
}
A friend looked at this and gave me an answer, I found a second one as well.
React uses the keys of the elements in a list to keep track of which elements have been changed, updated, or deleted. The components were not being recreated as I thought they were on each render. Instead react was seeing that there is one less key, so the task with the key that was missing (the last task) was not being displayed. Each key, while unique, was not associated with a specific task. It was just the index of the task in the array. One solution provided by my friend was to add a useEffect method to my Task component to have each task update when the task prop being passed in changes like so:
useEffect(() => {
setTitle(task.title);
setBuckets(task.buckets);
setBody(task.body);
setId(task._id);
}, [task])
Another solution is to set the key of each task to the id of the task. This way each task is provided with a key that can identify it directly, not by index. A key at any index that changes will then be updated. However if the contents of a task change, a task will not be updated because the key will remain the same.

Categories

Resources