Warning about unmounted component - javascript

I have an app that read values from an external devices, then these values are written in a DB.
When I wrote this data in the db I receive this error:
Can't perform a React state update on an unmounted component. This is
a no-op, but it indicates a memory leak in your application. To fix,
cancel all subscriptions and asynchronous tasks in the
componentWillUnmount method.
And It is about this code:
this.setState(state => ({
acc_dx,
array_acc_dx: [...state.array_acc_dx, [timeagm, ...acc_dx].join(":")]
}));
this code is a part of a function setupNotifications1 which in turn is called in a function called in the componentDidMount ().
How can I solve this warning? Thank you.
EDIT 1:
componentDidMount() {
this.deviceService1(this.device1);
}
Inside deviceService1() I call this.deviceService2(this.device2) and inside I call setupNotifications1(this.device1) and setupNotifications2(this.device2)
EDIT 2:
async setupNotifications1(device) {
var timeagm = 0;
var time = 0;
const service = this.serviceGeneral();
this.subscriptionMonitor1 = await device.monitorCharacteristicForService(
service,
this.AccGyrMg,
(error, characteristic) => {
if (error) {
this.error(error.message);
return;
}
const buf = Buffer.from(characteristic.value, "base64");
const [...acc_dx] = [2, 4, 6].map(index => buf.readInt16LE(index));
this.setState(state => ({
acc_dx,
array_acc_dx: [...state.array_acc_dx, [timeagm, ...acc_dx].join(":")]
}));
timeagm += 20;
then I have a stop button that is used to remove this.subscriptionMonitor1 and change page ( I pass the value in the other page where there are written in the DB )

Related

How can i prevent useEffect() from firing up the first time but listening for a state change?

I'm using react, node express, postgres
I have a react component that is an html table that gets populated from a postgres table.
Here is parent component Materials:
const Materials = () => {
const [thickness1, setThickness] = useState(0);
const [width1, setWidth] = useState(0);
const [length1, setLength] = useState(0);
const [partTotalDemand, setTotalDemand] = useState(0);
const [partPlanned, setPlanned] = useState(0);
...
Here is a method in the component that retrieves data
// Material requirements calculation
const getReq = async (id) => {
try {
const response = await fetch(`http://localhost:5000/materials/${id}`, [id])
const jsonData = await response.json();
const tempThickness = jsonData.parts_material_thickness
const tempWidth = jsonData.parts_material_width
const tempLength = jsonData.parts_material_length
const tempTotalDemand = jsonData.workorder_total
const tempPlanned = jsonData.parts_produced
stateSetter(tempThickness, tempWidth, tempLength)
} catch (err) {
console.log(err.message);
}
}
I then want to update the states of the global constants:
const stateSetter = (thickness, width, length) => {
try {
setThickness(thickness);
setWidth(width);
setLength(length);
console.log(thickness1);
console.log(width1);
console.log(length1);
} catch (err) {
console.log(err.message)
}
}
useEffect(() => {
stateSetter();
}, [thickness1]);
Essentially the getReq() method is supposed to retrieve the information, and then I need to update the states with those values. As I understand I then need to re-render the component so the new states are usable. I attempted to do this via useEffect() but I'm not successful. The idea was to stop getReq() from firing up on the first render, but if the state changes for thickness1/width1/length1 then it should fire up and re-render, help much appreciated!
You're over-complicating this. All you need to do is set the state values:
const getReq = async (id) => {
try {
const response = await fetch(`http://localhost:5000/materials/${id}`, [id])
const jsonData = await response.json();
// set state values
setThickness(jsonData.parts_material_thickness);
setWidth(jsonData.parts_material_width);
setLength(jsonData.parts_material_length);
setTotalDemand(jsonData.workorder_total);
setPlanned(jsonData.parts_produced);
} catch (err) {
console.log(err.message);
}
}
You don't need to manually do anything to re-render the component. It will re-render whenever state is updated. So the "setter" functions being invoked here will trigger that re-render. (All of the state updates will be batched. So the above won't trigger 5 re-renders, just one with the 5 updated state values.)
Where you would use useEffect is when you want to have some logic which responds to a change in a particular state. For example, if you want to show a message every time thickness changes to a negative value, you'd do something like:
useEffect(() => {
if (thickness < 1) {
alert('negative thickness!');
}
}, [thickness]);
But that's not what you're doing here. All you're doing here is setting state values.

Firebase firestore : Unhandled Rejection (RangeError): Maximum call stack size exceeded

Started playing around with createAsyncThunk for learning purpose, decided to implement a shopping cart with firebase firestore but I ran into problems when trying to implement pagination in my react app.
How should I return the last visible state into my redux state during the initial load and subsequent load (infinite loading)
I am basing on code from redux tutorial sandbox :https://codesandbox.io/s/github/reduxjs/redux-essentials-example-app/tree/checkpoint-3-postRequests/?from-embed, but instead of connecting to a fake api, I am using firebase firestore.
Code to fetch product from firestore : ProductSlice.js
const InitialState = {
products : [],
status: 'idle',
error: null,
last: null, //to hold lastVisible when fetching data from firestore
}
export const fetchProducts = createAsyncThunk(types.RECEIVE_PRODUCTS, async (limit) => {
const resp = await fire_base_product.firestore()
.collection(collection_name).orderBy('id').limit(limit)
let result = resp.get().then((querySnapshot) => {
const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1] //how set this to redux state
const products = querySnapshot.docs.map((doc)=> {
return { ...doc.data()}
})
return {
products: products,
lastVisible: lastVisible
};
})
return result;
}
I am not quite sure on how to set this lastVisible data back into redux state, is it possible to do that with reference?
#Edit:
Tried to return both product list and last visible as an array and assign lastVisible in createSlice as stated below:
const productSlice = createSlice({
name:'products',
initialState:
reducers: {},
extraReducers:{
[fetchProducts.fulfilled]: (state, action) => {
state.products = state.products.concat(action.payload.products)
state.last = action.payload.lastVisible // this causes call stack error
}
}
});
With the above coding, two error will be reported if I run react app,
Trying to assign non serialize value into redux state
Maximum call stack size exceeded in firestore
I then tried to add middleware serializableCheck during create configuration as below:
export default configureStore({
middlleware: getDefaultMiddlleWare({
serializableCheck: {
//ignore action type
ignoredActions : ['RECEIVE_PRODUCTS/fulfilled']
// ignore path
ignoredPath: ['products.last']
}
}),
... // reducer codes
})
Even though now I have dismissed the first error, call stack exceeded still exists. Does anyone knows why this is happening ? Feel free to discuss if there is any workaround on this. Thanks.
Edit 2
Similar approach works when using context but does not work when using redux. Do I need to wrap return in promise as suggested in Firebase Unhandled error RangeError: Maximum call stack size exceeded ?
Did not managed to find a way to save lastVisible, but I found a workaround by just keeping track of the last retrieve id of my firestore data by saving it into redux state.
export const fetchProducts = createAsyncThunk(types.RECEIVE_PRODUCTS, async (limit) => {
const resp = await fire_base_product.firestore()
.collection(collection_name).orderBy('id').limit(limit)
let result = resp.get().then((querySnapshot) => {
var lastVisible = limit - 1; //only keep track of ID so we can avoid saving un-serialize coded
const products = querySnapshot.docs.map((doc)=> {
return { ...doc.data()}
})
return {
products: products,
lastVisible: lastVisible
};
})
return result;
}
And when during fetch of additional data we can then access the state by using getState() as below:
export const fetchMoreProducts = createAsyncThunk(types.LOAD_MORE_PRODUCTS, async (limit, {getState}) => {
const last = getState().products.last
var newProducts = await firebase_product.firestore()
.collection('store_products').orderBy('id')
.startAfter(last).limit(limit)
const result = newProducts.get().then((querySnapshot) => {
var lastVisible = last + limit;
const products = querySnapshot.docs.map((doc) => {
return { ...doc.data() }
})
return {
products : products,
lastVisible: lastVisible
}
})
// return retrieved data from firebase
return result
})
But doing this, I could skip the serialization check config all together as well. Not sure if this is the correct way, but this is how I got pagination working. Feel free to let me know if there is other way to approach this.
This error will come if you store non-serializable object in redux store!
if you are getting some non-serializable data from firebase, serialize it before storing it in redux store!
const nonSerializable = firestore().collection().doc(uid).get().data();
// if using this nonSerializable object in reducer, serialized it using JSON.parse(JSON.stringify())
const serializable = JSON.parse(JSON.stringify(nonSerializable))
you should save last visible path not the whole data like this
const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1].ref.path

How to handle Promise in custom react useEffect

I have two components which i am working with. In the first component, i made a custom useEffect hook that retrieves data from my server. Please see the code below:
Code snippet One
import {useState, useCallback} from 'react';
import {stageQuizApi} from '../api/quiz';
import {QuestionService} from "../services/IdDbServices/question_service";
const usePostData = ({url, payload, config}) => {
const [res, setRes] = useState({data: null, error: null, isLoading: false});
const callAPI = useCallback(() => {
setRes(prevState => ({...prevState, isLoading: true}));
stageQuizApi.patch(url, payload, config).then( res => {
setRes({data: res.data, isLoading: false, error: null});
const questionInDb = {};
const {NoTimePerQuestion,anwser, question, playerDetails, option} = res.data.data;
const {playerid,anwserRatio, name} = playerDetails
questionInDb.timePerQuestion = NoTimePerQuestion;
questionInDb.anwserRatio = anwserRatio;
questionInDb.options = option;
questionInDb.answer = anwser;
questionInDb.playerId = playerid;
questionInDb.name = name;
questionInDb.question = question;
const Service = new QuestionService();
Service.addStudent(questionInDb).
then(response=>console.log(response))
.catch(err=>console.log(err));
}).catch((error) => {
console.log(error)
if (error.response) {
const errorJson = error.response.data
setRes({data: null, isLoading: false, error: errorJson.message});
} else if (error.request) {
setRes({data: null, isLoading: false, eror: error.request});
} else {
setRes({data: null, isLoading: false, error: error.message});
}
})
}, [url, config, payload])
return [res, callAPI];
}
export default usePostData;
The above module has two purpose. It first makes an axios request to my endpoint and secondly makes a database insertion to browser IndexDb (similar to localstorage but with sql approach) ( like inserting data into the database using the response that was gotten from the first request. so typically i have a promise in the outer .then block. This part:
Code snippet Two
const questionInDb = {};
const {NoTimePerQuestion,anwser, question, playerDetails, option} = res.data.data;
const {playerid,anwserRatio, name} = playerDetails
questionInDb.timePerQuestion = NoTimePerQuestion;
questionInDb.anwserRatio = anwserRatio;
questionInDb.options = option;
questionInDb.answer = anwser;
questionInDb.playerId = playerid;
questionInDb.name = name;
questionInDb.question = question;
const Service = new QuestionService();
Service.addStudent(questionInDb).
then(response=>console.log(response))
.catch(err=>console.log(err));
Here is the problem, I am trying to maintain state as i want the result of this module to be shared in another route and i don't want to hit the server again hence i inserted the result into indexDb browser storage. Here is the code that executes the above module:
Code snippet Three
const displaySingleQuestion = ()=>{
OnUserGetQuestion();
history.push('/player/question');
}
The above method is called from my first route /question and it is expected to redirect user to the /player/question when the displaySingleQuestion is called.
On the new route /player/question i then want to fetch the data from IndexDb and update the state of that component using the useEffect code below:
Code snippet Four
useEffect(()=>{
const getAllUserFromIndexDb = async()=>{
try{
const result = await new Promise((resolve, reject) => {
service.getStudents().then(res=>resolve(res)).catch(err=>reject(err))
});
console.log('it did not get to the point i was expecting',result)
if(result[0]){
console.log('it got to the point i was expecting')
const singleQuestion = result[0];
const questionPage = playerQuestionToDisplay;
questionPage.name = singleQuestion.name;
questionPage.anwserRatio = singleQuestion.anwserRatio;
questionPage.answer = singleQuestion.answer;
questionPage.options = singleQuestion.options;
questionPage.playerId = singleQuestion.playerId;
questionPage.question = singleQuestion.question;
questionPage.timePerQuestion = singleQuestion.timePerQuestion;
return setplayerQuestionToDisplay({playerQuestionToDisplay:questionPage})
}
}
catch(error){
console.log(error)
}
}
getAllUserFromIndexDb();
return function cleanup() {
setplayerQuestionToDisplay({playerQuestionToDisplay:{}})
}
},[history.location.pathname]);
The problem is that only one Button click (Code snippet three)(displaySingleQuestion()) triggers the whole functionality and redirect to the /player/question page but in this new route the state is not been set until a page reload as occurred, i tried debugging the problem and i found out that when the button is clicked i found out that Code snippet two is executed last hence when Code snippet Four ran it was in promise and until a page reloads occurs the state of the component is undefined
Thanks for reading, Please i would appreciate any help in resolving this issue.

Why is my custom hook called so many times?

I'm trying to implement a custom hook to provide the app with a guest shopping cart. My hook wraps around the useMutation hook from Apollo and it saves the shopping cart id in a cookie while also providing a function to "reset" the cart (basically, to remove the cookie when the order is placed).
Code time! (some code omitted for brevity):
export const useGuestCart = () => {
let cartId;
const [createCart, { data, error, loading }] = useMutation(MUTATION_CREATE_CART);
console.log(`Hook!`);
if (!cartId || cartId.length === 0) {
createCart();
}
if (loading) {
console.log(`Still loading`);
}
if (data) {
console.log(`Got cart id ${data.createEmptyCart}`);
cartId = data.createEmptyCart;
}
const resetGuestCart = useCallback(() => {
// function body here
});
return [cartId, resetGuestCart];
};
In my component I just get the id of the cart using let [cartId, resetCart] = useGuestCart(); .
When I run my unit test (using the Apollo to provide a mock mutation) I see the hooked invoked several times, with an output that looks something like this:
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:59
Still loading
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:62
Got cart id guest123
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
I'm only getting started with hooks, so I'm still having trouble grasping the way they work. Why so many invocations of the hook?
Thank you for your replies!
Think of hooks as having that same code directly in the component. This means that every time the component renders the hook will run.
For example you define:
let cartId;
// ...
if (!cartId || cartId.length === 0) {
createCart();
}
The content inside the statement will run on every render as cartId is created every time and it doesn't have any value assigned at that point. Instead of using if statements use useEffect:
export const useGuestCart = () => {
const [cartId, setCartId] = useState(0);
const [createCart, { data, error, loading }] = useMutation(
MUTATION_CREATE_CART
);
const resetGuestCart = () => {
// function body here
};
useEffect(() => {
if(!cartId || cartId.length === 0){
createCart();
}
}, [cartId]);
useEffect(() => {
// Here we need to consider the first render.
if (loading) {
console.log(`Started loading`);
} else {
console.log(`Finished loading`);
}
}, [loading]);
useEffect(() => {
// Here we need to consider the first render.
console.log(`Got cart id ${data.createEmptyCart}`);
setCartId(data.createEmptyCart);
}, [data]);
return [cartId, resetGuestCart];
};
Also notice that there is no actual benefit from using useCallback if the component which is receiving the function is not memoized.

Apply a throttled middleware in redux

I am creating a small middleware for redux. Since my electron application connects to another database and updates it's state (so that I may see the state of this application on my main application). Here is my basic code:
const BLACKLIST = ['redux-form/FOCUS ', 'redux-form/BLUR'];
export remoteMiddleware => store => next => action => {
const db = MongoClient.connect(process.env.MONGO_URL);
try {
if (!BLACKLIST.includes(action.type)) {
const state = store.getState();
db.collection('state').update(
{ _id: process.env.APP_ID },
{ $set: { state } }
)
}
} finally {
db.close();
}
}
However, I am having a bit of a problem where this is firing TOO often, when it doesn't always need to fire immediately. I would prefer if it fired every n iterations, or perhaps no more than x ms.
Is there a way to throttle a redux middleware so that it will only fire every n times, or such that it will only run every x number of ms?
How about simply
const BLACKLIST = ['redux-form/FOCUS ', 'redux-form/BLUR'];
var lastActionTime = 0;
export remoteMiddleware => store => next => action => {
let now = Date.now();
try {
if (!BLACKLIST.includes(action.type) && now - lastActionTime > 100)
...
} finally() {
lastActionTime = now;
db.close();
}
}

Categories

Resources