Closing server sent event in Nextjs - javascript

My component is listening to firestore through server sent events
'use client';
import styles from './Messages.module.scss';
import React, { useState, useContext, useEffect } from 'react';
import Message from '../Message/Message';
import { ChatContext } from '#/context/ChatContext';
import { useMessages } from '#/hooks/useMessages';
import { MessageData } from '#/typedef';
export default function Messages() {
const [messages, setMessages] = useState<Array<MessageData>>([]);
const { data: chatContextData } = useContext(ChatContext);
const slug = chatContextData.chatId ? chatContextData.chatId : '';
const encodedSlug = encodeURIComponent(slug);
const { isLoading } = useMessages(chatContextData.chatId || '', Boolean(chatContextData.chatId));
useEffect(() => {
if (!encodedSlug) {
return;
}
const eventSource = new EventSource(`/api/messages?chatId=${encodedSlug}`);
eventSource.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(message?.chats?.messages);
};
return () => {
eventSource.close();
};
}, [encodedSlug]);
return (
<div className={styles.messages}>
{isLoading && <p>Loading...</p>}
{messages?.map((message: MessageData) => (
<Message message={message} key={message.id} />
))}
</div>
);
}
I am using swr to handle my api
import useSWR from 'swr';
const fetcher = async (url: string) => {
const res = await fetch(url);
return res.json();
};
export const useMessages = (slug: string, shouldFetch: boolean) => {
const encodedSlug = encodeURIComponent(slug);
const { data, isLoading, error, mutate } = useSWR(
shouldFetch ? `/api/messages/?chatId=${encodedSlug}` : null,
fetcher
);
return {
data,
isLoading,
error,
mutate
};
};
Below is my api
import { database } from '#/utils/firebase';
import type { NextApiRequest, NextApiResponse } from 'next';
const handler = async (req: NextApiRequest, res: NextApiResponse<FirebaseFirestore.DocumentData>) => {
res.setHeader('Content-Type', 'text/event-stream');
if (req.method === 'GET') {
const chatId = req.query.chatId;
try {
const chatRef = database.collection('chats').doc(chatId as string);
const chatData = await chatRef.get();
if (!chatData.exists) {
return res.status(404).json({ error: 'Chat not found' });
}
// send the initial data
res.write(`data: ${JSON.stringify({ chats: chatData.data() })}\n\n`);
// listen to updates and send to the connected client
let unsubscribe: () => void;
const sendMessage = (chatData: FirebaseFirestore.DocumentData) => {
res.write(`data: ${JSON.stringify({ chats: chatData })}\n\n`);
};
unsubscribe = chatRef.onSnapshot((doc) => {
if (doc.exists) {
const chatData = doc.data() as FirebaseFirestore.DocumentData;
sendMessage(chatData);
}
});
// remove the listener when the client disconnects
req.on('close', () => {
unsubscribe();
});
res.statusCode = 200;
res.end();
} catch (error) {
console.error(error);
return res.status(500).json({ error: 'Error retrieving Chat' });
}
}
};
export default handler;
However, when I switch tabs or even close my laptop, I can see that a request to my api is being sent. It never stops. I even killed the dev server and the server sent events still seem to be going. It's been 10 minutes and I've sent around 1000 read requests to firestore.
How do I close my event source connection? Why is my firestore still being sent read requests? My application is not public so I have no idea where all these read requests are coming from.

Related

Database updates only on the first click of the button with this functions, what is wrong and how could i fix it?

I am new to react and MongoDB, I am trying to add months to a date in my database in mongo, but it only updates the first time I click on the <Price> button, I need it to update every time I click it. The user has to log out and log back in for it to work again, but still only 1 update can be made to the database. Can someone explain to me why this is happening, and how could it be fixed?
This is the function
import React, { useContext } from "react";
import { useState } from "react";
import useFetch from "../../hooks/useFetch";
import Footer from "../../components/Footer";
import Navbar from "../../components/Navbar";
import Sidebar from "../../components/Sidebar";
import {
ContractContainer,
HeadingContainer,
TypeH1,
ActiveUntil,
MonthlyWrapper,
MonthlyContainer,
MonthNumber,
Price,
Navbarback,
} from "./userinfoElements";
import { AuthContext } from "../../context/AuthContext";
import moment from "moment";
import axios from "axios";
const Userinfo = () => {
// for nav bars
const [isOpen, setIsOpen] = useState(false);
// set state to true if false
const toggle = () => {
setIsOpen(!isOpen);
};
const { user } = useContext(AuthContext);
let { data, loading, reFetch } = useFetch(`/contracts/${user.contractType}`);
let dateFormat = moment(user.activeUntil).format("DD/MMMM/yyyy");
const updateDate = async () => {
try {
let newDate = moment(user.activeUntil).add(1, "months");
dateFormat = newDate.format("DD/MMMM/yyyy");
axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
} catch (err) {
console.log(err);
}
reFetch();
};
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle} />
{/* navbar for smaller screens*/}
<Navbar toggle={toggle} />
<Navbarback /> {/* filling for transparent bacground navbar*/}
{loading ? (
"Loading components, please wait"
) : (
<>
<ContractContainer>
<HeadingContainer>
<TypeH1>{data.contractType}</TypeH1>
<ActiveUntil>Subscription active until {dateFormat}</ActiveUntil>
</HeadingContainer>
<MonthlyWrapper>
<MonthlyContainer>
<MonthNumber>1 Month</MonthNumber>
<Price onClick={updateDate}>{data.month1Price}$</Price>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>3 Month</MonthNumber>
<Price onClick={updateDate}>{data.month3Price}$</Price>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>6Month</MonthNumber>
<Price onClick={updateDate}>{data.month6Price}$</Price>
</MonthlyContainer>
<MonthlyContainer>
<MonthNumber>12Month</MonthNumber>
<Price onClick={updateDate}>{data.month12Price}$</Price>
</MonthlyContainer>
</MonthlyWrapper>
</ContractContainer>
</>
)}
<Footer />
</>
);
};
export default Userinfo;
this is the fetch hook
import { useEffect, useState } from "react";
import axios from "axios";
const useFetch = (url) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const res = await axios.get(url);
setData(res.data);
} catch (err) {
setError(err);
}
setLoading(false);
};
fetchData();
}, [url]);
const reFetch = async () => {
setLoading(true);
try {
const res = await axios.get(url);
setData(res.data);
} catch (err) {
setError(err);
}
setLoading(false);
};
return { data, loading, error, reFetch };
};
export default useFetch;
Any help is appreciated!
EDIT: added AuthContext file and server sided controllers if needed
import React from "react";
import { createContext, useEffect, useReducer } from "react";
const INITIAL_STATE = {
user: JSON.parse(localStorage.getItem("user")) || null,
loading: false,
error: null,
};
export const AuthContext = createContext(INITIAL_STATE);
const AuthReducer = (state, action) => {
switch (action.type) {
case "LOGIN_START":
return {
user: null,
loading: true,
error: null,
};
case "LOGIN_SUCCESS":
return {
user: action.payload,
loading: false,
error: null,
};
case "LOGIN_FAILURE":
return {
user: null,
loading: false,
error: action.payload,
};
case "LOGOUT":
return {
user: null,
loading: false,
error: null,
};
default:
return state;
}
};
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(AuthReducer, INITIAL_STATE);
useEffect(() => {
localStorage.setItem("user", JSON.stringify(state.user));
}, [state.user]);
return (
<AuthContext.Provider
value={{
user: state.user,
loading: state.loading,
error: state.error,
dispatch,
}}
>
{children}
</AuthContext.Provider>
);
};
api Controller to update active date
import User from "../models/User.js";
export const updateActiveDate = async (req, res, next) => {
try {
await User.updateOne({ $set: { activeUntil: req.body.activeUntil } });
res.status(200).json("Active date has been updated.");
} catch (err) {
next(err);
}
};
api Controller to find contracts
import Contracts from "../models/Contracts.js";
export const getContract = async (req, res, next) => {
try {
const Contract = await Contracts.findOne({
contractType: req.params.contractType,
});
res.status(200).json(Contract);
} catch (err) {
next(err);
}
};
api Controller for login authentication
export const login = async (req, res, next) => {
try {
const user = await User.findOne({ namekey: req.body.namekey });
if (!user) return next(createError(404, "User not found!"));
if (req.body.password === undefined) {
return next(createError(500, "Wrong password or namekey!"));
}
const isPasswordCorrect = await bcrypt.compare(
req.body.password,
user.password
);
if (!isPasswordCorrect)
return next(createError(400, "Wrong password or namekey!"));
const token = jwt.sign({ id: user._id }, process.env.JWT);
const { password, ...otherDetails } = user._doc;
res
.cookie("access_token", token, {
httpOnly: true,
})
.status(200)
.json({ details: { ...otherDetails } });
} catch (err) {
next(err);
}
};
You should update the stored user state to reflect the activeUntil date change.
Define a 'UPDATE_USER_DATE' action in your reducer to update the user instance:
case "UPDATE_USER_DATE":
const updatedUser = { ...state.user };
updatedUser.activeUntil = action.payload;
return {
...state,
user: updatedUser
};
Then, after updating the date in updateDate, update the user state as well:
const { user, dispatch } = useContext(AuthContext);
const updateDate = async () => {
try {
let newDate = moment(user.activeUntil).add(1, "months");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
dispatch({ type: "UPDATE_USER_DATE", payload: newDate });
} catch (err) {
console.log(err);
}
reFetch();
};
Give this a try. It awaits the put request, and only once that has responded it calls reFetch. Without the await you're calling the reFetch before the put request has had a chance to complete its work.
const updateDate = async () => {
try {
let newDate = moment(user.activeUntil).add(1, "months");
dateFormat = newDate.format("DD/MMMM/yyyy");
await axios.put(`/activedate/${user.namekey}`, {
activeUntil: newDate,
});
} catch (err) {
console.log(err);
} finally {
reFetch();
}
};

How to Sign and call anchor (solana) smart contract from web app

I want to call a very simple anchor smart contract from web app after signing with a wallet, I have the code for the smart contract and the web client javascript to get the sign. I am using https://github.com/project-serum/sol-wallet-adapter for interacting with the user's wallet for the signing but am flexible.
I suspect that I need to copy Anchor's IDL/json file to my web app and then call some JS functions.
I am not sure on how to call the smart contract's INCREMENT method? Can someone help me with the steps involved and the JS code?
Smart contract code
const assert = require("assert");
const anchor = require("#project-serum/anchor");
const { SystemProgram } = anchor.web3;
describe("basic-2", () => {
const provider = anchor.AnchorProvider.local();
// Configure the client to use the local cluster.
anchor.setProvider(provider);
// Counter for the tests.
const counter = anchor.web3.Keypair.generate();
// Program for the tests.
const program = anchor.workspace.Basic2;
it("Creates a counter", async () => {
await program.rpc.create(provider.wallet.publicKey, {
accounts: {
counter: counter.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [counter],
});
let counterAccount = await program.account.counter.fetch(counter.publicKey);
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey));
assert.ok(counterAccount.count.toNumber() === 0);
});
it("Updates a counter", async () => {
await program.rpc.increment({
accounts: {
counter: counter.publicKey,
authority: provider.wallet.publicKey,
},
});
const counterAccount = await program.account.counter.fetch(
counter.publicKey
);
assert.ok(counterAccount.authority.equals(provider.wallet.publicKey));
assert.ok(counterAccount.count.toNumber() == 1);
});
});
Client side code
import React, { useEffect, useMemo, useState } from 'react';
import './App.css';
import Wallet from '../../';
import {
Connection,
SystemProgram,
Transaction,
clusterApiUrl,
} from '#solana/web3.js';
function toHex(buffer: Buffer) {
return Array.prototype.map
.call(buffer, (x: number) => ('00' + x.toString(16)).slice(-2))
.join('');
}
function App(): React.ReactElement {
const [logs, setLogs] = useState<string[]>([]);
function addLog(log: string) {
setLogs((logs) => [...logs, log]);
}
const network = clusterApiUrl('devnet');
const [providerUrl, setProviderUrl] = useState('https://www.sollet.io');
const connection = useMemo(() => new Connection(network), [network]);
const urlWallet = useMemo(
() => new Wallet(providerUrl, network),
[providerUrl, network],
);
const injectedWallet = useMemo(() => {
try {
return new Wallet(
(window as unknown as { solana: unknown }).solana,
network,
);
} catch (e) {
console.log(`Could not create injected wallet`, e);
return null;
}
}, [network]);
const [selectedWallet, setSelectedWallet] = useState<
Wallet | undefined | null
>(undefined);
const [, setConnected] = useState(false);
useEffect(() => {
if (selectedWallet) {
selectedWallet.on('connect', () => {
setConnected(true);
addLog(
`Connected to wallet ${selectedWallet.publicKey?.toBase58() ?? '--'}`,
);
});
selectedWallet.on('disconnect', () => {
setConnected(false);
addLog('Disconnected from wallet');
});
void selectedWallet.connect();
return () => {
void selectedWallet.disconnect();
};
}
}, [selectedWallet]);
// =========== Need to modify this I think ==================
async function sendTransaction() {
try {
const pubkey = selectedWallet?.publicKey;
if (!pubkey || !selectedWallet) {
throw new Error('wallet not connected');
}
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: pubkey,
toPubkey: pubkey,
lamports: 100,
}),
);
addLog('Getting recent blockhash');
transaction.recentBlockhash = (
await connection.getRecentBlockhash()
).blockhash;
addLog('Sending signature request to wallet');
transaction.feePayer = pubkey;
const signed = await selectedWallet.signTransaction(transaction);
addLog('Got signature, submitting transaction');
const signature = await connection.sendRawTransaction(signed.serialize());
addLog('Submitted transaction ' + signature + ', awaiting confirmation');
await connection.confirmTransaction(signature, 'singleGossip');
addLog('Transaction ' + signature + ' confirmed');
} catch (e) {
console.warn(e);
addLog(`Error: ${(e as Error).message}`);
}
}
async function signMessage() {
try {
if (!selectedWallet) {
throw new Error('wallet not connected');
}
const message =
'Please sign this message for proof of address ownership.';
addLog('Sending message signature request to wallet');
const data = new TextEncoder().encode(message);
const signed = await selectedWallet.sign(data, 'hex');
addLog('Got signature: ' + toHex(signed.signature));
} catch (e) {
console.warn(e);
addLog(`Error: ${(e as Error).message}`);
}
}
return (
<div className="App">
<h1>Wallet Adapter Demo</h1>
<div>Network: {network}</div>
<div>
Waller provider:{' '}
<input
type="text"
value={providerUrl}
onChange={(e) => setProviderUrl(e.target.value.trim())}
/>
</div>
{selectedWallet && selectedWallet.connected ? (
<div>
<div>Wallet address: {selectedWallet.publicKey?.toBase58()}.</div>
<button onClick={sendTransaction}>Send Transaction</button>
<button onClick={signMessage}>Sign Message</button>
<button onClick={() => selectedWallet.disconnect()}>
Disconnect
</button>
</div>
) : (
<div>
<button onClick={() => setSelectedWallet(urlWallet)}>
Connect to Wallet
</button>
<button onClick={() => setSelectedWallet(injectedWallet)}>
Connect to Injected Wallet
</button>
</div>
)}
<hr />
<div className="logs">
{logs.map((log, i) => (
<div key={i}>{log}</div>
))}
</div>
</div>
);
}
export default App;
If you want to do this with #project-serum/anchor, you need install the dependency and import them:
import * as anchor from '#project-serum/anchor';
import { Program } from '#project-serum/anchor';
import { Basic2 } from '<path to your basic2.json idl>';
And then do something similar to this snippet in your code:
anchor.setProvider(new AnchorProvider(connection, wallet, AnchorProvider.defaultOptions()));
const program = anchor.workspace.Basic2 as Program<Basic2>;
await program.methods.increment()
.accounts({
counter: counter.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.rpc();
The way it is done in your "Smart contract code" above is the old-school way and has been superseded with this one.

Writing unit test for server error scenario in enzyme

I was working on a functionality which involved fetching the data from an API and rendering the components accordingly.
index.js
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import StarRating from './components/StarRating';
import {
API_URL,
API_TIMEOUT,
} from '../../../../someLoc';
export async function fetchWithTimeout(resource, options = {}) {
const { timeout } = options;
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const response = await fetch(resource, {
...options,
signal: controller.signal,
});
clearTimeout(id);
return response;
}
const UserRating = ({ productId }) => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState({});
const [error, setError] = useState(false);
useEffect(
() => {
const fetchReviewsNRatings = async (bvUrl, bvTimeout) => {
const url = `${bvUrl}:${productId}`;
const response = await fetchWithTimeout(url, { timeout: bvTimeout });
if (response.ok) {
const ratingData = await response.json();
setData(ratingData);
setLoading(false);
} else {
setError(true);
setLoading(false);
}
};
fetchReviewsNRatings(API_TIMEOUT);
},
[productId],
);
// I didn't know how to test this branch if I simply returned null. so wrapped null inside of div.
// MY INTENT HERE: if(loading) return null;
if (loading) return <div className="test-class">{null}</div>;
// destructuring the response to get the data once loading is complete.
const {
Results: [
{
ABC: {
DEF: { GHI, JKL },
},
},
],
} = data;
const numReviews = +GHI;
const value = JKL.toFixed(1);
return !error && <StarRating value={value} numReviews={numReviews} />;
};
UserRating.propTypes = {
productId: PropTypes.string,
};
export default UserRating;
index.test.js
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import UserRating from '..';
import { PRODUCT_ID } from '../../../../../someLoc';
describe('Testing for <UserRating /> component', () => {
const data = {
Results: [
{
ABC: {
DEF: {
GHI: 4.567,
JKL: 1103,
},
},
},
],
};
const productId = PRODUCT_ID;
let component = null;
beforeEach(() => {
global.fetchWithTimeout = jest.fn(() =>
Promise.resolve({
ok: true,
status: 200,
data,
json: () => data,
}),
);
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
status: 200,
data,
json: () => data,
}),
);
component = act(() => mount(<UserRating productId={productId} />));
});
it('should call fetch', async () => {
expect(component.exists()).toBeTruthy();
// Testting for loading state.
expect(component.find('.test-class').exists()).toBe(true);
});
What's working as expected:
The functionality that is expected.
The above test which I managed to write is passing for win-win scenario i.e. when the data is loaded and fetched successfully.
MY QUESTION:
a. I want to write a unit test in such a way so as to test what happens if there is some error from server side(5xx) or from client side(4xx).
b. What else can I improve in my passing test scenario?
What I have tried:
describe('Testing for <UserRating /> component for failed response', () => {
const data = { message: '500: Internal Server Error' };
const error = true;
const productId = PRODUCT_ID;
let component = null;
beforeEach(() => {
global.fetchWithTimeout = jest.fn(() =>
Promise.reject(
new Error({
ok: false,
status: 500,
data,
json: () => data,
}),
),
);
global.fetch = jest.fn(() =>
Promise.reject(
new Error({
ok: false,
status: 500,
data,
json: () => data,
}),
),
);
component = act(() => mount(<UserRating productId={productId} />));
});
it('should not fetch', async () => {
expect(component.exists()).toBeFalsy();
expect(component.find('.test-class').exists()).toBe(true);
console.debug(component);
});
});
When I tried the above code it is breaking the win-win scenario as well with the error message cannot read property exists for null. I am relatively new to enzyme and facing issues while using enzyme for writing tests for hooks. I know that React testing library is preferrable but my organization is yet to shift onto that. I've to write fail case as well since the coverage for branches is poor. I'll be highly obliged for any help. Thanks
REFFERED LINKS and Questions:
how-to-test-async-data-fetching-react-component-using-jest-and-enzyme
Testing hooks with mount
Should a Promise.reject message be wrapped in Error?

React - online not rendering

I am in my final steps of placing my react web app on the internet, everything works fine on my localhost, but as soon as I place it on the internet, I get the error below.
Uncaught TypeError: Object(...) is not a function
y AuthProvider.js:64
React 12
80 index.js:8
u (index):1
t (index):1
r (index):1
<anonymous> main.9b7fd734.chunk.js:1
AuthProvider.js:64:36
y AuthProvider.js:64
React 12
80 index.js:8
u (index):1
t (index):1
r (index):1
<anonymous> main.9b7fd734.chunk.js:1
I do not know what I am doing wrong, I read everything multiple times.
This is the component where the error is. From what I can tell from the error, the error is in the function setSession.
import { createContext, useState, useMemo, useEffect, useCallback, useContext } from "react";
import config from '../config.json';
import * as usersApi from '../api/users';
import * as api from "../api";
const JWT_TOKEN_KEY = config.token_key;
const AuthContext = createContext();
function parseJwt(token) {
if (!token) return {};
const base64url = token.split('.')[1];
const payload = Buffer.from(base64url, 'base64');
const jsonPayload = payload.toString('ascii');
return JSON.parse(jsonPayload);
}
function parseExp(exp) {
if (!exp) return null;
if (typeof exp !== 'number') exp = Number(exp);
if(isNaN(exp)) return null;
return new Date(exp * 1000);
}
const useAuth = () => useContext(AuthContext);
export const useSession = () => {
const { loading, error, token, user, ready, hasRole } = useAuth();
return { loading,
error,
token,
user,
ready,
isAuthed: Boolean(token),
hasRole,
};
}
export const useLogin = () => {
const { login } = useAuth();
return login;
}
export const useLogout = () => {
const { logout } = useAuth();
return logout;
}
export const useRegister = () => {
const { register } = useAuth();
return register;
}
export const AuthProvider = ({
children
}) => {
const [ready, setReady] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [token, setToken] = useState(localStorage.getItem(JWT_TOKEN_KEY));
const [user, setUser] = useState(null);
const setSession = useCallback(async (token, user) => {
const { exp, userId } = parseJwt(token);
const expiry = parseExp(exp);
const stillValid = expiry >= new Date();
if (stillValid) {
localStorage.setItem(JWT_TOKEN_KEY, token);
} else {
localStorage.removeItem(JWT_TOKEN_KEY);
token = null;
}
api.setAuthToken(token);
setToken(token);
setReady(token && stillValid);
if (!user && stillValid) {
user = await usersApi.getById(userId);
}
setUser(user);
}, []);
useEffect(() => {
setSession(token, null);
}, [setSession, token]);
const login = useCallback( async (email, password) => {
try {
setError('');
setLoading(true);
const {token, user} = await usersApi.login(email, password);
await setSession(token, user);
return true;
} catch (error) {
setError(error);
return false;
} finally {
setLoading(false);
}
}, [setSession]);
const logout = useCallback(() => {
setSession(null, null);
}, [setSession]);
const register = useCallback( async ({name, email, password}) => {
try {
setError('');
setLoading(true);
const {token, user} = await usersApi.register({name, email, password});
await setSession(token, user);
return true;
} catch (error) {
setError(error);
return false;
} finally {
setLoading(false);
}
}, [setSession]);
const hasRole = useCallback((role) => {
if (!user) return false;
return user.roles.includes(role);
}, [user])
const value = useMemo(() => ({
loading,
error,
token,
user,
ready,
login,
logout,
register,
hasRole,
}), [loading, error, token, user, ready, login, logout, register, hasRole]);
return(
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
This is the function usersApi.getById(userId).
export const getById = async (id) => {
const { data } = await axios.get(`/users/${id}`);
return data;
}
Every thing I get from an api, is an api that works fine and is running op Heroku.
Change this
import { useCallback, useContext } from "react/cjs/react.development";
with this
import { useCallback, useContext } from "react";
It works on the localhost because you're importing the React Hook from the local node modules file. Because there is no local node modules file in the deployment, it gives an error for importing.

How to wait until context value is set?

I'm trying to render a header.
First, in InnerList.js, I make an API call, and with the data from the API call, I set a list in context.
Second, in Context.js, I take the list and set it to a specific data.
Then, in InnerListHeader.js, I use the specific data to render within the header.
Problem: I currently get a TypeError undefined because the context is not set before rendering. Is there a way to wait via async or something else for the data to set before loading?
My code block is below. I've been looking through a lot of questions on StackOverflow and blogs but to no avail. Thank you!
InnerList.js
componentDidMount() {
const { dtc_id } = this.props.match.params;
const {
setSpecificDtcCommentList,
} = this.context;
MechApiService.getSpecificDtcCommentList(dtc_id)
.then(res =>
setSpecificDtcCommentList(res)
)
}
renderSpecificDtcCommentListHeader() {
const { specificDtc = [] } = this.context;
return (
<InnerDtcCommentListItemHeader key={specificDtc.id} specificDtc={specificDtc} />
)
}
Context.js
setSpecificDtcCommentList = (specificDtcCommentList) => {
this.setState({ specificDtcCommentList })
this.setSpecificDtc(specificDtcCommentList)
}
setSpecificDtc = (specificDtcCommentList) => {
this.setState({ specificDtc: specificDtcCommentList[0] })
}
InnerListHeader.js
render() {
const { specificDtc } = this.props;
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{specificDtc.dtc_id.dtc}
</div>
</div>
);
}
In general, you should always consider that a variable can reach the rendering stage without a proper value (e.g. unset). It is up to you prevent a crash on that.
For instance, you could rewrite you snippet as follows:
render() {
const { specificDtc } = this.props;
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{Boolean(specificDtc.dtc_id) && specificDtc.dtc_id.dtc}
</div>
</div>
);
}
When you make an api call you can set a loader while the data is being fetched from the api and once it is there you show the component that will render that data.
In your example you can add a new state that will pass the api call status to the children like that
render() {
const { specificDtc, fetchingData } = this.props;
if (fetchingData){
return <p>Loading</p>
}else{
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{specificDtc.dtc_id.dtc}
</div>
</div>
);
}
}
``
in my case, i am calling external api to firebase which lead to that context pass undefined for some values like user. so i have used loading set to wait untile the api request is finished and then return the provider
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
GoogleAuthProvider,
signInWithPopup,
updateProfile
} from 'firebase/auth';
import { auth } from '../firebase';
import { useNavigate } from 'react-router';
import { create_user_db, get_user_db } from 'api/UserAPI';
import { CircularProgress, LinearProgress } from '#mui/material';
import Loader from 'ui-component/Loader';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState();
const [user_db, setUserDB] = useState();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
const createUser = async (email, password) => {
const user = await createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false));
};
const googleSignIn = async () => {
const provider = new GoogleAuthProvider();
await signInWithPopup(auth, provider)
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false));
};
const logout = () => {
setUser();
return signOut(auth).then(() => {
window.location = '/login';
});
};
const updateUserProfile = async (obj) => {
await updateProfile(auth.currentUser, obj);
return updateUser(obj);
};
const updateUser = async (user) => {
return setUser((prevState) => {
return {
...prevState,
...user
};
});
};
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
setLoading(true);
if (currentUser) {
const user_db = await get_user_db({ access_token: currentUser.accessToken });
setUserDB(user_db);
setUser(currentUser);
setIsAuthenticated(true);
}
setLoading(false);
});
return () => {
unsubscribe();
};
}, []);
if (loading) return <Loader />;
return (
<UserContext.Provider value={{ createUser, user, user_db, isAuthenticated, logout, signIn, googleSignIn, updateUserProfile }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};

Categories