I am trying to use react hooks and context to create a scheduling app. The part that I am currently working on is using a "Task Creator" to add tasks to the schedule, which can be previewed above it in real time. I cannot get the tasks to add to the preview when they are added to the context. I have to refresh the page for new tasks to show in the preview. I will list below the code links and things that Ive tried, that I couldn't make it work with. After trying all of those things there are a couple of things that stand out to me and I feel are important, but I cannot figure out how to proceed even knowing them.
-The tasks in the local storage updates immediately when I add them using the task creator
-The tasks don't stay added in the local storage if I delete a task currently in the local storage before reloading the page, I think this indicates that there is some sort of disconnect between the local storage being updated and the tasks in memory that are actually being used in memory - if this is the case I cannot figure out how to remedy it
-Tasks delete in real time on the schedule preview, the page does not need to be reloaded
The code that I think is the most relevant are the reducer, context, task creator, schedule preview and main (the parent of both task creator and schedule preview) - I think the issue is somewhere in there
Schedule Preview
import React, { useContext, useEffect } from "react";
import TaskBlock from "../taskblock";
import ScheduleContextProvider, {
ScheduleContext,
} from "../../contexts/ScheduleContext";
import Task from "../models/taskmodel";
const SchedulePreview = (props) => {
const { tasks } = useContext(ScheduleContext);
console.log(tasks);
return (
<React.Fragment>
<div className="schedulepreviewcontainer">
<ScheduleContextProvider value={tasks}>
<div className="schedulepreview">
{tasks.length
? tasks.map((task) => {
return <TaskBlock tID={task.tID} key={task.tID} />;
})
: null}
</div>
</ScheduleContextProvider>
</div>
</React.Fragment>
);
};
export default SchedulePreview;
Task Creator
import React, { useEffect, useContext, useState } from "react";
import { ScheduleContext } from "../../contexts/ScheduleContext";
function TaskCreator(props) {
const { tasks, dispatch } = useContext(ScheduleContext);
const [taskName, setName] = useState("");
const [startTime, setStartTime] = useState("");
const [endTime, setEndTime] = useState("");
const [taskNotes, setNotes] = useState("");
const [subtasks, setSubtasks] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
dispatch({
type: "ADD_TASK",
task: {
taskName,
startTime,
endTime,
taskNotes,
subtasks,
},
});
setName("");
setStartTime("");
setEndTime("");
setNotes("Notes: ");
setSubtasks([]);
};
return (
<React.Fragment>
<form onSubmit={handleSubmit}>
<div id="taskcreator">
<div className="lineup">
<div>
<input
className="tasknameinput taskcreationinput"
placeholder="Task Name"
type="text"
value={taskName}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div>
<label htmlFor="starttime"></label>
<input
className="taskcreationinput timeinput"
type="time"
name="starttime"
onChange={(e) => setStartTime(e.target.value)}
value={startTime}
/>
<label htmlFor="endtime"></label>
<input
className="taskcreationinput timeinput"
type="time"
name="endtime"
onChange={(e) => setEndTime(e.target.value)}
value={endTime}
/>
</div>
<div>
<div className="create list of subtasks"></div>
<div className="creater of subtasks">
<input
className="subtasknameinput taskcreationinput"
placeholder="Subtask Name"
type="text"
/>
</div>
</div>
<div className="secondpart">
<label className="switch" id="typeselector">
<input type="checkbox" />
<span className="slider"></span>
</label>
indicator
</div>
</div>
<textarea
cols="28"
rows="5"
className="tasknotesinput taskcreationinput"
onChange={(e) => setNotes(e.target.value)}
value={taskNotes}
></textarea>
<div>
<input type="submit" value="Add" />
</div>
</div>
</form>
</React.Fragment>
);
}
export default TaskCreator;
Main
import React, { useContext } from "react";
import CreationSidebar from "./creationsidebar";
import SchedulePreview from "./schedulepreview";
import TaskCreator from "./taskcreator";
import BackHeader from "../backheader";
import ScheduleContextProvider, {
ScheduleContext,
} from "../../contexts/ScheduleContext";
function Main() {
const { tasks, dispatch } = useContext(ScheduleContext);
return (
<React.Fragment>
<div className="doubler">
<div>
<CreationSidebar />
</div>
<div className="content">
<ScheduleContextProvider>
<BackHeader />
<SchedulePreview props={tasks} />
<TaskCreator props={tasks} />
</ScheduleContextProvider>
</div>
</div>
</React.Fragment>
);
}
export default Main;
Schedule Reducer
export const scheduleReducer = (state, action) => {
switch (action.type) {
case "ADD_TASK":
return [
...state,
{
taskName: action.task.taskName,
tID: Math.random() * 900000,
pID: 1,
startTime: action.task.startTime,
endTime: action.task.endTime,
isComplete: false,
taskNotes: action.task.taskNotes,
subtasks: action.task.subtasks,
},
];
case "REMOVE_TASK":
return state.filter((task) => task.tID !== action.tID);
default:
return state;
}
};
Schedule Context
import React, { createContext, useReducer, useEffect } from "react";
import { scheduleReducer } from "../reducers/schedulereducer";
import ReactDOM from "react-dom";
export const ScheduleContext = createContext();
const ScheduleContextProvider = (props) => {
const [tasks, dispatch] = useReducer(scheduleReducer, [], () => {
const localData = localStorage.getItem("tasks");
return localData ? JSON.parse(localData) : [];
});
useEffect(() => {
localStorage.setItem("tasks", JSON.stringify(tasks));
}, [tasks]);
return (
<ScheduleContext.Provider value={{ tasks, dispatch }}>
{props.children}
</ScheduleContext.Provider>
);
};
export default ScheduleContextProvider;
The things I have tried include re-rendering only the preview when the reducer is called using the effect hook, but that doesn't work for different reasons depending on how I tried to implement it, these are some of the resources I used attempting that.
https://reactjs.org/docs/thinking-in-react.html https://reactjs.org/docs/rendering-elements.html https://reactjs.org/docs/hooks-effect.html https://reactjs.org/docs/conditional-rendering.html https://reactjs.org/docs/state-and-lifecycle.html
I have tried removing props from anything that state could be used for instead and also passing props to basically everything in the hopes that at some point they would change and the component would re-render with the new state. I got these ideas from Why is my react component not updating with state updates?
after this I kind of started trying random things because I couldn't figure it out.
I tried changing
ScheduleContextProvider
toScheduleContext.Provider
which didn't do anything (No error or anything just would never load) I also tried addedScheduleContext.Consumer
places but that didnt work either.I have also tried just calling the default reducer function to try to "refresh" the state whenever a task is created
I recently learned about hooks and context and reducers from a tutorial on Youtube, the code from which I was able to run perfectly fine prior to using hooks here. I spent a lot of time comparing my code to the tutorial code (https://github.com/iamshaunjp/react-context-hooks) but cannot figure out why it works there but not on this application.
I don't know if its helpful but the full code is on GitHub here
And I have the current (in Progress Version) on GitHub Pages Here -***
Please Let me know if you need any more information
Don't know why but changing the schedule preview to
import React, { useContext } from "react";
import TaskBlock from "../taskblock";
import { ScheduleContext } from "../../contexts/schedulecontext";
const SchedulePreview = () => {
const { tasks } = useContext(ScheduleContext);
console.log(tasks);
return (
<div className="schedulepreviewcontainer">
<div className="schedulepreview">
<ul>
{tasks.map((task) => {
return <TaskBlock task={task} key={task.tID} />;
})}
</ul>
</div>
</div>
);
};
export default SchedulePreview;
Fixed it, basically just putting the taskblock elements into a UL.