Component rendering before finishing the useEffect - javascript

I have a component (ownPrescriptionsPanel) inside which I'm rendering another component (PrescriptionsList). Inside the parent component, I have a useEffect hook to fetch data using Axios for the child component (PrescriptionsList). The problem is no matter what I try, the PrescriptionsList is always empty and only gets populated when I refresh. I have three child components (all are PrescriptionsList components) but I've shown only one in the below code.
import React, { useEffect, useState } from "react";
import Axios from "axios";
import { PrescriptionsList } from "../../components/prescriptionsList/prescriptionsList";
import "./ownPrescriptionsPanelStyles.css";
export const OwnPrescriptionsPanel = () => {
const [pastPrescriptions, setPastPrescriptions] = useState([]);
const [openPrescriptions, setOpenPrescriptions] = useState([]);
const [readyPrescriptions, setReadyPrescriptions] = useState([]);
const [isBusy1, setIsBusy1] = useState(true);
useEffect(() => {
Axios.post(
"http://localhost:3001/getpatientprescriptions",
{
id: sessionStorage.getItem("id"),
},
{
headers: {
"Content-Type": "application/json",
},
}
).then((response) => {
console.log("getpatientprescriptions", response.data);
var resArr = []; //getting rid of the duplicates
response.data.filter(function (item) {
var i = resArr.findIndex(
(x) => x.prescriptionId === item.prescriptionId
);
if (i <= -1) {
resArr.push(item);
}
return null;
});
setPastPrescriptions(resArr);
setIsBusy1(false);
});
}, []);
if (isBusy1) {
return <div>loading</div>;
}
return (
<>
<PrescriptionsList
pastPrescriptions={pastPrescriptions}
heading="All prescriptions"
viewOnly={true}
prescriptionStatusOpen={false}
showPharmacy={false}
/>
</>
);
};
Edit: Given below is the code for PrescriptionList component
import React, { useState } from "react";
import Axios from "axios";
import DescriptionTwoToneIcon from "#mui/icons-material/DescriptionTwoTone";
import PresciptionModal from "../prescriptionModal/prescriptionModal";
import "./prescriptionsListStyles.css";
export const PrescriptionsList = ({
pastPrescriptions,
heading,
viewOnly,
showPharmacy,
}) => {
const [prescriptionDetails, setprescriptionDetails] = useState([]);
const [prescriptionDrugList, setPrescriptionDrugList] = useState([]);
const [open, setOpen] = useState(false);
const handleClose = () => {
console.log("close");
setOpen(false);
};
console.log("pastPrescriptions", pastPrescriptions);
const getPrescriptionDrugDetails = async (prescriptionId) => {
await Axios.post(
"http://localhost:3001/prescriptionDrugDetails",
{
prescriptionId: prescriptionId,
},
{
headers: {
"Content-Type": "application/json",
},
}
).then((response) => {
console.log("prescriptionDrugDetails", response.data);
setPrescriptionDrugList(response.data);
});
};
const handlePrescriptionClick = async (prescriptionDetails) => {
console.log("prescriptionDetails", prescriptionDetails);
setprescriptionDetails(prescriptionDetails);
await getPrescriptionDrugDetails(prescriptionDetails.prescriptionId);
setOpen(true);
};
const pastPrescriptionsList = pastPrescriptions.map((d) => (
<div
value={d}
onClick={() => handlePrescriptionClick(d)}
key={d.drugId}
className="prescriptionListItem"
>
<div style={{ width: "30px" }}>
<DescriptionTwoToneIcon fontSize="small" />
</div>
{d.prescriptionId}
</div>
));
const markPrescriptionComplete = async (d) => {
await Axios.post(
"http://localhost:3001/markcomplete",
{
prescriptionId: d.prescriptionDetails.prescriptionId,
pharmacyId: d.prescriptionDetails.pharmacyId,
},
{
headers: {
"Content-Type": "application/json",
},
}
);
console.log(
"prescriptionId, pharmacyId",
d.prescriptionDetails.prescriptionId,
d.prescriptionDetails.pharmacyId
);
window.location.reload(true);
};
return (
<div className="prescriptionsListContainer">
<div className="viewPrescriptionsLabel">{heading}</div>
<div className="prescriptionsContainer">{pastPrescriptionsList}</div>
{open && (
<PresciptionModal
open={open}
onClose={handleClose}
prescriptionDetails={prescriptionDetails}
prescriptionDrugList={prescriptionDrugList}
viewOnly={viewOnly}
// prescriptionStatusOpen={false}
markprescriptioncomplete={markPrescriptionComplete}
showPharmacy={showPharmacy}
/>
)}
</div>
);
};
I tried solution 1, solution 2 and the code shown above is using solution from geeksforgeeks. None seem to be working

Related

How to Transform data from the backend with react?

I have react native app. And I am calling a api url. But I don't want to have all the data from the api call. But some specific data. Like name and image.
So I have a service:
export const fetchCategoryData = async () => {
try {
const response = await fetch("http://10.14.220.60:8000/animal/categories/main_groups/", {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
console.log("RESSPONSE", response);
return await response.json();
} catch (error) {
console.error("There was a problem with the fetch operation:", error);
throw error;
}
};
export const categoryTransform = ({ results = [] }) => {
const mappedResults = results.map((categoryList) => {
//categoryList.images = categoryList.images;
return {
...categoryList,
};
});
console.log("MAPPEDRESULT", fetchCategoryData.response());
return mappedResults;
};
and data context:
import { Children, createContext, useEffect, useState } from "react";
import { categoryTransform, fetchCategoryData } from "./category.service";
export const CategoryContext = createContext();
export const CategoryContextProvider = ({ children }) => {
const [categoryList, setCategoryList] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const retrieveCategories = () => {
setLoading(true);
setTimeout(() => {
fetchCategoryData()
.then(categoryTransform)
.then((results) => {
setLoading(false);
setCategoryList(results);
})
.catch((err) => {
setLoading(false);
setError(err);
});
}, 200);
};
useEffect(() => {
retrieveCategories();
}, []);
console.log(categoryList);
return (
<CategoryContext.Provider
value={{
categoryList,
loading,
error,
}}>
{children}
</CategoryContext.Provider>
);
};
and component dat injects the data context:
import { FlatList, SafeAreaView, StatusBar } from "react-native";
import React, { useContext } from "react";
import { CategoryContext } from "../../../services/category/category.context";
import { CategoryInfoCard } from "../components/category-info-card.component";
import { Searchbar } from "react-native-paper";
import { Spacer } from "../../../components/spacer/spacer.component";
import styled from "styled-components/native";
const SafeArea = styled(SafeAreaView)`
flex: 1;
${StatusBar.currentHeight && `margin-top: ${StatusBar.currentHeight}px`};
`;
const SearchContainer = styled.View`
padding: ${(props) => props.theme.space[3]};
`;
const CategoryList = styled(FlatList).attrs({
contentContainerStyle: {
padding: 16,
},
})``;
export const CategoryScreen = () => {
const { categoryList } = useContext(CategoryContext);
return (
<SafeArea>
<SearchContainer>
<Searchbar />
</SearchContainer>
<CategoryList
data={categoryList}
renderItem={({ item }) => {
console.log("ITEMs", item);
return (
<Spacer position="bottom" size="large">
<CategoryInfoCard categoryList={item} />
</Spacer>
);
}}
keyExtractor={(item) => item.name}
/>
</SafeArea>
);
};
And this is a deault component with hard coded data:
/* eslint-disable prettier/prettier */
import { React, useEffect, useState } from "react";
import { Card } from "react-native-paper";
import { Spacer } from "../../../components/spacer/spacer.component";
import { Text } from "../../../components/typography/text.component";
import styled from "styled-components/native";
const CategoryCard = styled(Card)`
background-color: ${(props) => props.theme.colors.bg.primary};
`;
const CategoryGroupCardCover = styled(Card.Cover)`
padding: ${(props) => props.theme.space[3]};
background-color: ${(props) => props.theme.colors.bg.primary};
`;
export const CategoryInfoCard = ({ categoryList = {} }) => {
const {
name = "Zoogdieren",
images = "https://cdn.pixabay.com/photo/2017/02/20/18/03/cat-2083492_960_720.jpg",
} = categoryList;
return (
<CategoryCard
elevation={5}
style={{
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
}}>
<Text center variant="h1" style={{ left: 100 }}>
{name}
</Text>
<CategoryGroupCardCover source={{ uri: images }} />
</CategoryCard>
);
};
So it is about the categoryTransform in the service.
Because when I am calling this function it doesn't work. I get empty array back.
But if I comment the categoryTransform method in the categoryContext. Like:
const retrieveCategories = () => {
setLoading(true);
setTimeout(() => {
fetchCategoryData()
//.then(categoryTransform)
.then((results) => {
setLoading(false);
setCategoryList(results);
})
.catch((err) => {
setLoading(false);
setError(err);
});
}, 200);
};
Then it works.
And the api call looks like:
ITEMs Object {
"animals": Array [],
"category": null,
"description": "zoogdieren",
"eaza": "",
"id": 1,
"images": "http://10.14.220.60:8000/media/photos/categories/mammal.jpg",
"legislation": "",
"name": "zoogdieren",
"review": "",
}
Question: how to fix the categoryTransform method, so that it will filter the specif data: name and images?

How to pass State in Context React and use this an another components

I am trying this code with useContext. i want to take this response.data.message in Editdata.js component. how did i do? how i will send this state message using context.
auth-context.js
const AuthContext = React.createContext({
updatePlayer: () => {},
});
export const AuthContextProvider = (props) => {
const [msg, setmsg] = useState();
const playerupdate = async (updatedPlayer) => {
const { id, player_fname, player_lname, player_nickname } = updatedPlayer;
await axios
.post(
"https://scorepad.ominfowave.com/api/adminPlayerUpdate",
JSON.stringify({ id, player_fname, player_lname, player_nickname }),
{
headers: { "Content-Type": "application/json" },
}
)
.then((response) => {
fetchPlayerList()
//navigate('/view-players');
setmsg(response.data.message)
})
.catch((error) => {
alert(error);
});
};
return (
<AuthContext.Provider
value={{
updatePlayer: playerupdate
}}
>
{props.children}
</AuthContext.Provider>
);
};
type here
Editdata.js
function Editdata() {
const authcon = useContext(AuthContext);
const submitupdateForm = (e) => {
e.preventDefault();
const playerdetail = {
player_fname: firstname,
player_lname: lastname,
player_nickname: nickname,
id: empid
}
authcon.updatePlayer(playerdetail)
}
return (
<form onSubmit={submitupdateForm}>
</form>
);
}
How is the correct way to pass state between components with useContext?

React page doesn't display properly after updating or creating

I have simple note app based on Django+Rest+React. It contains list of notes page (NotesListPage.js) and note pages (NotePage.js). List of notes page contains short previews, titles and links to note pages. The NotePage, in addition to the entire content, contains delete and update functionality. It works, but sometimes (~50%) to see updates on NotesListPage it needs hard refresh or step back to NotePage and come back to list of notes again.
When I look at the sequence of execution of functions in the console, everything goes in the correct order. First, updating the note, then reloading the data.
How can this be fixed?
NotesListPage.js
import ListItem from '../components/ListItem'
import AddButton from '../components/AddButton'
const NotesListPage = () => {
let [notes, setNotes] = useState([])
let getNotes = async () => {
let response = await fetch('/api/notes/')
let data = await response.json()
console.log(data)
setNotes(data)
}
useEffect(() => {
getNotes().then(() => {console.log('NotesList useEffect getNote')})
}, [])
return (
<div className="notes">
<div className="notes-list">
{notes.map((note, index) => (
<ListItem key={index} note={note} />
))}
</div>
<AddButton />
</div>
)
}
export default NotesListPage
NotePage.js
import { ReactComponent as ArrowLeft } from '../assets/arrow-left.svg'
const NotePage = ({ match, history }) => {
let noteId = match.params.id
let [note, setNote] = useState(null)
let getNote = async () => {
if (noteId === 'new') return
let response = await fetch(`/api/notes/${noteId}/`)
let data = await response.json()
setNote(data)
}
useEffect(() => {
getNote().then(() => {console.log('NotePage useEffect getNote')})
}, [noteId])
let createNote = async () => {
await fetch(`/api/notes/`, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(note)
})
}
let updateNote = async () => {
await fetch(`/api/notes/${noteId}/`, {
method: "PUT",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(note)
})
}
let deleteNote = async () => {
await fetch(`/api/notes/${noteId}/`, {
method: 'DELETE',
'headers': {
'Content-Type': 'application/json'
}
})
history.push('/')
}
let handleSubmit = () => {
console.log('NOTE:', note)
if (noteId !== 'new' && note.body === '') {
deleteNote().then(() => {console.log('deleteNote')})
} else if (noteId !== 'new') {
updateNote().then(() => {console.log('updateNote')})
} else if (noteId === 'new' && note.body !== null) {
createNote().then(() => {console.log('createNote')})
}
history.push('/')
}
let handleChange = (value) => {
setNote(note => ({ ...note, 'body': value }))
console.log('Handle Change:', note)
}
return (
<div className="note" >
<div className="note-header">
<h3>
<ArrowLeft onClick={handleSubmit} />
</h3>
{noteId !== 'new' ? (
<button onClick={deleteNote}>Delete</button>
) : (
<button onClick={handleSubmit}>Done</button>
)}
</div>
<textarea onChange={(e) => { handleChange(e.target.value) }} value={note?.body}></textarea>
</div>
)
}
export default NotePage
ListItem.js
import React from 'react'
import { Link } from 'react-router-dom'
let getTime = (note) => {
return new Date(note.updated).toLocaleDateString()
}
let getTitle = (note) => {
let title = note.body.split('\n')[0]
if (title.length > 45) {
return title.slice(0, 45)
}
return title
}
let getContent = (note) => {
let title = getTitle(note)
let content = note.body.replaceAll('\n', ' ')
content = content.replaceAll(title, '')
if (content.length > 45) {
return content.slice(0, 45) + '...'
} else {
return content
}
}
const ListItem = ({ note }) => {
return (
<Link to={`/note/${note.id}`}>
<div className="notes-list-item" >
<h3>{getTitle(note)}</h3>
<p><span>{getTime(note)}</span>{getContent(note)}</p>
</div>
</Link>
)
}
export default ListItem
App.js
import {
BrowserRouter as Router,
Route
} from "react-router-dom";
import './App.css';
import Header from './components/Header'
import NotesListPage from './pages/NotesListPage'
import NotePage from './pages/NotePage'
function App() {
return (
<Router>
<div className="container dark">
<div className="app">
<Header title="Note List" />
<Route path="/" exact component={NotesListPage} />
<Route path="/note/:id" component={NotePage} />
</div>
</div>
</Router>
);
}
export default App;
My first thought was that the router is trying to be smart and it does not refetch the notes on the navigation back.
That could be solved by moving the let [notes, setNotes] = useState([]) to the app, and you could then avoid fetching the individual notes as well (on the premise they don't fetch some addition info)
I added async and await to handleSubmit in NotePage.js and and it looks like it fixed the problem.
let handleSubmit = async () => {
console.log('NOTE:', note)
if (noteId !== 'new' && note.body === '') {
deleteNote().then(() => {console.log('deleteNote')})
} else if (noteId !== 'new') {
await updateNote().then(() => {console.log('updateNote')})
} else if (noteId === 'new' && note.body !== null) {
await createNote().then(() => {console.log('createNote')})
}
history.push('/')
}

Passing arguments to Custom Hook onClick

I have a simple react app in which I am fetching the data using jsonplaceholder API and displaying the data with a delete button. I have a function deleteUser inside the User.js component. Now I want to make this deleteUser function a general function so I created a separate hook for it.
Now I want to pass arguments to that custom hook but I get the error
React Hooks must be called in a React function component or a custom
React Hook function react-hooks/rules-of-hooks
userDelete.js
import { useFetch } from "./useFetch";
export const useDelete = (userName) => {
const { data, setData} = useFetch();
const newData = data.filter((dataItem) => dataItem.name !== userName);
console.log(newData);
setData(newData)
};
useFetch.js
import { useState, useEffect } from "react";
export const useFetch = () => {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const url = `https://jsonplaceholder.typicode.com/users`;
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const data = await response.json();
console.log(data);
setData(data);
setLoading(false);
return;
} catch (error) {
console.log("Error", error);
}
};
useEffect(() => {
fetchData();
}, []);
return { loading, data, setData };
};
User.js
import { useFetch } from "../Hooks/useFetch";
import { useDelete } from "../Hooks/useDelete";
const Users = () => {
const { loading, data, setData } = useFetch();
const deleteCallback = useDelete(data);
const deleteUser = (userName) => {
const newData = data.filter((dataItem) => dataItem.name !== userName);
console.log("newData", newData);
setData(newData);
};
return (
<>
<h1>Custom Hook Example</h1>
{loading && <h4>Fetching Data...</h4>}
{data.map((data, index) => (
<div
key={index}
style={{
border: "2px solid red",
width: "fit-content",
margin: "0 auto"
}}
>
<p>Name: {data.name}</p>
<article>Email: {data.email}</article>
{/* <button onClick={(e) => deleteUser(data.name, e)}>Delete</button> */}
<button onClick={() => useDelete(data.name)}>Delete</button>
</div>
))}
{data.length === 0 && (
<>
<p>No Items to Show</p>
</>
)}
</>
);
};
export default Users;
What am I doing wrong ?

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

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)
}

Categories

Resources