I am having problem with updating document in firebase. I am a bit new, so this question is probably very silly but I wasn't able to find an answer online. I have a collection in my firebase called 'therapists', which contains some information. I want to be able to update some information of a specific document, they are stored by 'uid', which is user id using authentication (therapists are users). I have an admin screen that in where I want the updated to be made. In my reducer.js file I added 'therapist' field, which will contain the uid of the therapist we want to update. I set the therapist in one file and then I am reading it in the other file(class), I am able to edit information once, but after that even though I can read all the information I cannot edit anything in the firebase from that class.
Here is a reducer:
export const initialState = {
patient: null,
therapist: null,
user: null
};
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case "SET_USER":
return {
...state,
user: action.user
};
case "SET_THERAPIST":
return Object.assign({}, state, {
therapist: action.therapist
})
case "SET_PATIENT":
return {
...state,
patient: action.patient
};
default:
return state;
}
};
export default reducer;
Here is how I set the therapist in initial class, which contains all the therapists:
let navigate = useNavigate();
const edit = () =>{
dispatch({
type: 'SET_THERAPIST',
therapist: uid
})
let path = "/adminEdit";
navigate(path);
}
Here the class that does the edit:
Reading the file:
const [{ therapist }, dispatch] = useStateValue();
useEffect(() => {
console.log(therapist)
if (therapist){
db.collection('therapists').doc(therapist).onSnapshot((snapshot) => {
setUid(snapshot.data().uid);
setPhone(snapshot.data().phone);
setName(snapshot.data().name);
setPhone(snapshot.data().phone);
setAddress(snapshot.data().address);
const newService = []
for (let i = 0; i < snapshot.data().service.length; i++) {
newService.push({ value: snapshot.data().service[i], label: snapshot.data().service[i]})
}
setService(newService);
const newLanguage = []
for (let i = 0; i < snapshot.data().language.language.length; i++) {
newLanguage.push(snapshot.data().language.language[i])
}
setLanguage(newLanguage);
});
}
}, [therapist]);
writing back to file:
const change = () => {
console.log("hi")
const serviceLabels = []
for (let i = 0; i < service.length; i++) {
serviceLabels.push(service[i].label);
}
console.log(serviceLabels)
console.log(uid)
console.log(therapist)
db
.collection('therapists')
.doc(uid)
.update({
name: name,
phone: phone,
address: address,
service: serviceLabels,
language: {language},
})
alert("You have changed");
}
All the alert, console logs are working as expected, it is just not updating the database after I edit once, when I log out as a user and log back in, I can edit once again before I am not able to edit again, even different therapist(object). I am pretty sure the issue is somewhere with the how I am using useStateValue() and how I am updating the 'therapist' value, it for some reason blocks writing to the firebase as soon as I am updating the value of therapist in reducer, even if with the same exact value.
Related
This component is for counting views at page level in Next.js app deployed on AWS Lambda
function ViewsCounter({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.catch(console.log);
registerView();
}, [slug]);
return (
<>
{views}
</>
);
}
This one is for displaying views on homepage
function ViewsDisplay({ slug }: { slug: string }) {
const { data } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
return (
<>
{views}
</>
);
}
While it works as expected on localhost, looks like it displays only the first fetched value and doesn't revalidate it for some reason.
When visiting the page, Counter is triggered correctly and the value is changed in DB.
Probably it has something to do with mutating, any hints are appreciated.
useSWR won't automatically refetch data by default.
You can either enable automatic refetch using the refreshInterval option.
const { data } = useSWR(`/api/views/${slug}`, fetcher, { refreshInterval: 1000 });
Or explicitly update the data yourself using a mutation after the POST request to the API.
function ViewsCounter({ slug }: { slug: string }) {
const { data, mutate } = useSWR(`/api/views/${slug}`, fetcher);
const views = new Number(data?.total);
useEffect(() => {
const registerView = () =>
fetch(`/api/views/${slug}`, { method: "POST" })
.then(() => {
mutate();
})
.catch(console.log);
registerView();
}, [slug]);
return (<>{views}</>);
}
Take a look at my Slice below
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const KEY = process.env.REACT_APP_API_KEY
const BASE_URL = process.env.REACT_APP_BASE_URL
const API = `${BASE_URL}/countrymap`
const initialState = {
mapdata:[],
status: 'idle',
error:null
}
export const fetchMapData = createAsyncThunk(
'mapdata/fetchMapData',
async (id) => {
try {
const response = await axios.get(
API,
{
headers: {
'Content-Type': 'application/json',
'X-API-KEY': KEY,
},
params: {
titleId: id,
}
}
)
return response.data.Item;
} catch (error) {
console.error('API call error:', error.message);
}
}
)
const mapSlice = createSlice({
name: 'mapdata',
initialState,
reducers:{
},
extraReducers(builder) {
builder
.addCase(fetchMapData.fulfilled, (state, action) => {
state.status = 'succeeded'
//This attempt to make it an array failed with the error
// that the state is not iterable
state.mapdata = [action.payload, ...state]
console.log("map payload: ", state.mapdata);
})
}
})
// SELECTORS
// This works for an array, but the data is originally coming
// in as an object instead
export const allMapData = (state, id) => state.mapdata.mapdata.find(item => item.title_id === id);
export default mapSlice.reducer
for reference, look at these two console logs from two different API calls to two different endpoints from two different slices . Except Media is coming back as an array, and map is an object
I need to either, turn the state.mapdata into an Object to I can use the selector the way it is or re-code the selector so that it doesn't use the .find() function because that's an array function. But either way, it needs to compare the titleId in the data to the id in the params
Sorry for not providing workable code. I would but there is an insane amount of dependencies here
You should use
state.mapdata = [action.payload, ...state.mapdata]
instead of
state.mapdata = [action.payload, ...state]
I have an AsyncThunk method named likePost when a user clicks like on a post it will send this action via dispatch. The method runs fine, and in my database the Post is updated successfully, but I can't get Redux to update the Post that is liked during the .fulfilled method.
Here is what I'm currently working with:
// shoes = [{_id: '', title: '', likes: []}, ...{}]
export const likePost = createAsyncThunk(
'posts/likePost',
async (payload, { rejectWithValue }) => {
try {
const response = await userTokenAxios.post(`/${payload}/like`);
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
[likePost.fulfilled]: (state, action) => {
const post = state.posts.find((post) => post._id === action.payload._id);
post.likes.push(action.payload.user);
},
Instead of finding the post, get the index for that object in state.posts
const post = state.posts.find((post) => post._id === action.payload._id);
const postIndex=state.posts.findIndex((post)=> post._id === action.payload._id);
Now you can push the user in likes array:
state.posts[postIndex].likes.push(action.payload.user)
I am aiming to have a state in my Vuex store which gets populated on initial app load with data from an external API as follows:
import axios from 'axios'
export const state = () => ({
airports: [],
pairing: {
departure: null,
arrival: null
},
loading: false
})
export const getters = {
getAirports: (state) => {
return state.airports
}
}
export const mutations = {
SET_AIRPORTS(state, payload) {
state.airports = payload
},
SET_LOADING(state, payload) {
state.loading = payload
},
SET_AIRPORT(state, { airport, type }) {
state.pairing[type] = airport
},
CLEAR_AIRPORT(state, type) {
state.pairing[type] = null
}
}
export const actions = {
loadAirports({ commit }) {
commit('SET_LOADING', true)
axios.get('https://raw.githubusercontent.com/jpatokal/openflights/master/data/airports.dat')
.then(response => {
// Get each row of data from the source
const rows = response.data.split('\n');
// Convert data from row to object
const airports = rows.map(row => {
// Parse the comma-separated fields from the line by using
const fields = row.split(',')
.map(x => x.replace(/(^"|"$)/g, ''));
return {
id: fields[0],
name: fields[1],
city: fields[2],
country: fields[3],
iata: fields[4],
icao: fields[5],
longitude: fields[6],
latitude: fields[7],
};
});
commit('SET_AIRPORTS', airports)
commit('SET_LOADING', false)
})
},
}
Usually I would just dispatch the loadAirports action in the App.vue when working with Vue.js standalone. However, as I am building my app in Nuxt.js I cannot seem to figure out how to load the data to my state without dispatching this method in every page I create but rather just once on app load.
Any suggestions?
If you have an action called nuxtServerInit in universal mode, Nuxt will call it with the Nuxt context. You can use this function to load data or dispatch other actions:
const actions = {
nuxtServerInit({ dispatch }, ctx) {
dispatch('loadAirports');
}
}
Note that nuxtServerInit is only called server side (or during compile-time if statically generating). You can implement a similar nuxtClientInit by creating a client plugin that immediately dispatches to the store.
https://nuxtjs.org/docs/directory-structure/store/#the-nuxtserverinit-action
This is actually quite simple to do in NuxtJs.
First of all define your initial state:
export const state = () => ({
airports: [],
})
Since Nuxt gives access to nuxtServerInit inside of your file you can do this:
//NOTE this only works when mode is set to universal inside of your nuxt config
export const actions = {
async nuxtServerInit({ commit }) {
const data = await this.$axios.$get(`your-api`)
// you can do all of your logic here before commiting
commit('setAirports', data)
}
}
than all you have left to do is create a mutatation to fill the state with your data
export const mutations = {
setAirports(state, payload) {
state.airports.push(...payload)
},
}
I have a like function on the backend (Node, MongoDB) that returns the given post with updated likes counter. This works, tested it with Postman. This is just an object with a bunch of properties like likes, _id., by, createdAt and so on...
let p = await Post.findById(req.params.id).populate("by");
return res.json(p);
Then I have a like action in React:
export const like = (id) => (dispatch) => {
const token = localStorage.getItem("token");
if (token) {
axios
.put(`http://localhost:5000/likePost/${id.id}`, id, {
headers: { "X-Auth-Token": token },
})
.then((res) => {
dispatch({
type: LIKE,
payload: res.data,
});
});
}
};
And I have a LIKE reducer:
case LIKE:
return {
...state,
posts: state.posts.map((p) => {
return { ...p };
}),
};
The LIKE reducer triggers when I click on the button and on the backend I can see the update but on the client side it doesn't update. I use redux-logger and the posts state is not updated.
What did I do wrong? I thought that spreading all the posts (...p) will update it, since it is updated on the backend.
This one works:
case LIKE:
return {
...state,
posts: state.posts.map((p) => {
if (p._id === action.payload._id) {
p.likes = action.payload.likes;
}
return p;
}),
};