How can I update an object without replacing it in Supabase? - reactjs

I have a function, updateHistory:
export const updateHistory = async (id, historyObj) => {
const { data, error } = await supabase
.from("property")
.update({ history: historyObj })
.eq("id", id);
return data;
};
I am calling it like so:
useEffect(() => {
let tempHistory = {
column: column,
field: name,
previousValue: `${defaultValue}`,
updatedValue: `${changedValue}`,
time: new Date().toISOString(),
};
let newHistory = [tempHistory, ...history];
setHistory(newHistory);
updateHistory(propertyId, newHistory);
}, [changedValue]);
The goal is to have a log history with all previous values, with the current value being the 1st element in the array of objects. The issue is, with this code, it is just replacing the data in the history column with only the current value, rather than just adding to it and keeping the previous values.
I'm not sure why my current code is not working.
Thanks in advance.

I figured this out. This is not an issue with Supabase. history was null at some points, which replaced the previous values of it with nothing.

Related

How do you access query arguments in getSelectors() when using createEntityAdapter with RTK Query

I've been following along the REDUX essentials guide and I'm at part 8, combining RTK Query with the createEntityAdapter. I'm using the guide to implement it in a personal project where my getUni endpoint has an argument named country, as you can see from the code snippet below.
I'm wondering is there anyway to access the country argument value from the state in universityAdaptor.getSelector(state => ) at the bottom of the snippet, as the query key name keeps changing.
import {
createEntityAdapter,
createSelector,
nanoid
} from "#reduxjs/toolkit";
import {
apiSlice
} from "../api/apiSlice";
const universityAdapter = createEntityAdapter({})
const initialState = universityAdapter.getInitialState();
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getUni: builder.query({
query: country => ({
url: `http://universities.hipolabs.com/search?country=${country}`,
}),
transformResponse: responseData => {
let resConvert = responseData.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map(each => {
return { ...each,
id: nanoid()
}
});
return universityAdapter.setAll(initialState, resConvert)
}
})
})
});
export const {
useGetUniQuery
} = extendedApiSlice;
export const {
selectAll: getAllUniversity
} = universityAdapter.getSelectors(state => {
return Object.keys({ ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }).length === 0
? initialState : { ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }
})
UPDATE: I got it working with a turnery operator due to the multiple redux Actions created when RTK Query handles fetching. Wondering if this is best practice as I still haven't figured out how to access the country argument.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
return !Object.values(state.api.queries)[0]
? initialState : Object.values(state.api.queries)[0].status !== 'fulfilled'
? initialState : Object.values(state.api.queries)[0].data
})
I wrote that "Essentials" tutorial :)
I'm actually a bit confused what your question is - can you clarify what specifically you're trying to do?
That said, I'll try to offer some hopefully relevant info.
First, you don't need to manually call someEndpoint.select() most of the time - instead, call const { data } = useGetThingQuery("someArg"), and RTKQ will fetch and return it. You only need to call someEndpoint.select() if you're manually constructing a selector for use elsewhere.
Second, if you are manually trying to construct a selector, keep in mind that the point of someEndpoint.select() is to construct "a selector that gives you back the entire cache entry for that cache key". What you usually want from that cache entry is just the received value, which is stored as cacheEntry.data, and in this case that will contain the normalized { ids : [], entities: {} } lookup table you returned from transformResponse().
Notionally, you might be able to do something like this:
const selectNormalizedPokemonData = someApi.endpoints.getAllPokemon.select();
// These selectors expect the entity state as an arg,
// not the entire Redux root state:
// https://redux-toolkit.js.org/api/createEntityAdapter#selector-functions
const localizedPokemonSelectors = pokemonAdapter.getSelectors();
const selectPokemonEntryById = createSelector(
selectNormalizedPokemonData ,
(state, pokemonId) => pokemonId,
(pokemonData, pokemonId) => {
return localizedPokemonSelectors.selectById(pokemonData, pokemonId);
}
)
Some more info that may help see what's happening with the code in the Essentials tutorial, background - getLists endpoint takes 1 parameter, select in the service:
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId) : [];
};
And my selector in the slice:
export const selectAllLists = createSelector(getListsResult, (listsResult) => {
console.log('inside of selectAllLists selector = ', listsResult);
return listsResult.data;
// return useSelector(listsResult) ?? [];
});
Now this console logs listsResult as ƒ memoized() { function! Not something that can have .data property as tutorial suggests. Additionally return useSelector(listsResult) - makes it work, by executing the memoized function.
This is how far I got, but from what I understand, the code in the Essentials tutorial does not work as it is...
However going here https://codesandbox.io/s/distracted-chandrasekhar-r4mcn1?file=/src/features/users/usersSlice.js and adding same console log:
const selectUsersData = createSelector(selectUsersResult, (usersResult) => {
console.log("usersResult", usersResult);
return usersResult.data;
});
Shows it is not returning a memorised function, but an object with data on it instead.
Wonder if the difference happening because I have a parameter on my endpoint...
select returns a memoized curry function. Thus, call it with first with corresponding arg aka tribeId in your case and then with state. This will give you the result object back for corresponding chained selectors.
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId)(state) : [];
};
The intention of the getUni endpoint was to produce an array of university data. To implement the .getSelector function to retrieve that array, I looped over all query values, searching for a getUni query and ensuring they were fulfilled. The bottom turnery operator confirms the getUni endpoint was fired at least once otherwise, it returns the initialState value.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
let newObj = {};
for (const value of Object.values(state.api.queries)) {
if (value?.endpointName === 'getUni' && value?.status === 'fulfilled') {
newObj = value.data;
}
}
return !Object.values(newObj)[0] ? initialState : newObj;
})

Why do I get undefined value from async function?

I have been using Google firestore as a database for my projet.
In the collection "paths", I store all the paths I have in my app, which are composed of 2 fields : name, and coordinates (which is an array of objects with coordinates of points).
Anyway, i created a utility file in utils/firebase.js
In the file, i have this function which gets all the paths in my collection and return an array of all documents found :
export const fetchPaths = () => {
let pathsRef = db.collection('paths');
let pathsArray = []
pathsRef.get().then((response) => {
response.docs.forEach(path => {
const {nom, coordonnees } = path.data();
pathsArray.push({ nom: nom, coordonnees: coordonnees})
})
console.log(pathsArray)
return pathsArray;
});
};
In my react component, What i want to do is to load this function in useEffect to have all the data, and then display them. Here is the code I use :
import { addPath, fetchPaths } from './Utils/firebase';
//rest of the code
useEffect(() => {
let paths = fetchPaths()
setLoadedPaths(paths);
}, [loadedPaths])
//.......
The issue here is if I console log pathsArray in the function it's correct, but it never gets to the state.
When i console log paths in the component file, i get undefined.
I am quite new with react, i tried different things with await/async, etc. But I don't know what i am doing wrong here / what i misunderstand.
I know that because of my dependency, i would be supposed to have an infinite loop, but it's not even happening
Thank you for your help
Have a nice day
fetchPaths does not return any result. It should be:
export const fetchPaths = () => {
let pathsRef = db.collection('paths');
let pathsArray = []
return pathsRef.get().then((response) => {
response.docs.forEach(path => {
const {nom, coordonnees } = path.data();
pathsArray.push({ nom: nom, coordonnees: coordonnees})
})
console.log(pathsArray)
return pathsArray;
});
};
note the return statement.
Since the fetchPaths returns a promise, in the effect it should be like following:
useEffect(() => {
fetchPaths().then(paths =>
setLoadedPaths(paths));
}, [loadedPaths])

Does anyone have a way to delete id before passing through url? React Axios

Does anyone have a way to delete id before passing through url?
I want to remove the id that is in the addedItems array.
Delete only id. Without interfering with the value inside
onFinish = async(values) => {
const { addedItems, total } = this.state;
values.re_total = total;
const { data, result } = this.props.loginReducer;
await addedItems.forEach((doc) => {
doc.car_number = values.cus_car_number;
doc.reference = values.re_reference;
doc.detail = values.re_detail;
doc.adName = result.data.u_fname;
doc.total = doc.price * doc.quantity;
});
delete addedItems.id;
console.log(addedItems)
await httpClient
.post(`http://localhost:8085/api/v1/revenue/revenue`,{addedItems})
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log("Error :", error);
})
message.success({ content: 'บันทึกรายรับเรียบร้อย!', duration: 2, style: {
marginTop: '5vh',
}} ,100);
await this.props.history.goBack();
};
I'm assuming that the id is in each item of the addedItems array. Rather than it being added to the array directly (addedItems.id = 'bad').
I switched from using a Array#forEach to Array#map so that it creates a new array. Then I use object destructuring with the rest operator (...). So I can pull the id out of the doc object and make a shallow copy of the doc at the same time. The shallow copy is important so we don't accidentally modify state values without meaning to.
That's actually the biggest issue in your code as is - it's modifying the this.state.addedItems unintentionally, as you are changing the docs without doing a shallow copy first.
As for the code: Replace the await addedItems.forEach() with this. You didn't actually need an await there, as Array#forEach doesn't return anything let alone a promise.
const mappedItems = addedItems.map(({id,...doc})=>{
doc.car_number = values.cus_car_number;
doc.reference = values.re_reference;
doc.detail = values.re_detail;
doc.adName = result.data.u_fname;
return doc;
})
Then use {addedItems: mappedItems} as the body of the httpClient.post. So that you use the new array instead of the old one pulled from the state.

Target one specific piece of data and update its state

If I have a piece of data and I store it in a state as follows:
interface DataType {[key: string]: string;}
const [data, setData] = React.useState<DataType>({
{
"Widget1": "weather",
"Widget2": "timer",
"Widget3": "clock"
});
And I want a function to update a single widget, what would be the best way to do so. I tried using one function for each widget, but that is obviously not best practice.
const widget1Handler = (widget: string) => {
var updatedData = {
Widget1: widget,
Widget2: data.Widget2,
Widget3: data.Widget3,
Widget4: data.Widget4
};
setData(updatedData);
};
Same for Widget2 and so on. Better would be one function for all of them something like this:
const widgetHandler = (widget: string, widgetNr: number ) => {
const updatedData = ???;
setData(updatedData);
}
How can I target one specific value and update its state?
Thank you for your help.
You can dynamically selected the property name by using computed property names. This will allow you can pass in the widget number to widgetHandler, and update the state.
const widgetHandler = (widget: string, widgetNr: number ) => {
// const someData = ???;
setData({
...data,
['Widget' + widgetNr.toString()]: someData
});
}
You can make use of state updater callback and pass in the widget number to the updater function like widget2 and so on
const widgetHandler = (widget: string, widgetNr: number ) => {
// widgetNr should be of the format Widget1, Widget2 and so on
setData(previousData => ({
...previousData, // spreading all the previous values
[widgetNr]: widget // updating the required value
}));
}

Removing an object from an array in Firestore

I'm just getting into Firebase, and really digging it so far, but I'm having a bit of an issue using a Cloud Function to remove an item from an array in a collection. In the app I'm using, users have a document in a users collection that contains user-specific data that doesn't fit within the authentication model. The data looks like this:
Screenshot of user collection structure.
(It's a fitness-related app, hence the array being called 'workouts')
The user has the ability to create a workout, and when they do, after it's created, a cloud function watching for new workouts adds the ID of the newly created workout to the user document, appending it to the workouts array using arrayUnion:
exports.addWorkoutToAuthorsListOfWorkouts = functions.firestore
.document('workouts/{workoutId}')
.onCreate((snap, context) => {
const id = context.params.workoutId
const name = snap.data().title
const uid = snap.data().author
const workout_reference = { id: id, name: name, uid: uid }
const workoutsRef = admin.firestore().collection("users").doc(uid)
return workoutsRef.update({
workouts: admin.firestore.FieldValue.arrayUnion(workout_reference)
})
})
This works fine, but I want the user to be able to delete workouts, and I didn't add a string to the workouts array, but instead an object like this:
{ id: "1214", name: "My workout", uid: "123asd" }
Now, for this exact use-case I could delete the array in my ReactJS app and then do an update, but I'm going to be adding the ability for users to "like" the workouts of other users, which will result in a reference to the workout being added to "workouts" in their personal user document. So if I, as a creator of a workout, delete my workout, I need to be able to delete it from the workouts array of any user who has it. Using arrayRemove didn't work because (I assume) I couldn't pass the object to be removed.
What's the best-practices way to do this? I made this attempt:
exports.removeWorkoutFromAuthorsListOfWorkouts = functions.firestore
.document('workouts/{workoutId}')
.onDelete((snap, context) => {
const id = context.params.workoutId
const uid = snap.data().author
const name = snap.data().name
let newWorkouts = new Array()
admin.firestore().collection("users").doc(uid).collection("workouts").get().then((querySnapshot) => {
querySnapshot.forEach((doc: any) => {
if (doc.id !== id) {
newWorkouts.push(doc.data())
}
});
});
const workoutsRef = admin.firestore().collection("users").doc(uid)
return workoutsRef.update({
workouts: newWorkouts
})
})
But Firebase didn't like it at all, and I'm new enough to the platform that I realize this is most likely due to the result of a knowledge gap, rather than any problem with Firestore or Cloud Functions.
Any help you could provide would be very much appreciated. Cheers!
UPDATE: Got it working, with the following code:
exports.removeWorkoutFromSubscribersListOfWorkouts = functions.firestore
.document('workouts/{workoutId}')
.onDelete((snap, context) => {
const workoutId = context.params.workoutId
const subscribers = snap.data().subscribers
let newWorkouts = new Array()
subscribers.forEach(subscriber => {
let userRef = admin.firestore().collection("users").doc(subscriber)
return userRef.get().then(doc => {
return userRef.update({
workouts: doc.data().workouts.filter(workout => workout.id !== workoutId)
})
})
})
.catch(() => {
console.log("error")
})
})

Resources