Reputation: 43
I'm creating a job site where you can add specific job cards to a favorites section by using the ContextAPI and useReducer hook for global state management.
I followed the code for using contextAPI and useReducer through this video on youtube: https://www.youtube.com/watch?v=awGFsGc9oCM&t=946s, attached is also the sandbox code for it https://codesandbox.io/s/usereducer-hook-swkwl
The favoriting function now works, however the next step I'd like to achieve is to persist those favorited jobs upon page refresh using localStorage.
I found this stackoverflow site Persist localStorage with useReducer showing how to initialize state with localStorage, but I'm having some issues incorporating the implementation, specifically that in my application, you can see how when each job is added to favorites, it is also added in localstorage, however the content of the localstorage resets to an empty array after page refresh. I'm missing something but I'm not sure where the problem is.
JobCard.js
import { useState, useEffect } from "react";
import useJob from "../context/JobContext";
const JobCard = ({id,title,responsibilities,job,locations,updated_date,url}) => {
const {jobArray, addToFavorite, removeFavorite} = useJob();
const [isInFav,setIsInFav] = useState(false);
useEffect(() => {
const jobIsInFavorite = jobArray.find((job) => job.id === id);
if (jobIsInFavorite) {
setIsInFav(true);
} else {
setIsInFav(false);
}
}, [jobArray, id]);
const handleClick = () => {
const singleJob = {id,title,responsibilities,job,locations,updated_date,url}
if (isInFav) {
removeFavorite(singleJob);
} else {
addToFavorite(singleJob);
}
};
return (
<div>
<div className="container" key = {id}>
<div className="card">
<div className="row ">
<div className="card-title col-sm-6">
<p className="heading">{title}</p>
<p className="col-4 col-md-5"><img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRk4fgpQ_EFjQ96BAtFlYIPIjfQdcrWsRzwzQ&usqp=CAU" className="icon2"></img> {job.company.name}</p>
<p className="text-muted">{responsibilities}</p>
<span className="col-4 col-md-4"><img src="https://toppng.com/uploads/preview/location-pin-comments-location-icon-small-11562928969ztngidtv1e.png" className="icon"></img> {locations != 0 ? locations[0].value : null}</span>
</div>
<div className="job-info col-sm-4">
<p><strong>Job Type:</strong> {job.type == 1 ? "Intern" : ""} </p>
{/* isn't updated different from posted date? we should put posted date if possible*/}
<p><strong>Updated:</strong> {updated_date.substring(0,10)} </p>
</div>
<div className="apply-btn col-sm">
<button type="button" className="btn btn-outline-primary btn-md float-right"><a href={url}> Apply Now </a></button>
<button type="button" className="btn btn-outline-primary btn-md float-right" onClick={handleClick} isInFav={isInFav}><p>{isInFav ? "-" : "+"}</p></button>
</div>
</div>
</div>
</div>
</div>
)
}
export default JobCard;
JobContext.js
import { createContext, useReducer, useContext,useEffect } from "react";
import jobReducer, { initialState,initializer } from "./JobReducer";
const JobContext = createContext(initialState)
export const JobProvider = ({children}) => {
const [state,dispatch] = useReducer(jobReducer,initialState,initializer);
useEffect(() => {
localStorage.setItem("favorited-jobs", JSON.stringify(state));
}, [state]);
const addToFavorite = (job) => {
const updatedJobs = state.jobArray.concat(job)
dispatch({
type: "ADD_TO_FAVORITE",
payload:{
jobArray:updatedJobs
}
})
}
const removeFavorite = (job) => {
const updatedJobs = state.jobArray.filter((currentJob) =>
currentJob.id !== job.id)
dispatch({
type: "REMOVE_FROM_FAVORITE",
payload:{
jobArray:updatedJobs
}
})
}
const value = {
jobArray: state.jobArray,
addToFavorite,
removeFavorite,
}
return <JobContext.Provider value={value}>{children}</JobContext.Provider>
}
//custom hook
const useJob = () => {
const context = useContext(JobContext)
if(context === undefined){
throw new Error("Error")
}
return context
}
export default useJob
jobReducer.js
export const initialState = {
jobArray: []
}
export const initializer = (initialValue = initialState.jobArray) =>
JSON.parse(localStorage.getItem("favorited-jobs")) || initialValue;
const jobReducer = (state,action) => {
const {type,payload} = action;
switch(type) {
case "ADD_TO_FAVORITE":
console.log("ADD_TO_FAVORITE",payload)
return {
...state,
jobArray:payload.jobArray
}
case "REMOVE_FROM_FAVORITE":
console.log("REMOVE_FROM_FAVORITE",payload)
return {
...state,
jobArray:payload.jobArray
}
default:
throw new Error("Nothing ")
}
}
export default jobReducer
Favorites.js
import { useEffect, useState } from "react";
import JobCard from "../components/JobCard";
import useJob from "../context/JobContext";
const Favorites = () => {
const {jobArray} = useJob();
return(
<div className="favorite_list">
{jobArray.map((job, index) => (
<JobCard key={index} {...job} />
))}
</div>
)
}
export default Favorites
Upvotes: 2
Views: 1849
Reputation: 2530
Try this:
useEffect(() => {
if(state.jobArray.length) { // <- make sure array is not empty
localStorage.setItem("favorited-jobs", JSON.stringify(state));
}
}, [state]);
Everytime page refresh, state is initialized to empty array, and local storage will pick up the initial state, override previous value. By doing a empty check, the localstorage data won't be overridden. BTW, it's generally a good idea to do a empty check before updating array.
Sandbox to test out useEffect approach
On a second look, you acctually don't have to rely on useEffect
to update localStorage, a better way would be:
// delete useEffect first
const addToFavorite = (job) => {
const updatedJobs = state.jobArray.concat(job)
localStorage.setItem("favorited-jobs", JSON.stringify(updatedJobs )); // update directly
dispatch({
type: "ADD_TO_FAVORITE",
payload:{
jobArray:updatedJobs
}
})
}
const removeFavorite = (job) => {
const updatedJobs = state.jobArray.filter((currentJob) =>
currentJob.id !== job.id)
localStorage.setItem("favorited-jobs", JSON.stringify(updatedJobs )); // update directly
dispatch({
type: "REMOVE_FROM_FAVORITE",
payload:{
jobArray:updatedJobs
}
})
}
Upvotes: 1