ngrx-store-localstorage sync and rehydrate based on user id - ngrx

I am trying to sync local storage to NgRx state using ngrx-store-localstorage. I want my store to sync/rehydrate data from/to local storage based on user id. For now I did this:
export function localStorageSyncReducer(reducer: ActionReducer<any>): ActionReducer<any> {
return (state: AppState, action: any) => {
const keys = [...some keys];
return localStorageSync({
keys,
rehydrate: true,
syncCondition: () => (state && state.user && state.user.id !== ''), // Initial value is ''
storageKeySerializer: (key: string): string => ( state ? `${state.user.id}-${key}` : '')
})(reducer)(state, action);
};
}
With this, I am able to save the entire state into local storage based on the user id. However, whenever I refresh the page the hydration is not working...and the entire state is set to initial state, even in the local storage.

Related

How to use a Firestore listener 'ref' in other composables?

I am using Firestore realtime listeners in my Vue 3 / TypeScript app.
I created a composable that returns a ref to a user doc.
export const getUserListener = (userId: string) => {
const user = ref<User>();
const userDocRef = doc(db, 'users', userId);
const unsubscribe = onSnapshot(
userDocRef,
(snapshot) =>
(user.value = {
...snapshot.data(),
id: snapshot.id,
})
);
onUnmounted(unsubscribe);
return user;
};
It works great when populating the front end with user doc values.
But now I want to use those values in another composable, like so:
export const doSomethingWithUserListener = () => {
const user = useGetUserListener('user-id-abc-123');
if (user.value) {
console.log('User found');
} else {
console.log('User not found');
}
}
And this always returns User not found.
I already know why this is happening...
When getUserListener() first runs the user ref is undefined. So the ref returns as undefined immediately.
The onSnapshot part is asynchronous, so it executes some milliseconds later, and only then populates the ref with user data.
This is not an issue when populating fields on the front end, because users do not perceive the delay. But it causes an issue when using the listener in other composables, because the ref value is always undefined.
Since I known why this is happening I'm not looking for an explanation, but rather help with how to overcome this problem.
Summary: I want to use the listener ref value in other composables. But I can't because it always returns undefined.
Any ideas?

firebase update existing db

I created a db at the user registration time, with some empty fields that I want to update once logged in user is at the EditScreen.js
So, inside of that db, I want to target 2 maps with all info/values they have inside.
#1 map "wantsToGetFood" {.......values entered in input fields, and updated on btnpress}
#2 map "wantsToGiveFood" {.......values entered in input fields, and updated on btnpress}
here is a db
In the EditScreen.js
I can check and console log uid like this:
auth.onAuthStateChanged((user) => {
if (user) {
// User logged in already or has just logged in.
console.log('edit screen page look for uid',user.uid);
// } else {
// User not logged in or has just logged out.
// console.log('only for logged in users');
}})
auth comes from my config file "const auth = firebase.auth();"
I cant seem to be able to grab that uid so to go towards map#1 and map#2, so I cant update velues.
Here is what I have so far:
// imports...
export default function EditScreen() {
const[getFoodCheckboxState, getFoodSetCheckboxState] = useState();
const [getFoodText, setGetFoodText] = useState('')
const [importantGetFoodInfo, setImportantGetFoodInfo] = useState('')
const [getFoodAreas, setGetFoodAreas] = useState('')
//and etc... for the rest values
const handleInputChange = () => {
auth
.then((response) => {
const timestamp = firebasestorenotfunc .FieldValue.serverTimestamp()
const dataGetFood = {
ActivityStatusGet: getFoodCheckboxState,
createdAt: timestamp,
getFood: getFoodText,
importantGetFoodInfo: importantGetFoodInfo ,
GetFoodMeetingArea: getFoodAreas,
};
const dataGiveFood = {
ActivityStatusGive: giveFoodCheckboxState ,
createdAt: timestamp,
giveFood: giveFoodText,
importantGiveFoodInfo: importantGiveFoodInfo ,
GiveFoodMeetingArea: giveFoodAreas,
}
const uid = response.user.uid
const usersRef = db.collection('users')
usersRef
.doc(uid)
.child('wantsToGetFood') //map#1
.update(dataGetFood)
.child('wantsToGiveFood') //map#2
.update(dataGiveFood)
.then(() => {
console.log('update was a success');
navigation.navigate('Home')
})
.catch((error) => {
alert(error)
});
})
}
return(......
//TextInputs, text, button, etc...
)}
Current error is: TypeError: undefined is not a function (near '..._config.auth.then...')
how do i fix this, and make an update to the db map values I want?
the closest I got, it can not get uid, it undefined (but console.log if user is logged works and shows uid...

How to execute buisness logic in NgRx reducers

Hi am using NgRx store for state management in my angular project.
My goal is to clear few state properties on action dispatch. The array of property names are passed to the action.
// Action
export const clearFields = createAction(
'[SC Base Data] Clear Fields',
props<{ fields: string[] }>()
);
// Reducers
on(SCActions.clearFields, (state: SCState,fields: string[]) => ({
...state,
SCData: {
...state.SCData
}
})),
/
How can iterate over fields array and set the state properties value as blank
If by "blank" you mean null, I believe what you're looking for is something along these lines:
on(SCActions.clearFields, (state: SCState, fields: string[]) => ({
// create an Object whose keys are all elements of fields and every value is null
const clearedState = {};
fields.forEach(field => {
clearedState[field] = null;
});
// return new copy of state nulling all fields from fields array
return {
...state,
...clearedState
};
}))

Redux - Update store with same function from different files

being rather new to react.js + redux, I'm facing the following conundrum:
I have multiple files, which need to update the store in exactly the same way, based on the stores current state. Currently I simply copy-paste the same code (along with the needed mapStateToProps), which goes again DRY.
Similar to something like the below, where getData is an Ajax call living in the actions file and props.timeAttribute is coming from mapStateToProps:
props.getData(props.timeAttribute).then((newState) => {
console.log(newState)
})
Would a function like that go in the actions file? Can the current state be read from within that actions file? Or does one normally create some sort of helperFile.js in which a function like that lives and is being called from other files?
Thanks!
If your file is executing the same action, then yes, you would put the action creator in a separate file and export it. In theory, you can put state in an action by passing the state as a parameter, but the philosophy behind an action is that it announces to your application that SOMETHING HAPPENED (as denoted by the type property on the return value of the action function). The reducer function responsible for handling that type subsequently updates the state.
You can access the current state of the store inside of an action creator like this:
export const testAction = (someParam) => {
return (dispatch, getState) => {
const {
someState,
} = getState(); //getState gets the entire state of your application
//do something with someState and then run the dispatch function like this:
dispatch(() => {type: ACTION_TYPE, payload: updatedState})
}
I like this approach because it encapsulates all the logic for accessing state inside of the one function that will need to access it.
DO NOT modify the state inside of the action creator though! This should be read only. The state of your application should only be updated through your reducer functions.
Yes, it is recommended to maintain a separate file for your actions.
Below is an example of how i use an action to fetch information and dispatch an action.
export const fetchComments = () => (dispatch) => {
console.log("Fetch Comment invoked");
/*you can use your Ajax getData call instead of fetch.
Can also add parameters if you need */
return fetch(baseUrl + 'comments')
.then(response => {
if (response.ok){
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText);
error.response = response;
throw error;
}
},
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(comments => dispatch(addComments(comments)))
.catch(error => dispatch(commentsFailed(error.message)));
}
/* Maintain a separate file called ActionTypes.js where you can store all the ActionTypes as Strings. */
export const addComments = (comments) => ({
type : ActionTypes.ADD_COMMENTS,
payload : comments
});
export const comments = (errMess) => ({
type : ActionTypes.COMMENTS_FAILED,
payload : errMess
});
Once, you receive dispatch an action, you need an reducer to capture the action and make changes to your store.
Note that this reducer must be a pure function.
export const comments = (state = { errMess: null, comments:[]}, action) => {
console.log("inside comments");
switch (action.type) {
case ActionTypes.ADD_COMMENTS:
return {...state, errMess: null, comments: action.payload};
case ActionTypes.COMMENTS_FAILED:
return {...state, errMess: action.payload};
default:
return state;
}
};
Don't forget to combine the reducers in the configureStore().
const store = createStore(
combineReducers({
comments
}),
applyMiddleware(thunk,logger)
);
In your components where you use the Actions, use
const mapDispatchToProps = dispatch => ({
fetchComments : () => dispatch(fetchComments()),
})
Note to export the component as
export default connect(mapStateToProps,mapDispatchToProps)(Component);

Redux Thunk Firebase - fetch arrays

I'm building a react native app with redux, react-redux, redux-thunk, and using firebase as a backend.
My database is denormalized, so I've to fetch keys in a firebase reference and for each key I need to fetch data from another reference.
Using firebase .once('value') I got something like this:
const fetchPosts = ( uid ) => {
return ( dispatch ) => {
dispatch(postsIsFetching())
const userPostsRef = firebase.database().ref('users/' + uid + '/myposts')
var keys = []
//
userPostsRef.once('value').then(snap => {
snap.forEach(post => keys.push(post.key))
}).then(() => {
keys.forEach(key => {
const postRef = firebase.database().ref('posts/' + key )
postRef.once('value').then(snap => {
var newPost = {
title: snap.val().title,
user: snap.val().user
}
dispatch(setPost(newPost))
})
})
}
}
But that's not realtime updating, cause the .once() method read data once only.
The .on() method doesn't return a Promise, so how could I fix that?
What's the best pattern to fetch data with redux-thunk and a denormalized firebase database?
Thanks guys
Put the code you'd normally put in a .then() inside the .on() callback. This way, every time the data refreshes, your setPost action will be dispatched.
For example:
...
postRef.on('value', snap => {
var newPost = {
title: snap.val().title,
user: snap.val().user
}
dispatch(setPost(newPost))
});
You probably want to hold on to that database reference so that you can stop listening later.

Resources