keeping a local variable in react - javascript

I want to add a pagination to my app for this reason i coded below code but there is a problem.
Here is my useEffect:
useEffect(() => {
let x = null;
const unsubscribe = chatsRef
.orderBy("createdAt", "desc")
.limit(10)
.onSnapshot((querySnapshot) => {
const messagesFirestore = querySnapshot
.docChanges()
.filter(({ type }) => type === "added")
.map(({ doc }) => {
const message = doc.data();
x = message;
return { ...message, createdAt: message.createdAt.toDate() };
});
appendMessages(messagesFirestore);
if (latestMessage != null) {
if (
new Date(
latestMessage["createdAt"]["seconds"] * 1000 +
latestMessage["createdAt"]["nanoseconds"] / 1000000
) >
new Date(
x["createdAt"]["seconds"] * 1000 +
x["createdAt"]["nanoseconds"] / 1000000
)
) {
latestMessage = x;
}
} else {
latestMessage = x;
}
});
return () => unsubscribe();
}, []);
I got the data from my database and i saved the oldest data in to latestMessage (for pagination) but the problem is that:
I declared my latestMessage out of my function like that:
let latestMessage = null;
export default function ChatTutor({ route }) {
...
}
And I passed my props to ChatTutor component (chatRoomId, username...) and according to that id, the room and its data are rendered. But the latestMessage always set some value and when i go to parent component and clicked another chatRoom, ChatTutor has a value of latestMessage's other value(oldest value). How can i set latestMessage null when i go to the parent ?

You can use useRef to store local mutable data (it would not participate in re-renders):
export default function ChatTutor({ route }) {
const latestMessage = useRef(null); // null is initial value
// ...
latestMessage.current = 'some new message' // set data
console.log(latestMessage.current) // read data
return <>ChatTutor Component</>
}

Related

I am trying to delete a note in react but I am getting an error that you cannot access new notes before initialization?

React
Find error in delete note function that can't acces the newNotes befor initialization thats why that note can not be deleted?
The deleteNote function is working because its console log the message with the id that i written in it but it does'not delete the note,I already declare the newNote and initial the value in it but i can't understand why it gave the error?
const [notes, setNotes] = useState(notesInitial)
//Add Note
const addNote = (title, description, tag) => {
//TODO API CALL
console.log("adding a new note");
const note = {
"_id": "63074e71318fac99be7ce65a",
"user": "62ee8f0b86e5c4946d3b75d5",
"title": title,
"description": description,
"tag": tag,
"date": "2022-08-25T10:26:57.324Z",
"__v": 0
};
setNotes(notes.concat(note));
}
//Delete A Note
const deleteNote = (id) => {
console.log("The node delteing with id" + id);
I think I missing something here
const newNotes = newNotes.filter((notes) => { return notes._id !== id })
setNotes(newNotes);
}
return (
<noteContext.Provider value={{ notes, addNote, deleteNote, editNote }}>
{props.children}
</noteContext.Provider>
)
}
export default NoteState;
yes you made mistake here so convert it from
//Delete A Note
const deleteNote = (id) => {
console.log("The node delteing with id" + id);
I think I missing something here
const newNotes = newNotes.filter((notes) => { return notes._id !== id })
setNotes(newNotes);
}
to
const deleteNote = (id) => {
console.log("The node delteing with id" + id);
I think I missing something here
const newNotes = notes.filter((note) => { return note._id !== id })
setNotes(newNotes);
}
const newNotes = newNotes.filter((notes) => { return notes._id !== id })
newNotes are being declared again and it won't take from your useState one.
In the deleteNode function name it anything else.
const deleteNote = (id) => {
console.log("The node delteing with id" + id);
I think I missing something here
const newNotes2 = notes.filter((notes) => { return notes._id !== id })
setNotes(newNotes2);
}

How to setup default value of fetched data

I am fetching data from my "backend" CMS - everything works fine, but when I want to setup default value I am getting error of undefined data.
My content is divided into some categories e.g.
const [category1, setCategory1] = useState([]);
const [category2, setCategory2] = useState([]);
Then I am fetching data from backend
useEffect(() => {
const fetchData = async () => {
const result = await client.query(
Prismic.Predicates.at('document.type', 'post'),
{ pageSize: 100 }
);
if (result) {
const category1Arr = [];
const category2Arr = [];
result.results.forEach((post) => {
switch (post.data.category[0].text) {
case 'Category1':
category1Arr.push(post);
break;
case 'Category2':
category2Arr.push(post);
break;
default:
console.warn('Missing blog post category.');
}
});
setCategory1(category1Arr);
setCategory2(category2Arr);
return setDocData(result);
} else {
console.warn(
'Not found'
);
}
};
fetchData();
}, []);
Code above works without any issues, BUT chosen category should have one post opened by default.
I am having menu when you can pick category and therefore I am using activeComponent function.
const [activeComponent, setActiveComponent] = useState('category1');
const modifyActiveComponent = React.useCallback(
(newActiveComponent) => {
setActiveComponent(newActiveComponent);
},
[setActiveComponent]
);
So category1 is active on default, therefore the category should also have default post.
This is what I tried:
const [postTitle, setPostTitle] = useState('');
const [postText, setPostText] = useState([]);
{activeComponent === 'category1' &&
category1.length > 0 && category1.map((post) => {
return ( <button onClick={()=> {setPostTitle(post.data.title[0].text); setPostText(post.data.body)}}
And data are shown typical just as a {postTitle} & {postText}
I tried to put default value in each category like this
useEffect(() => {
if (activeComponent === 'category1') {
setPostTitle(category1[2].data.title[0].text);
setPostText(category1[2].data.body);
}
if (activeComponent === 'category2') {
// same here just with category2 }
}, [activeComponent, category1, category2]);
But the code above gives me an error or undefined data even though it should be correct.
How can I achieve to make a default value with this logic above? Everything works like charm, just the default data does not work :(
This is array of objects:
In your last piece of code you have a typo, here:
useEffect(() => {
if (activeComponent === 'category1') {
setPostTitle(category1[2].data.title[0].text);
setPostText(category[2].data.body);
}
if (activeComponent === 'category2') {
// same here just with category2 }
}, [activeComponent, category1, category2]);
it should be:
useEffect(() => {
if (activeComponent === 'category1') {
setPostTitle(category1[2].data.title[0].text);
setPostText(category1[2].data.body);
}
if (activeComponent === 'category2') {
// same here just with category2 }
}, [activeComponent, category1, category2]);
in the first if statement, in second setPostText, you have category instead of category1.

use async function to get draft inside reducer of useImmerReducer

I have this reducer function that I use for state management of my app.
const initialState = {roles: null};
const reducer = (draft, action) => {
switch (action.type) {
case 'initialize':
//what should i do here????
return;
case 'add':
draft.roles = {...draft.roles, action.role};
return;
case 'remove':
draft.roles = Object.filter(draft.roles, role => role.name != action.role.name);
}
};
const [state, dispatch] = useImmerReducer(reducer, initialState);
to initialize my state I must use an async function that reads something from asyncStorage if it exists, must set draft.roles to it, if not it should be set to a default value.
const initialize = async () => {
try {
let temp = await cache.get();
if (temp == null) {
return defaultRoles;
} else {
return temp;
}
} catch (error) {
console.log('initialization Error: ', error);
return defaultRoles;
}
};
how can I get initilize function returned value inside 'initialize' case? if I use initilize().then(value=>draft.roles=value) I get this error:
TypeError: Proxy has already been revoked. No more operations are allowed to be performed on it
You cannot use asynchronous code inside of a reducer. You need to move that logic outside of the reducer itself. I am using a useEffect hook to trigger the initialize and then dispatching the results to the state.
There are quite a few syntax errors here -- should state.roles be an array or an object?
Here's my attempt to demonstrate how you can do this. Probably you want this as a Context Provider component rather than a hook but the logic is the same.
Javascript:
import { useEffect } from "react";
import { useImmerReducer } from "use-immer";
export const usePersistedReducer = () => {
const initialState = { roles: [], didInitialize: false };
const reducer = (draft, action) => {
switch (action.type) {
case "initialize":
// store all roles & flag as initialized
draft.roles = action.roles;
draft.didInitialize = true;
return;
case "add":
// add one role to the array
draft.roles.push(action.role);
return;
case "remove":
// remove role from the array based on name
draft.roles = draft.roles.filter(
(role) => role.name !== action.role.name
);
return;
}
};
const [state, dispatch] = useImmerReducer(reducer, initialState);
useEffect(() => {
const defaultRoles = []; // ?? where does this come from?
// always returns an array of roles
const retrieveRoles = async () => {
try {
// does this need to be deserialized?
let temp = await cache.get();
// do you want to throw an error if null?
return temp === null ? defaultRoles : temp;
} catch (error) {
console.log("initialization Error: ", error);
return defaultRoles;
}
};
// define the function
const initialize = async() => {
// wait for the roles
const roles = await retrieveRoles();
// then dispatch
dispatch({type: 'initialize', roles});
}
// execute the function
initialize();
}, [dispatch]); // run once on mount - dispatch should not change
// should use another useEffect to push changes
useEffect(() => {
cache.set(state.roles);
}, [state.roles]); // run whenever roles changes
// maybe this should be a context provider instead of a hook
// but this is just an example
return [state, dispatch];
};
Typescript:
import { Draft } from "immer";
import { useEffect } from "react";
import { useImmerReducer } from "use-immer";
interface Role {
name: string;
}
interface State {
roles: Role[];
didInitialize: boolean;
}
type Action =
| {
type: "initialize";
roles: Role[];
}
| {
type: "add" | "remove";
role: Role;
};
// placeholder for the actual
declare const cache: { get(): Role[] | null; set(v: Role[]): void };
export const usePersistedReducer = () => {
const initialState: State = { roles: [], didInitialize: false };
const reducer = (draft: Draft<State>, action: Action) => {
switch (action.type) {
case "initialize":
// store all roles & flag as initialized
draft.roles = action.roles;
draft.didInitialize = true;
return;
case "add":
// add one role to the array
draft.roles.push(action.role);
return;
case "remove":
// remove role from the array based on name
draft.roles = draft.roles.filter(
(role) => role.name !== action.role.name
);
return;
}
};
const [state, dispatch] = useImmerReducer(reducer, initialState);
useEffect(() => {
const defaultRoles: Role[] = []; // ?? where does this come from?
// always returns an array of roles
const retrieveRoles = async () => {
try {
// does this need to be deserialized?
let temp = await cache.get();
// do you want to throw an error if null?
return temp === null ? defaultRoles : temp;
} catch (error) {
console.log("initialization Error: ", error);
return defaultRoles;
}
};
// define the function
const initialize = async() => {
// wait for the roles
const roles = await retrieveRoles();
// then dispatch
dispatch({type: 'initialize', roles});
}
// execute the function
initialize();
}, [dispatch]); // run once on mount - dispatch should not change
// should use another useEffect to push changes
useEffect(() => {
cache.set(state.roles);
}, [state.roles]); // run whenever roles changes
// maybe this should be a context provider instead of a hook
// but this is just an example
return [state, dispatch];
};

Firebase when add/delete data, app do functions more than once

I have problems with my money app. When I add/delete data from my app (products collection), my app do function "sumPrices()" more than one. For example: When I add one product, make once, add another product, make twice, add another product make three etc. This happen in the same way with delete data.
A do something wrong in my code?
Callback.push push data do array where I unsubscribe events from firebase.
AddStatsUI add UI to my DOM.
index.js:
// delete products
const handleTableClick = e => {
console.log(e); // mouseevent
if (e.target.tagName === 'BUTTON'){
const id = e.target.parentElement.parentElement.getAttribute('data-id');
db.collection('users')
.doc(user.uid)
.collection('products')
.doc(id)
.delete()
.then(() => {
// show message
updateMssg.innerText = `Product was deleted`;
updateMssg.classList.add('act');
setTimeout(() => {
updateMssg.innerText = '';
updateMssg.classList.remove('act');
}, 3000);
productUI.delete(id);
products.sumPrices(user.uid, callbacks).then(value => {
sumStats.addStatsUI('','');
const unsubscribe = db.collection('users').doc(user.uid).get().then(snapshot => {
sumStats.addStatsUI(value[0], snapshot.data().budget);
})
callbacks.push(unsubscribe);
});
})
}
}
table.addEventListener('click', handleTableClick);
callbacks.push(() => table.removeEventListener('click', handleTableClick))
//add new products to firebase
const handleExpenseFormSubmit = e => {
e.preventDefault();
const name = expenseForm.productName.value.trim();
const price = Number(expenseForm.price.value.trim());
console.log(`Product added: ${name}, ${price}`);
const user = firebase.auth().currentUser.uid;
products.addProduct(name, price, user)
.then(() => {
products.sumPrices(user, callbacks).then(value => {
sumStats.addStatsUI('','');
const unsubscribe = db.collection('users').doc(user).onSnapshot(snapshot => {
sumStats.addStatsUI(value, snapshot.data().budget);
})
callbacks.push(unsubscribe);
});
expenseForm.reset()
})
.catch(err => console.log(err));
}
expenseForm.addEventListener('submit', handleExpenseFormSubmit);
callbacks.push(() => expenseForm.removeEventListener('submit', handleExpenseFormSubmit))
product.js:
class Product {
constructor(name, price, budget, user) {
this.products = db.collection('users');
this.budget = budget;
this.name = name;
this.price = price;
this.user = user;
}
async addProduct(name, price, user) { //dodaje produkt do firebase
const now = new Date();
const product = {
name: name,
price: price,
created_at: firebase.firestore.Timestamp.fromDate(now),
};
const response = await this.products.doc(user).collection('products').add(product);
return response;
}
getProducts(callback, user){ //download list from firebase
this.products.doc(user).collection('products')
.orderBy("created_at", "desc")
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added'){
//udpate UI
return callback(change.doc.data(), change.doc.id);
}
});
});
}
updateBudget(budget, user){
this.budget = budget;
db.collection('users').doc(user).update({budget: budget});
// callbacks.push(unsubscribe);
}
async sumPrices(user, callbacks){
let finish = [];
const unsubscribe = this.products.doc(user).collection('products').onSnapshot(snapshot => {
let totalCount = 0;
snapshot.forEach(doc => {
totalCount += doc.data().price;
});
const a = totalCount;
console.log(a);
finish.push(a);
return finish;
})
callbacks.push(unsubscribe);
return finish;
};
};
sumStatsUI.js:
class Stats {
constructor(stats, circle, budget){
this.stats = stats;
this.circle = circle;
this.budget = budget;
}
addStatsUI(data, budget){
if(data) {
const outcome = Math.round(data * 100) / 100;
const sumAll = Math.round((budget - outcome) * 100) / 100;
this.stats.innerHTML += `
<div><span class="budget-name">Budget: </span> <span class="stat-value">${budget}$</span></div>
<div><span class="budget-name">Outcome: </span> <span class="stat-value outcome-value">${outcome}$</span></div>
<div><span class="budget-name">All: </span> <span class="stat-value last-value">${sumAll}$</span></div>
`;
const circle = Math.round(((outcome * 100) / budget) * 100) / 100;
this.circle.innerHTML += `${circle}%`;
} else {
this.stats.innerHTML = '';
this.circle.innerHTML = '';
}};
};
export default Stats;
I add console.log to sumPrices
App screenshot, when I add 2 products and try update budget
Okey, a add some improvement to my code, but still have problems with subscriptions. Now everything it's okey, but when I log out and log in functions getProducts() and updateBudget() no unsubscribe.
Code here:
index.js:
//get the products and render
const unsubscribe = products.getProducts((data, id) => {
console.log(data, id);
productUI.render(data, id);
}, user.uid);
callbacks.push(unsubscribe);
//update budget + form
const handleBudgetFormSubmit = e => {
e.preventDefault();
//update budget
const budget = Number(budgetForm.budget_value.value.trim());
sumStats.addStatsUI('', '');
products.updateBudget(budget, user.uid);
//reset form
budgetForm.reset();
const budgetCart = document.querySelector('#budget');
budgetCart.classList.remove('active');
// show message
updateMssg.innerText = `Your budget was updated to ${budget}$`;
updateMssg.classList.add('act');
setTimeout(() => {
updateMssg.innerText = '';
updateMssg.classList.remove('act');
}, 3000);
};
budgetForm.addEventListener('submit', handleBudgetFormSubmit);
callbacks.push(() =>
budgetForm.removeEventListener('submit', handleBudgetFormSubmit)
);
and else to onAuthStateChanged() -> if(user):
} else {
console.log('user logged out');
authUI('');
productUI.render('');
sumStats.addStatsUI('');
console.log('Callbacks array', callbacks);
callbacks.forEach(callback => callback());
callbacks.length = 0;
}
});
getProducts() and updateBudget():
getProducts(callback, user) {
//download list from firebase
this.products
.doc(user)
.collection('products')
.orderBy('created_at', 'desc')
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === 'added') {
//udpate UI
return callback(change.doc.data(), change.doc.id);
}
});
});
}
updateBudget(budget, user) {
console.log('budget', budget, user);
const db = firebase.firestore();
// this.budget = budget;
db.collection('users')
.doc(user)
.update({ budget: budget });
}
When I log out and log in:
When I have getProducts and add product to collection, this function render (render()) product twice, but add to collection once. When I update budget this return budget but after that, return 0 (on DOM where show budget a can see "Infinity")
And one thing, when I log out, console return error:
TypeError: callback is not a function
at eval (index.js:182)
at Array.forEach (<anonymous>)
at Object.eval [as next] (index.js:182)
at eval (index.cjs.js:1226)
at eval (index.cjs.js:1336)
I think it's because getProducts and updateBudget don't return unsubscribe, but undefined.
Maybe someone have solution for this?

Get list of unique entity keys defined on nested schema

I'm trying to obtain a list of all the keys defined on a normalizr schema, & have written a function that does what I need for a simple schema:
export const collectAttributes = target => {
const schemaKeys = []
if (target.hasOwnProperty('_key')) {
schemaKeys.push(target._key)
}
const definitions = Object.keys(target).filter(key => key[0] !== '_')
definitions.forEach(key => {
collectAttributes(target[key]).forEach(attribute => schemaKeys.push(attribute))
})
return schemaKeys
}
However, this fails on a nested schema definition with a Maximum call stack size exceeded error, as illustrated with this test case:
describe('collectAttributes', () => {
it('should collect all unique collections defined on a recursive schema', () => {
const nodeSchema = new schema.Entity('nodes', {})
const nodeListSchema = new schema.Array(nodeSchema)
nodeSchema.define({ children: nodeListSchema })
expect(collectAttributes(nodeSchema)).toEqual(['nodes'])
})
})
If anyone has ideas on how to collect the already visited schemas such that the recursive function halts, they would be much appreciated.
I figured it out in the end - solution below:
export const isSchema = target => {
if (Array.isArray(target)) {
return target.length ? isSchema(target[0]) : false
} else {
return target.hasOwnProperty('schema') || target instanceof schema.Entity || target instanceof schema.Array
}
}
const recursiveCollect = (target, visited = []) => {
const entities = []
const visitedSchemas = [...visited]
if (isSchema(target)) {
entities.push(target.key)
visitedSchemas.push(target)
}
if (Array.isArray(target) || target instanceof schema.Array) {
/*
* If the current target is an ArraySchema, call `recursiveCollect`
* on the underlying entity schema
*/
return recursiveCollect(target.schema, visitedSchemas)
}
Object.keys(target.schema).filter(x => x[0] !== '_').forEach(definition => {
const childSchema= target.schema[definition]
const alreadyVisited = visitedSchemas.includes(childSchema)
if (isSchema(childSchema) && !alreadyVisited) {
/* Only call `recursiveCollect` on the child schema if it hasn't
* already been encountered
*/
const result = recursiveCollect(childSchema, visitedSchemas)
if (result.entities) {
result.entities.forEach(x => entities.push(x))
}
if (result.visitedSchemas) {
result.visitedSchemas.forEach(x => visitedSchemas.push(x))
}
}
})
return { entities, visitedSchemas }
}
export const collectAttributes = target => {
const { entities } = recursiveCollect(target)
return entities
}

Categories