Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. - useEffect() - javascript

I get this error when I try and call a function I have imported within my useEffect() hook in Dashboard.jsx. I am just trying to pull in data from database on the page load pretty much so that when user click button they can send off correct credentials to the api.
I am pulling it in from database for security reasons, so client id is not baked into the code.
I am pretty sure that I am getting this error maybe because the function is not inside a react component? although I am not 100% sure. And if that is the case I am not sure of the best way to restructure my code and get the desired output.
Code below.
mavenlinkCredentials.js
import { doc, getDoc } from "firebase/firestore";
import { useContext } from "react";
import { AppContext } from "../../context/context";
import { db } from "../../firebase";
const GetMavenlinkClientId = async () => {
const {setMavenlinkClientId} = useContext(AppContext)
const mavenlinkRef = doc(db, 'mavenlink', 'application_id');
const mavenlinkDocSnap = await getDoc(mavenlinkRef)
if(mavenlinkDocSnap.exists()){
console.log("mavenlink id: ", mavenlinkDocSnap.data());
console.log(mavenlinkDocSnap.data()['mavenlinkAccessToken'])
setMavenlinkClientId(mavenlinkDocSnap.data()['application_id'])
} else {
console.log("No doc");
}
}
export default GetMavenlinkClientId;
Dashboard.jsx
import React, { useContext, useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { useNavigate } from "react-router-dom";
import { query, collection, getDocs, where, setDoc, doc, getDoc } from "firebase/firestore";
import { auth, db, logout } from "../firebase";
import { Button, Container, Grid, Paper } from "#mui/material";
import ListDividers from "../components/ListDividers";
import { AppContext } from "../context/context";
import axios from "axios";
import {SuccessSnackbar, ErrorSnackbar} from '../components/PopupSnackbar';
import GetMavenlinkClientId from "../helpers/firebase/mavenlinkCredentials";
const Dashboard = () => {
const [user, loading, error] = useAuthState(auth);
const [name, setName] = useState("");
const [ accessToken, setAccessToken ] = useState("")
const [errorAlert, setErrorAlert] = useState(false);
const [successAlert, setSuccessAlert] = useState(false);
const [mavenlinkClientId, setMavenlinkClientId] = useState("");
const {isAuthenticated} = useContext(AppContext);
const navigate = useNavigate();
const uid = user.uid
const parsedUrl = new URL(window.location.href)
const userTokenCode = parsedUrl.searchParams.get("code");
const { mavenlinkConnected, setMavenlinkConnected } = useContext(AppContext)
const { maconomyConnected, setMaconomyConnected } = useContext(AppContext)
const { bambooConnected, setBambooConnected } = useContext(AppContext)
const fetchUserName = async () => {
try {
const q = query(collection(db, "users"), where("uid", "==", user?.uid));
const doc = await getDocs(q);
const data = doc.docs[0].data();
setName(data.name);
} catch (err) {
console.error(err);
alert("An error occured while fetching user data");
}
};
//
useEffect(() => {
if (loading) return;
if (!user) return navigate("/");
fetchUserName();
if(userTokenCode !== null){
authorizeMavenlink();
}
if(isAuthenticated){
GetMavenlinkClientId()
}
}, [user, loading]);
///put this into a page load (use effect maybe) so user does not need to press button to connect to apis
const authorizeMavenlink = () => {
console.log(uid);
const userRef = doc(db, 'users', uid);
axios({
//swap out localhost and store in variable like apitool
method: 'post',
url: 'http://localhost:5000/oauth/mavenlink?code='+userTokenCode,
data: {}
})
.then((response) => {
setAccessToken(response.data);
setDoc(userRef, { mavenlinkAccessToken: response.data}, { merge: true });
setMavenlinkConnected(true);
setSuccessAlert(true);
})
.catch((error) => {
console.log(error);
setErrorAlert(true)
});
}
//abstract out client id and pull in from db
const getMavenlinkAuthorization = () => {
window.open('https://app.mavenlink.com/oauth/authorize?client_id='+mavenlinkClientId+'&response_type=code&redirect_uri=http://localhost:3000');
window.close();
}
const authorizeBamboo = () => {
axios({
method: 'get',
url: 'http://localhost:5000/oauth/bamboo',
data: {}
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error);
});
// console.log('bamboo connected')
setBambooConnected(true);
}
const authorizeMaconomy = () => {
console.log("Maconomy connected")
setMaconomyConnected(true);
}
const syncAccount = async() => {
if(!mavenlinkConnected){
await getMavenlinkAuthorization()
}
if (!bambooConnected){
await authorizeBamboo();
}
if (!maconomyConnected){
await authorizeMaconomy();
}
}
const handleAlertClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSuccessAlert(false) && setErrorAlert(false);
};
console.log(mavenlinkClientId);
return(
<>
<Container>
<div className="dashboard">
<h1>Dashboard</h1>
<Grid container spacing={2}>
<Grid item xs={12}>
<Paper style={{paddingLeft: "120px", paddingRight: "120px"}} elevation={1}>
<div className="dashboard-welcome">
<h2>Welcome {name}</h2>
<h4>{user?.email}</h4>
<hr/>
<h2>Integrations</h2>
<Button onClick={syncAccount}>
Sync Account
</Button>
{/* <Button onClick={getMavenlinkClientId}>
Bamboo Test
</Button> */}
<ListDividers/>
</div>
</Paper>
</Grid>
</Grid>
</div>
{successAlert === true ? <SuccessSnackbar open={successAlert} handleClose={handleAlertClose}/> : <></> }
{errorAlert === true ? <ErrorSnackbar open={errorAlert} handleClose={handleAlertClose}/> : <></> }
</Container>
</>
);
}
export default Dashboard;

the error is because you’re calling const {setMavenlinkClientId} = useContext(AppContext) inside the file mavenlinkCredentials.js which is not a react components.
you could maybe change the function inside mavenlinkCredentials.js to accept a setMavenlinkClientId and pass it from outside like this.
const GetMavenlinkClientId = async (setMavenlinkClientId) => {
const mavenlinkRef = doc(db, 'mavenlink', 'application_id');
const mavenlinkDocSnap = await getDoc(mavenlinkRef)
if(mavenlinkDocSnap.exists()){
console.log("mavenlink id: ", mavenlinkDocSnap.data());
console.log(mavenlinkDocSnap.data()['mavenlinkAccessToken'])
setMavenlinkClientId(mavenlinkDocSnap.data()['application_id'])
} else {
console.log("No doc");
}
}
and then you can call this function in your dashboard.js like so,
const {setMavenlinkClientId} = useContext(AppContext)
if(isAuthenticated){
GetMavenlinkClientId(setMavenlinkClientId)
}

Related

React useEffect hook will cause an infinite loop if I add tasksMap to the depedency array

If I add tasksMap to the useEffect dependency array below an infinite loop will happen. Can someone point me in the right direction on how to fix this? In order for the user to get an updated view of the tasks that have been added or modified, I need the app to call getProjectTasks and assign the returned map to the tasksMap. I do know that anytime you update state the component rerenders. I just havne't figured out how to do this without creating an infinite loop. Any help is greatly appreciated. Thank you.
import { useContext, useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { UserContext } from "../../contexts/user.context";
import { ProjectsContext } from "../../contexts/projects.context";
import { createProjectTask, getProjectTasks } from "../../utils/firebase/firebase.utils";
import OutlinedCard from "../../components/cards/TaskCard.component";
import { TextField, Button, } from "#mui/material";
import "./project.styles.css";
import "./project.styles.css";
const Project = () => {
const params = useParams();
const projectId = params.id;
const { currentUser } = useContext(UserContext);
const { projectsMap } = useContext(ProjectsContext);
const [taskName, setTaskName] = useState("");
const [tasksMap, setTasksMap] = useState({});
const project = Object.keys(projectsMap)
.filter((id) => id.includes(projectId))
.reduce((obj, id) => {
return Object.assign(obj, {
[id]: projectsMap[id],
});
}, {});
useEffect(() => {
console.log("running")
const getTasksMap = async () => {
const taskMap = await getProjectTasks(currentUser, projectId);
taskMap ? setTasksMap(taskMap) : setTasksMap({});
};
getTasksMap();
}, [projectId])
const handleChange = (event) => {
const { value } = event.target;
setTaskName(value);
};
const handleSubmit = async (event) => {
event.preventDefault();
try {
await createProjectTask(currentUser, projectId, taskName);
setTaskName("");
} catch (error) {
console.log(error);
}
};
return (
<div className="project-container">
{project[projectId] ? <h2>{project[projectId].name}</h2> : ""}
<form onSubmit={handleSubmit} className="task-form">
<TextField label="Project Task" onChange={handleChange} value={taskName}></TextField>
<Button type="submit" variant="contained">
Add Task
</Button>
</form>
<div className="tasks-container">
{Object.keys(tasksMap).map((id) => {
const task = tasksMap[id];
return (
<OutlinedCard key={id} projectId={projectId} taskId={id} name={task.name}></OutlinedCard>
);
})}
</div>
</div>
);
};
export default Project;
This is where the taskMap object comes from. For clarification, I'm using Firebase.
export const getProjectTasks = async(userAuth, projectId) => {
if(!userAuth || !projectId) return;
const tasksCollectionRef = collection(db, "users", userAuth.uid, "projects", projectId, "tasks")
const q = query(tasksCollectionRef);
try {
const querySnapshot = await getDocs(q);
const taskMap = querySnapshot.docs.reduce((acc, docSnapshot) => {
const id = docSnapshot.id;
const { name } = docSnapshot.data();
acc[id] = {id, name};
return acc;
}, {});
return taskMap;
} catch (error) {
console.log("Error getting task docs.");
}
};
The useEffect appears to be setting tasksMap when executed. Because this state is an object its reference will change everytime, which will produce an infinite loop

Context userdata is an empty object in useEffect hook

I have a problem in my code. useEffect cant read imported object. As you see user object is imported from another context. When i try to use this user object in useEffect of DataContext it returns an empty object. I think useEffect is rendered before that state has a value but I don't have idea how to fix it. I want to access user.email in useEffect instead of email(iashviligiorgi3#gmail.com) so firebase will filter emails and give me the object of current user. Thanks. I am begginer and don't know a lot of things yet.
DataContext.js :
import { createContext, useContext, useEffect, useState } from "react";
import { db, storage, storedb } from "../firebase";
import { uid } from "uid";
import { set, ref, onValue } from "firebase/database";
import {
doc,
setDoc,
updateDoc,
collection,
addDoc,
query,
where,
getDocs,
getDoc,
onSnapshot,
collectionGroup,
limit,
} from "firebase/firestore";
import {
ref as storageref,
uploadBytesResumable,
getDownloadURL,
} from "firebase/storage";
import { useUserAuth } from "./UserAuthContext";
const dataContext = createContext();
export function DataContextProvider({ children }) {
const { user } = useUserAuth();
const [category, setCategory] = useState("");
const [contactnumber, setContactNumber] = useState("");
const [description, setDescription] = useState("");
const [price, setPrice] = useState("");
const [sellername, setSellerName] = useState("");
const [title, setTitle] = useState("");
const [imageurl, setImageUrl] = useState("");
const [imageUpload, setImageUpload] = useState(null);
const [imageuploaddone, setImageUploadDone] = useState("");
// Data for Users and Items
const [items, setItems] = useState([]);
const [userdata, setUserData] = useState([]);
console.log(items);
const userUid = user ? user.uid : null;
const uploadImage = async () => {
try {
const uuid = uid();
if (imageUpload == null) return;
const imageRef = storageref(
storage,
`images/${uuid} + ${imageUpload.name}`
);
const uploadTask = uploadBytesResumable(imageRef, imageUpload);
uploadTask.on(
"state_changed",
(snapshot) => {
// progrss function ....
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
setImageUploadDone("Upload is " + progress.toFixed() + "% done");
},
(error) => {
// error function ....
console.log(error);
},
() => {
// complete function ....
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
setImageUrl(downloadURL);
});
}
);
} catch (error) {
throw error;
}
};
const writeToDatabase = () => {
const uuid = uid();
const docRef = doc(storedb, "users", user.email);
const colRef = collection(docRef, "items");
addDoc(colRef, {
uuid,
category,
contactnumber,
description,
price,
sellername,
title,
userUid,
imageurl,
});
setTitle("");
setCategory("");
setContactNumber("");
setDescription("");
setPrice("");
setSellerName("");
setImageUrl("");
};
const changeHandler = () => {
writeToDatabase();
};
useEffect(() => {
if (user) {
console.log(user);
}
const docRef = doc(storedb, "users", "iashviligiorgi3#gmail.com");
const docSnap = getDoc(docRef).then((doc) => {
console.log(doc.data());
});
// const useremailref = collection(storedb, "users");
// const q = query(useremailref, where("email", "==", user.email));
// const getquery = getDocs(q).then((collection) => {
// setUserData(collection.docs.map((doc) => doc.data()));
// });
const itemsCollection = collectionGroup(storedb, "items");
const data = getDocs(itemsCollection).then((collection) => {
setItems(collection.docs.map((doc) => doc.data()));
});
console.log(items);
}, []);
return (
<dataContext.Provider
value={{
category,
setCategory,
contactnumber,
setContactNumber,
description,
setDescription,
price,
setPrice,
sellername,
setSellerName,
title,
setTitle,
imageurl,
setImageUrl,
changeHandler,
items,
setImageUpload,
uploadImage,
setImageUploadDone,
imageuploaddone,
imageUpload,
userdata,
}}
>
{children}
</dataContext.Provider>
);
}
export function useDataContext() {
return useContext(dataContext);
}
// import { useState, createContext, useContext } from "react";
// const dataContext = createContext();
// export function DataContextProvider({ children }) {
// const data = "1245r1";
// return (
// <dataContext.Provider value={{ data }}>{children}</dataContext.Provider>
// );
// }
// export function useDataContext() {
// return useContext(dataContext);
// }
UserAuthContext.js :
import { createContext, useContext, useEffect, useState } from "react";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
signOut,
GoogleAuthProvider,
signInWithPopup,
sendEmailVerification,
} from "firebase/auth";
import { auth } from "../firebase";
import { storedb } from "../firebase";
const userAuthContext = createContext();
export function UserAuthContextProvider({ children }) {
const [user, setUser] = useState({});
function emailVerification() {
return sendEmailVerification(auth.currentUser);
}
function logIn(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
function signUp(email, password) {
return createUserWithEmailAndPassword(auth, email, password);
}
function logOut() {
return signOut(auth);
}
function googleSignIn() {
const googleAuthProvider = new GoogleAuthProvider();
return signInWithPopup(auth, googleAuthProvider);
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentuser) => {
// console.log("Auth", currentuser);
setUser(currentuser);
});
return () => {
unsubscribe();
};
}, []);
return (
<userAuthContext.Provider
value={{ user, logIn, signUp, logOut, googleSignIn, emailVerification }}
>
{children}
</userAuthContext.Provider>
);
}
export function useUserAuth() {
return useContext(userAuthContext);
}
App.js :
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { UserAuthContextProvider } from "./context/UserAuthContext";
import { DataContextProvider } from "./context/DataContext";
import { SearchContextProvider } from "./context/SearchContext";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<UserAuthContextProvider>
<DataContextProvider>
<SearchContextProvider>
<App />
</SearchContextProvider>
</DataContextProvider>
</UserAuthContextProvider>
);
You have to add currentuser to dependency array, so when value of current user will change useEffect will be re-render and assign new value to user state
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentuser) => {
// console.log("Auth", currentuser);
setUser(currentuser);
});
return () => {
unsubscribe();
};
}, [currentuser]);

invalidateQueries doesn't refetch/refresh the page

I have a button that, on click, has to perform a thing that makes a list displayed on my page to change, and in theory the page should reload. However, that never happens, no matter how many times I click my button.
Full code of my button:
import React from 'react';
import { useSaveOrderItemsForList } from '../../hooks/Lists/useSaveOrderItemsForList';
import ErrorIndicator from '../shared/ErrorIndicator';
import LoadingButton from '../shared/LoadingButton';
import { valueState as valueStateAtom } from '../../atoms/orderItemsAtom';
import { useSetRecoilState } from 'recoil';
export default function SaveOrderItemsButton({ orderItems, listID }) {
const { isError, error, isLoading, mutate } = useSaveOrderItemsForList(orderItems, listID);
const setValue = useSetRecoilState(valueStateAtom);
const handleClick = () => {
mutate(orderItems, listID);
setValue([]);
}
return (
<div className={'w-100'}>
<br />
<ErrorIndicator isError={isError} error={error} />
<LoadingButton
className={'w-100'}
variant={'success'}
loading={isLoading}
onClick={handleClick}
>
Save
</LoadingButton>
</div>
);
}
As for the code of my custom hook:
import { getToken } from '../../tokens/getToken';
import { basePath } from '../../config/basePath';
import { getTokenAuthHeaders } from '../../functions/sharedHeaders';
import { useMutation, useQueryClient } from 'react-query';
async function saveOrderItemsForList(orderItems, listID) {
const token = await getToken();
const response = await fetch(`${basePath}/lists/save_order_items/${listID}`, {
method: 'PUT',
body: JSON.stringify({ orderItems }),
headers: getTokenAuthHeaders(token)
});
return response.json();
}
export function useSaveOrderItemsForList() {
const queryClient = useQueryClient();
return useMutation(saveOrderItemsForList,
{
onSuccess: () => {
return queryClient.invalidateQueries('lists');
}
}
);
}
My theory is that, since I'm managing the organizing of my list client-side, the page doesn't get updated with the information I passed (this is the code of the page that shows the list):
import Col from 'react-bootstrap/Col';
import CardsList from './CardsList';
import { useList } from '../../hooks/Cards/useList';
import useOrderItemsForCardsInList from '../../hooks/Lists/useOrderItemsForCardsInList';
import usePaginateCardsInList from '../../hooks/Cards/usePaginateCardsInList';
import LoadingAndErrorCentered from '../shared/LoadingAndErrorCentered';
export default function List({ listID }) {
const { isLoading, isError, error, data } = useList(listID);
const { data: orderItems } = useOrderItemsForCardsInList(listID);
const pagesArray = usePaginateCardsInList(orderItems, data);
return (
<Col xs={12}>
<br />
<LoadingAndErrorCentered isLoading={isLoading} isError={isError} error={error} />
{data && <CardsList cards={pagesArray} listID={listID} />}
</Col>
);
}
What do you guys think?
Edit: This is the code of my useList hook.
import { useQuery } from 'react-query';
import { getTokenAuthHeaders } from '../../functions/sharedHeaders';
import { basePath } from '../../config/basePath';
import { getToken } from '../../tokens/getToken';
async function getList(listID) {
const token = await getToken();
const response = await fetch(`${basePath}/cards/list/${listID}`, {
method: 'GET',
headers: getTokenAuthHeaders(token)
});
return response.json();
}
export function useList(listID) {
return useQuery(['cards', 'list', listID], () => {
return getList(listID);
});
}
and on my server, I have this function declared on my endpoint:
static async getList(id) {
const query = await List.findById(id).exec();
return query;
}
queryClient.invalidateQueries('lists');
vs
useQuery(['cards', 'list', listID], () => {});
You are not invalidating the right query keys, so naturally the query doesn't refetch. You need to use the correct key for invalidation, in your case:
queryClient.invalidateQueries(['cards', 'list']);

Refreshing UI after deleting item in React

I'm new to React and I have the issue that my UI ain't refreshing once I send a delete fetch in my React app. I tried to use a useEffect on my deleteTaskHandler but it broke my code. Any ideas how to accomplish this refresh?
This is my Task.js file, which is receiving props from a TaskList.js file, and TaskList.js file sends a component to App.js:
import React, { useState } from 'react';
import classes from './Task.module.css';
const Task = (props) => {
const [isCompleted, setIsCompleted] = useState(props.isCompleted);
const changeCompleteStatus = () => {
setIsCompleted(!isCompleted);
}
const deleteTaskHandler = async () => {
try {
const key = props.id
const response = await fetch('http://localhost:5050/delete-task/' + key, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('Something went wrong!');
};
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
};
const updateTaskHandler = async () => {
const id = props.id
const taskData = {
id: id,
content: props.content,
isCompleted: !props.isCompleted,
dateCreation: props.dateCreation,
};
try {
const response = await fetch('http://localhost:5050/edit-task/' + id, {
method: 'PATCH',
body: JSON.stringify(taskData),
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Something went wrong!');
};
const data = await response.json();
console.log(data);
} catch (error) {
console.log(error);
}
};
let task;
if (props.isAllView) {
task = <div >
<input type="checkbox" onClick={updateTaskHandler} onChange={changeCompleteStatus} checked={isCompleted} />
<h2>{props.content}</h2>
<h3>{props.dateCreation}</h3>
<button onClick={deleteTaskHandler}>X</button>
</div>
} else {
task = <div >
<h2>{props.content}</h2>
<h3>{props.dateCreation}</h3>
</div>
}
return (
<li>{task}</li>
);
};
export default Task;
This is TaskList.js:
import React, { useState } from 'react';
import classes from './TaskList.module.css';
import Task from './Task';
const TaskList = (props) => {
const [taskView, setTaskView] = useState('all');
const getCompleteURL = () => {
setTaskView('complete')
props.onChangeTaskURL('http://localhost:5050/completed');
};
const getAllURL = () => {
setTaskView('all')
props.onChangeTaskURL('http://localhost:5050/');
};
const getPendingURL = () => {
setTaskView('pending')
props.onChangeTaskURL('http://localhost:5050/pending');
};
let taskList;
if (taskView != 'all') {
taskList = props.taskData.map((task) => (
<Task
key={task.id}
content={task.content}
dateCreation={task.dateCreation}
isCompleted={task.isCompleted}
isAllView={false}
/>
));
} else {
taskList = props.taskData.map((task) => (
<Task
key={task.id}
id={task.id}
content={task.content}
dateCreation={task.dateCreation}
isCompleted={task.isCompleted}
isAllView={true}
/>
));
}
return (
<div>
<ul >
{taskList}
</ul>
<button onClick={getCompleteURL}>Completed</button>
<button onClick={getAllURL}>All</button>
<button onClick={getPendingURL}>Pending</button>
</div>
);
};
export default TaskList;
This is App.js:
import React, { useState, useEffect, useCallback } from 'react';
import './App.css';
import TaskList from './components/Tasks/TaskList';
import NewTask from './components/NewTask/NewTask';
function App() {
const [tasks, setTasks] = useState([]);
const [taskURL, setTaskURL] = useState('http://localhost:5050/');
const fetchTasksHandler = useCallback(async (url) => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const data = await response.json();
const loadedTasks = [];
for (const key in data) {
loadedTasks.push({
id: data[key]._id,
content: data[key].content,
isCompleted: data[key].isCompleted,
dateCreation: data[key].dateCreation
});
}
console.log(loadedTasks)
setTasks(loadedTasks);
} catch (error) {
// throw new Error('Something went wrong!');
console.log(error)
}
}, []);
useEffect(() => {
fetchTasksHandler(taskURL);
}, [fetchTasksHandler, taskURL]);
const changeTaskURL = url => {
console.log(url)
setTaskURL(url);
};
return (
<React.Fragment>
<TaskList taskData={tasks} onChangeTaskURL={changeTaskURL}></TaskList>
<NewTask></NewTask>
</React.Fragment>
);
}
export default App;
Extract deleteTaskHandler and updateTaskHandler in your App.js and pass them down to the TaskList => Task. In both methods, on successful operation update the tasks state array (for delete - filter out the deleted task, for update - swap the old task with the updated one). This way, the Task component will call the relevant handler which will update the parent tasks state, which in turn will spill down to the TaskList and Task and everything will get updated automatically.
Here is a sample. Consider it more as a pseudo code as you'll have to modify some of the parts to handle your case appropriately.
Your Task.js:
import React, { useState } from 'react';
import classes from './Task.module.css';
const Task = (props) => {
const {
updateTaskHandler,
deleteTaskHandler
} = props;
const [isCompleted, setIsCompleted] = useState(props.isCompleted);
const changeCompleteStatus = () => {
setIsCompleted(!isCompleted);
}
const updateHandler = () => {
const taskData = {
id: id,
content: props.content,
isCompleted: !props.isCompleted,
dateCreation: props.dateCreation,
};
updateTaskHandler(props.id, taskData);
};
const deleteHandler = () => {
deleteTaskHandler(props.id);
};
let task;
if (props.isAllView) {
task = <div >
<input type="checkbox" onClick={updateHandler} onChange={changeCompleteStatus} checked={isCompleted} />
<h2>{props.content}</h2>
<h3>{props.dateCreation}</h3>
<button onClick={deleteHandler}>X</button>
</div>
} else {
task = <div >
<h2>{props.content}</h2>
<h3>{props.dateCreation}</h3>
</div>
}
return (
<li>{task}</li>
);
};
export default Task;
TaskList.js:
import React, { useState } from 'react';
import classes from './TaskList.module.css';
import Task from './Task';
const TaskList = (props) => {
const [taskView, setTaskView] = useState('all');
const getCompleteURL = () => {
setTaskView('complete')
props.onChangeTaskURL('http://localhost:5050/completed');
};
const getAllURL = () => {
setTaskView('all')
props.onChangeTaskURL('http://localhost:5050/');
};
const getPendingURL = () => {
setTaskView('pending')
props.onChangeTaskURL('http://localhost:5050/pending');
};
let taskList;
if (taskView != 'all') {
taskList = props.taskData.map((task) => (
<Task
key={task.id}
content={task.content}
dateCreation={task.dateCreation}
isCompleted={task.isCompleted}
isAllView={false}
updateTaskHandler={props.updateTaskHandler}
deleteTaskHandler={props.deleteTaskHandler}
/>
));
} else {
taskList = props.taskData.map((task) => (
<Task
key={task.id}
id={task.id}
content={task.content}
dateCreation={task.dateCreation}
isCompleted={task.isCompleted}
isAllView={true}
updateTaskHandler={props.updateTaskHandler}
deleteTaskHandler={props.deleteTaskHandler}
/>
));
}
return (
<div>
<ul >
{taskList}
</ul>
<button onClick={getCompleteURL}>Completed</button>
<button onClick={getAllURL}>All</button>
<button onClick={getPendingURL}>Pending</button>
</div>
);
};
export default TaskList;
And App.js:
import React, { useState, useEffect, useCallback } from 'react';
import './App.css';
import TaskList from './components/Tasks/TaskList';
import NewTask from './components/NewTask/NewTask';
function App() {
const [tasks, setTasks] = useState([]);
const [taskURL, setTaskURL] = useState('http://localhost:5050/');
const fetchTasksHandler = useCallback(async (url) => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const data = await response.json();
const loadedTasks = [];
for (const key in data) {
loadedTasks.push({
id: data[key]._id,
content: data[key].content,
isCompleted: data[key].isCompleted,
dateCreation: data[key].dateCreation
});
}
setTasks(loadedTasks);
}
catch (error) {
// throw new Error('Something went wrong!');
console.log(error)
}
}, []);
const deleteTaskHandler = async (taskID) => {
try {
const response = await fetch(`http://localhost:5050/delete-task/${taskID}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('Something went wrong!');
};
const data = await response.json();
setTasks(tasks => {
return tasks.filter(task => task.id !== taskID)
});
}
catch (error) {
console.log(error);
}
};
const updateTaskHandler = async (taskID, taskData) => {
const id = props.id
const taskData = {
id: id,
content: props.content,
isCompleted: !props.isCompleted,
dateCreation: props.dateCreation,
};
try {
const response = await fetch(`http://localhost:5050/edit-task/${taskID}`, {
method: 'PATCH',
body: JSON.stringify(taskData),
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Something went wrong!');
};
const data = await response.json();
setTasks(tasks => {
return tasks.map(task => {
if (task.id !== taskID) {
return task;
}
else {
return data; // The updated task
}
})
});
}
catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchTasksHandler(taskURL);
}, [fetchTasksHandler, taskURL]);
const changeTaskURL = url => {
console.log(url)
setTaskURL(url);
};
return (
<React.Fragment>
<TaskList
taskData={tasks}
onChangeTaskURL={changeTaskURL}
deleteTaskHandler={deleteTaskHandler}
updateTaskHandler={updateTaskHandler}
/>
<NewTask />
</React.Fragment>
);
}
export default App;
You have to pass a callback that removes a task and pass it as a prop to the Task component.
In your App.js:
function deleteTask(id) {
setTasks(loadedTasks.filter(x => x.id !== id);
}
// ...
<TaskList taskData={tasks} onDelete={deleteTask}></TaskList>
In your TaskList.js:
tasks.map(task => <Task id={task.id} key={task.id} onDelete={props.deleteTask}></Task>)
In the Task.js call props.deleteTask(props.id) whenever you need to.
Note that passing a prop through two components or more is called "prop drilling" and should be avoided (by keeping the state in TaskList.js for example).
you can use
window.location.reload();
to refresh the page or
this.setState({});
to refresh the component or
const [value,setValue] = useState();
const refresh = ()=>{
setValue({});
}
to refresh the component using hooks
i hope you found this answer helpful

Using the Context API gives me undefined

So I'm using Auth0 for my user sign up. I'm trying to get the user id under sub:value to add to my database to identify with the post of a user. I'm trying to use a Context API in order to get the user info to put in my database.
react-auth0-spa.js
// src/react-auth0-spa.js
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
useEffect(() => {
const initAuth0 = async () => {
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
if (window.location.search.includes("code=") &&
window.location.search.includes("state=")) {
const { appState } = await auth0FromHook.handleRedirectCallback();
onRedirectCallback(appState);
}
const isAuthenticated = await auth0FromHook.isAuthenticated();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const user = await auth0FromHook.getUser();
setUser(user);
}
setLoading(false);
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await auth0Client.getUser();
setUser(user);
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await auth0Client.getUser();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
other.js (trying to get user info from react-auth0-spa.js)
class AddAlbum extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
let value = this.context;
console.log(value);
}
render() {
return (
)
}
AddAlbum.contextType = Auth0Context;
This gives me user: undefined
In my index.js I have this
ReactDOM.render(
<Auth0Provider
domain={config.domain}
client_id={config.clientId}
redirect_uri={window.location.origin}
onRedirectCallback={onRedirectCallback}
>
<App />
</Auth0Provider>,
document.getElementById("root")
);
Which I believe is giving me these results:
So I'm wondering why my Context API isn't working and giving me user: undefined.
You're logging the user when the component first mounts, which is long before the await auth0FromHook.getUser() call will complete. Log it in a componentDidUpdate, or check in a parent if that value is available, and don't mount the child component until it is.

Categories

Resources