Data leak due to using asynchronous function - node.js

I am getting a data leak while using an asychronous function causing my application to not load the second page during navigation.
I am using async/await on my get request, and I have tried to use a cleanup function to prevent this leak, but it is not working.
How do I fix this leak, and still get the data to load when the page is loaded?
import React, { useEffect, useState, useContext } from "react";
import ReactTable from "react-table";
import "react-table/react-table.css";
import axios from "axios";
import StatusContext from "../../context/status/statusContext";
const Table = props => {
const [tableData, setTableData] = useState([]);
const statusContext = useContext(StatusContext);
useEffect(async () => {
await axios
.get("/api/status")
.then(function(response) {
console.log(response.data);
setTableData(
response.data.filter(item => {
let itemDate = new Date(item.date);
let variableDate = new Date() - 604800000;
return itemDate > variableDate;
})
);
})
.catch(function(error) {
console.log(error);
});
}, [statusContext]);
const columns = [
{
id: "Name",
Header: "Name",
accessor: "name"
},
{
Header: "Date",
accessor: "date"
},
{
Header: "Comment",
accessor: "comment"
}
];
return (
<ReactTable
data={tableData}
columns={columns}
pivotBy={["date"]}
defaultPageSize={7}
minRows={5}
/>
);
};
export default Table;

There's really no need to bring async/await into this situation, and in fact useEffect won't work if you do. The only thing you can return from useEffect is a cleanup function, and an async function returns a Promise.
This should work just fine, including a cleanup function in case you unmount your component before the promise resolves:
useEffect(() => {
let isMounted = true;
axios
.get("/api/status")
.then(function(response) {
if (!isMounted) {
return;
}
console.log(response.data);
setTableData(
response.data.filter(item => {
let itemDate = new Date(item.date);
let variableDate = new Date() - 604800000;
return itemDate > variableDate;
})
);
})
.catch(function(error) {
console.log(error);
});
return () => {
isMounted = false;
}
}, [statusContext]);

Related

`useEffect` not being able to fetch data on component mount

I am trying to set an array of objects into dineIns after fetching them inside useEffect. What I understood is that there is some kind of delay in receiving the data because the state variable returns an empty array when I log it after fetching the data.
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router';
import jwtDecode from 'jwt-decode';
function CheckIns() {
const [dineIns, setDineIns] = useState([]);
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
const user = jwtDecode(token);
if (!user) {
localStorage.removeItem('token');
navigate('/login');
} else {
async function UserData(user_email) {
const user_data = await axios
.get(`/api/users/${user_email}`)
.then((res) => {
const info = res.data.reservations;
setDineIns(info);
console.log(dineIns);
});
}
UserData(user.email);
}
} else {
navigate('/login');
}
}, []);
}
What needs to be corrected here to set the state in time?
set state is an async operation, which log the data after set it, will log the old value.
To ensure that the data set correctly, you can use setState again
const info = res.data.reservations
setDineIns(info)
setDineIns(prev => {
console.log(prev)
return prev;
})
Or you can use effect with dineIns dependence.
I think your code works fine.
You are expecting a Promise from the axios call but you are awaiting it.
Try to change your code like this:
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
const user = jwtDecode(token);
if (!user) {
localStorage.removeItem('token');
navigate('/login');
} else {
async function UserData(user_email) {
try {
const { data } = await axios.get(`/api/users/${user_email}`);
setDineIns(data.reservations);
console.log(dineIns);
} catch (err) {
console.log(err);
}
}
UserData(user.email);
}
} else {
navigate('/login');
}
}, []);

Error: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render

import { setCookies, removeCookies } from "cookies-next";
import { useRouter } from "next/router";
import { useEffect } from "react";
const { URL } = process.env;
export const getServerSideProps = async (context) => {
const userAuthToken = context.req.cookies["authToken"];
const data = {
authToken: userAuthToken,
};
const requestJSON = JSON.stringify(data);
const response = await fetch(URL + "api/userFetch", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: requestJSON,
});
const responseData = await response.json();
return {
props: { datas: responseData },
};
};
const Home = ({ datas }) => {
const router = useRouter();
if (datas[0].error == true) {
useEffect(() => {
setTimeout(() => {
router.push("/");
}, 3000);
}, []);
removeCookies("authToken");
return <h1>Something Went Wrong</h1>;
} else {
return <h1>Welcome To Home{datas[0].error}</h1>;
}
};
export default Home;
This code is running fine on development server but when I try to build this code in production I get this error **
./pages/Home.js
28:5 Error: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?**
I tried everything I could but can't fix the error
Just do exactly what the error is telling you. Move the useEffect call out of the conditional block. You can still conditionally perform the operation within the hook. For example:
const Home = ({ datas }) => {
const router = useRouter();
useEffect(() => {
if (datas[0].error == true) {
setTimeout(() => {
router.push("/");
}, 3000);
}
}, []);
if (datas[0].error == true) {
removeCookies("authToken");
return <h1>Something Went Wrong</h1>;
} else {
return <h1>Welcome To Home{datas[0].error}</h1>;
}
};
Specifically, as the error states, the same hooks must always be called on every render. (I don't know enough under the hood of React to describe why that's the case, it just seems necessary for stability/consistency/etc.) But the operation being performed by the hook in this case can still be effectively a no-op if the intended condition is not met.

Cannot get the array in json when I put the data in the state

I'm having trouble to get the data of array in JSON.
I send the json file by using node.js and the code is down below.
const express = require("express");
const router = express.Router(); // 라우터 분리
router.get("/api/hello", (req, res) => {
// app 대신 router에 연결
res.json({ express: "hello~" });
});
router.get("/api/beef", (req, res) => {
res.json();
});
module.exports = router; // 모듈로 만드는 부분
And the data which I send with json is like this.
{
"test": "Beef data get Completed",
"name": "beef",
"data": [
{ "productName": "양지", "price": 20000 },
{ "productName": "갈비", "price": 30000 },
{ "productName": "등심", "price": 15000 }
]
}
]
And then I get the data by using fetch, and save it in the state.
I get the right data which I want in the fetch function but when I try to get the data out of the fetch function I keep getting the error like cannot get the property blahblahblah...
And this is the code I did in the client.
import React, { Component } from "react";
const itemList = ["돼지고기", "소고기", "닭&오리", "Sale", "연락처"];
class Contents extends Component {
state = {
parsedJson : "",
};
componentDidMount() {
this._callAPI()
.then(parsedJson=>{
console.log("In the fetch : ",parsedJson[0].data[0])
this.setState({
parsedJson
})
})
.catch(error => {
console.log(error);
this.setState({
...this.state,
isError: true
});
});
}
_callAPI = async () => {
const response = await fetch(`api/${this.props.curPage}`);
const data = response.json();
if (response.status !== 200) throw Error(data.message);
return data;
};
render() {
const stateData = this.state;
console.log("In the render : ",stateData.parsedJson[0].data[0]) <- !!!!!!!!!!!! where error occur
return (
<>
<RenderByItemList
curPage={this.props.curPage}
data={stateData.productData}
/>
</>
);
}
}
On the first render of your component, parsedJson is string, and the stateData.parsedJson[0] returns an empty string, and there is no data property on an empty string, so you get the error.
For solving this problem you have to write an if inside your render method:
import React, { Component } from "react";
const itemList = ["돼지고기", "소고기", "닭&오리", "Sale", "연락처"];
class Contents extends Component {
state = {
parsedJson: [],
error: null,
};
componentDidMount() {
this._callAPI()
.then(res => {
this.setState({
parsedJson: res[0].data, // here we set state the data array
})
})
.catch(error => {
console.log(error);
this.setState({
...this.state,
error // here we set state occurred error
});
});
}
_callAPI = async () => {
const response = await fetch(`api/${this.props.curPage}`);
const data = response.json();
if (response.status !== 200) throw Error(data.message);
return data;
};
render() {
const { parsedJson, error } = this.state;
if (parsedJson && parsedJson.length) {
return <RenderByItemList
curPage={this.props.curPage}
data={parsedJson}
/>
} else {
return !!error || 'loading';
}
}
}
And also it's better to handle loading on your fetch action:
import React, { Component } from "react";
const itemList = ["돼지고기", "소고기", "닭&오리", "Sale", "연락처"];
class Contents extends Component {
state = {
parsedJson: [],
error: null,
loading: false
};
componentDidMount() {
this.setState({
loading: true,
}, () => {
this._callAPI()
.then(res => {
this.setState({
parsedJson: res[0].data, // here we set state the data array
loading: false,
error: null,
})
})
.catch(error => {
console.log(error);
this.setState({
loading: false,
error // here we set state occurred error
});
});
})
}
_callAPI = async () => {
const response = await fetch(`api/${this.props.curPage}`);
const data = response.json();
if (response.status !== 200) throw Error(data.message);
return data;
};
render() {
const { parsedJson, error, loading } = this.state;
if (loading) {
return 'loading' // or write a Loading component and render it everywhere
} else if (error) {
return error // or write an Error component
} else {
return (
<RenderByItemList
curPage={this.props.curPage}
data={parsedJson}
/>
);
}
}
}

Unit test for customPollingHook which uses apollo useLazyQuery

So I have written a custom polling hook which uses useContext and useLazyQuery hooks. I want to write a unit test for this, which should cover its returned values state and side effect.
So far I have managed to do this much but I'm not so sure how to proceed ahead. Any tips?
export const useUploadActivityPolling = (
teId: TeIdType
): UploadActivityPollingResult => {
const { dispatch, uploadActivityId }: StoreContextType = useAppContext();
const [fetchActivityStatus, { error: UploadActivityError, data: UploadActivityData, stopPolling }] = useLazyQuery(
GET_UPLOAD_ACTIVITY,
{
pollInterval: 3000,
fetchPolicy: 'network-only',
variables: { teId, activityId: uploadActivityId },
}
);
useEffect(() => {
if (UploadActivityData) {
setUploadActivityId(
UploadActivityData.getUploadActivityStatus.activity_id,
dispatch
);
updateActivityStateAction(UploadActivityData.getExcelUploadActivityStatus.status, dispatch);
}
}, [UploadActivityData]);
return { fetchActivityStatus, stopPolling, UploadActivityError };
};
import React from 'react';
import { mount } from 'enzyme';
const TestCustomHook = ({ callback }) => {
callback();
return null;
};
export const testCustomHook = callback => {
mount(<TestCustomHook callback={callback} />);
};
describe('useUploadActivityPolling', () => {
let pollingResult;
const teId = 'some id';
beforeEach(() => {
testCustomHook(() => {
pollingResult = useUploadActivityPolling(teId);
});
});
test('should have an fetchActivityStatus function', () => {
expect(pollingResult.fetchActivityStatus).toBeInstanceOf(Function);
});
});

Testing custom hook with react-hooks-testing-library throws an error

I am trying to test a simple hook that fetches some data using axios. However the test is throwing a TypeError: "Cannot read property 'fetchCompanies' of undefined". Here's my custom hook (the full repo is here):
import { useState, useEffect } from 'react';
import { Company } from '../../models';
import { CompanyService } from '../../services';
export const useCompanyList = (): {
loading: boolean;
error: any;
companies: Array<Company>;
} => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const [companies, setCompanies] = useState<Array<Company>>([]);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const companies = await CompanyService.fetchCompanies();
// Sort by ticker
companies.sort((a, b) => {
if (a.ticker < b.ticker) return -1;
if (a.ticker > b.ticker) return 1;
return 0;
});
setCompanies(companies);
setLoading(false);
} catch (e) {
setError(e);
}
};
fetchData();
}, []);
return { loading, error, companies };
};
and here's my test:
import { renderHook } from 'react-hooks-testing-library';
import { useCompanyList } from './useCompanyList';
const companiesSorted = [
{
ticker: 'AAPL',
name: 'Apple Inc.'
},
...
];
jest.mock('../../services/CompanyService', () => {
const companiesUnsorted = [
{
ticker: 'MSFT',
name: 'Microsoft Corporation'
},
...
];
return {
fetchCompanies: () => companiesUnsorted
};
});
describe('useCompanyList', () => {
it('returns a sorted list of companies', () => {
const { result } = renderHook(() => useCompanyList());
expect(result.current.loading).toBe(true);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual(companiesSorted);
});
});
Please help me understand how to use react-hooks-testing-library in this case.
Edit
This seems to be related to a Jest issue that was seemingly resolved. Please see https://github.com/facebook/jest/pull/3209.
The
TypeError: "Cannot read property 'fetchCompanies' of undefined"
is caused by the way you define the CompanyService service. In the code, you are exporting an object CompanyService with all the service methods. But in your test, you are mocking the CompanyService to return an object with the methods.
So, the mock should return a CompanyService object that is an object with all the methods:
jest.mock('../../services/CompanyService', () => {
const companiesUnsorted = [
{
ticker: 'MSFT',
name: 'Microsoft Corporation'
},
...
];
return {
CompanyService: {
fetchCompanies: () => companiesUnsorted
}
};
});
Now, once you solve this, you will find that you don't have the TypeError anymore but your test is not passing. That is because the code you are trying to test is asynchronous, but your test is not. So, immediately after you render your hook (through renderHook) result.current.companies will be an empty array.
You will have to wait for your promise to resolve. Fortunately, react-hooks-testing-library provides us a waitForNextUpdate function in order to wait for the next hook update. So, the final code for the test would look:
it('returns a sorted list of companies', async () => {
const { result, waitForNextUpdate } = renderHook(() => useCompanyList());
expect(result.current.loading).toBe(true);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual([]);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeUndefined();
expect(result.current.companies).toEqual(companiesSorted);
});

Resources