The layout of my data in Firestore:
User documents contain an array of id references to chat documents.
Ex:
collections
/users
'CQATa3f3d2XmBskmmt8hmCJhzcJ2'
name: '...'
email: '...'
chats: ["RuUKEwsGtR9QylicdgJW", "JlzcIfkZ1KzeXClhJvOE"]
/chats
...
For the current user, I want to get the chat object associated with each element in its chat array. This data should go to firestore.ordered so I can supply it to a FlatList, but firestore.ordered.chats is always undefined, even though firestore.data.chats has the correct data.
I'm not sure if this is an issue having to do with the order in which I call connect and firestoreConnect or how I am using populates, or something else.
... Component ...
const populates = [
{ child: 'chats', root: 'chats' }
]
const mapStateToProps = (state, props) => {
return {
auth: state.firebase.auth,
user: populate(state.firestore, 'users', populates),
chats: state.firestore.ordered.chats,
}
}
// ---- TEST ----
export default compose(
connect(mapStateToProps, mapDispatchToProps),
firestoreConnect((props) => {
return [ { collection: 'users', doc: props.auth.uid, populates } ]
}),
)(Main)
Result of logging state inside of mapStateToProps:
I ended up just getting the array of chats from the result of the populate call.
const populates = [
{ child: 'chats', root: 'chats', keyProp: 'subjectName', queryParams: ['orderByKey'] }
]
const mapStateToProps = (state, props) => {
const populatedUser = populate(state.firestore, 'users', populates)
let chats = null
if(populatedUser){
chats = Object.values(populatedUser[state.firebase.auth.uid].chats)
}
return {
auth: state.firebase.auth,
user: populatedUser,
chats: chats
}
}
// ---- TEST ----
export default compose(
connect(mapStateToProps, mapDispatchToProps),
firestoreConnect((props) => {
return [ { collection: 'users', doc: props.auth.uid, populates } ]
}),
)(Main)
Related
I have a Pinia auth module, auth.js
I have the following code in it:
export const useAuthStore = defineStore('auth', {
state: () => ({
token: null,
is_logged: false,
user: {
default_landing_page: {},
},
actions: [],
}),
getters: {},
actions: {
async login(formData) {
const { data } = await api.post('login', formData);
this.token = data.access_token;
this.is_logged = data.auth;
this.actions = data.user.meta_actions;
},
},
});
Then for example, I get this.actions as
['can_view_thing', 'can_edit_thing', 'can_delete_thing']
This makes it so that I can have code such as:
import { useAuthStore } from '#/store/auth';
const auth = useAuthStore();
...
<button v-if="auth.actions.includes('can_edit_thing')">Edit Thing</button>
That works and is perfectly reactive if permissions are added or removed from the auth store actions array. The problem is I want to change it so it's a function, such as:
// pinia auth store
// introduce roles
this.roles = [{ id: 1, key: 'admin' }, { id: 2, key: 'manager' }]
...
getters: {
hasAuthorization() {
return (permission) => {
// if some condition is true, give permission
if (this.roles.some(role => role.key === 'admin') return true;
// else check if permissions array has the permission
return this.permissions.includes(permission);
// also permission could be an array of permissions and check if
// return permissions.every(permission => this.permissions.includes(permission))
};
},
},
<button v-if="hasAuthorization('can_edit_thing')">Edit Thing</button>
I researched it before and you can make a getter than returns a function which allows you to pass in a parameter. I was trying to make it reactive so that if this.actions changed, then it would re-run the getter, but it doesn't.
Is there some way I can achieve a reactive function in Pinia?
Here's an example of what I don't want:
<button v-if="auth.actions.includes('can_edit_thing') || auth.roles.some(role => role.key === 'admin')">Edit Thing</button>
I want to arrive at something like:
// preferably in the pinia store
const hasAuthorization = ({ type = 'all', permissions }) => {
const superAdminRoles = ['arbitrary', 'admin', 'superadmin', 'customer-service'];
if (auth.roles.some(role => superAdminRoles.includes(role.key)) return true;
switch (type) {
case 'any': {
return permissions.some(permission => auth.actions.includes(permission));
}
case 'all': {
return permissions.every(permission => auth.actions.includes(permission));
}
}
};
<button
v-if="auth.hasAuthorization({ type: 'all', permissions: ['can_edit_thing', 'can_edit_business'] })"
>Edit Thing</button>
I don't want to create a computed prop in 100 components that causes each to reactively update when pinia state changes.
Is there any way to do this reactively so that anytime auth.actions or auth.roles changes, the function will re-run with parameters?
Ok. so I do have this image object I get from my DB and store it into a vuex store:
// State
const state = {
userImages: [],
};
// Getters
const getters = {
getImages: (state) => {
return state.userImages;
},
getFilePathExists: (state) => (fullPath) => {
return !!state.userImages.find((item) => item.fullPath === fullPath);
},
getFileNameExists: (state) => (name) => {
return !!state.userImages.find((item) => item.name === name);
},
getImagesInFolder: (state) => (folder) => {
if(folder) {
return state.userImages.filter(im => {
if(im.folders) {
return im.folders.includes(folder)
}
return false
})
}
return state.userImages
}
};
// Mutations
const mutations = {
setImages(state, val) {
state.userImages = val;
},
};
export default {
state,
getters,
// actions,
mutations,
};
So far so good. Now if I use the '''getImages''' getter, the Object gets to be loaded into a ref:
//...
const detailImage = shallowRef({});
const showImageDetails = (image) => {
if (!showDetails.value) {
showDetails.value = true;
}
activeImage.value = image.id
detailImage.value = image;
}
The JSON has a nested Object called exif_data. If I onsole.log this, I get: Proxy {Make: 'Apple', Flash: 'Flash did not fire. No strobe return detection fun…on present. No red-eye reduction mode or unknown.', Model: 'iPhone 6', FNumber: 2.2, COMPUTED: {…}, …}.
So far so good, but when I would like to access any property of the nested Object, either with or without using .value I just get undefined.
Any ideas?
I have my store using the Context API to create a user, where there are two screens for completing the registration.
The first screen has several input fields that I control with React Hook Form, and when I enter all the data, I navigate to the other screen and fill in 2 more inputs, one being a checkbox with several options.
On the first screen, I can get all the information from the user, and on the second screen, I can get only the values of the checked boxes, and the location input that I have, it cannot insert into the state of my Context API.
src/store/CreatePersonalAccount/index.tsx
import { createContext, useReducer } from 'react'
import { reducer } from './reducer'
export const initialState: PersonalAccount = {
avatar: '',
name: '',
email: '',
password: '',
location: '',
occupationCategory: '',
occupationSubcategories: []
}
export const CreatePersonalAccountContext = createContext<
IContext<PersonalAccount>
>({
state: initialState,
dispatch: () => {}
})
export const CreatePersonalAccountContextProvider: React.FC = ({
children
}) => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<CreatePersonalAccountContext.Provider value={{ state, dispatch }}>
{children}
</CreatePersonalAccountContext.Provider>
)
}
CreatePersonalAccount/action.ts
export enum CreatePersonalAccountActions {
SET_CREATE_PERSONAL_ACCOUNT = 'SET_CREATE_PERSONAL_ACCOUNT',
}
export const setPersonalAccount = (payload: Partial<PersonalAccount>) => {
return {
type: CreatePersonalAccountActions.SET_CREATE_PERSONAL_ACCOUNT,
payload
}
}
CreatePersonalAccount/reducer.ts
import { initialState } from './index'
import { CreatePersonalAccountActions as ActionTypes } from './action'
export const reducer = (state: PersonalAccount, action: IAction) => {
switch (action.type) {
case ActionTypes.SET_CREATE_PERSONAL_ACCOUNT:
return {
...state,
...action.payload
}
case ActionTypes.RESET:
return {
...initialState
}
default:
return state
}
}
In my user creation routes, I have hooks that can be used both on the first screen and on the second screen.
import { useContext } from 'react'
import { CreatePersonalAccountContext } from 'store/CreatePersonalAccount'
import {
setPersonalAccount,
resetPersonalAccount
} from 'store/CreatePersonalAccount/action'
export function usePersonalAccount() {
const { state, dispatch } = useContext(CreatePersonalAccountContext)
const setPersonalInformation = (state: PersonalAccountInformation) =>
dispatch(setPersonalAccount(state))
const setPersonalLocation = (location: string) =>
dispatch(setPersonalAccount({ location }))
const setPersonalOccupation = (
params: Partial<
Pick<PersonalAccount, 'occupationCategory' | 'occupationSubcategories'>
>
) => dispatch(setPersonalAccount(params))
const clearPersonalAccount = () => dispatch(resetPersonalAccount())
const submit = () => {
console.log('Enviar pra API', state)
}
return {
state,
submit,
setPersonalLocation,
clearPersonalAccount,
setPersonalOccupation,
setPersonalInformation
}
}
The setPersonalInformation function is used on the first screen with the user information, and the other functions setPersonalLocation and setPersonalOccupation are used on the second screen of the user information, the problem is the setPersonalLocation function that cannot update the state with the new location property.
This function below is the function I use in the first info screen, and it works correctly, updating the avatar, name, document, email, password fields.
SignUp/PersonalAccount/PersonalInformation/index.tsx
...
function handleRegisterPersonal(data: PersonalAccountInformation) {
const personalInformationData = {
...data,
avatar: image
}
setPersonalInformation(personalInformationData)
navigation.navigate('PersonalLocationAndOccupation')
}
...
And on the screen where I would like to update the location property is the function below.
SignUp/PersonalAccount/PersonalLocationAndOccupation
function registerPersonalAccountForm({ location }: PersonalAccountLocation) {
setPersonalLocation(location)
navigation.navigate('Welcome')
}
And my final state is this, but without the location property assigned
Object {
"avatar": "file:///Users/vagnerwentz/Library/Developer/CoreSimulator/Devices/BCB82E0B-A81E-495A-9917-80C315CEE735/data/Containers/Data/Application/FB8A6B2E-3F8F-4EBE-9AC0-128BBC80A84E/Library/Caches/ExponentExperienceData/%2540anonymous%252FPlugApp0800-fe3f8358-2821-44a3-a206-d0e1909b03e0/ImagePicker/FF615EB9-E385-4397-9198-E49FE6CD8CE4.jpg",
"documentation": "1234567890",
"email": "email#email.com",
"location": "",
"name": "John Doe",
"occupationCategory": "Developer",
"occupationSubcategories": Array [
Object {
"id": "frontend",
"title": "Front-end",
},
Object {
"id": "backend",
"title": "Back-end",
},
],
"password": "1234567890",
}
I've already tried to put it in the property inside the useForm, assign the context field, with my Context, but without success, I also tried to put a setTimeout to simulate an asynchronous call, and I didn't succeed either.
I have been trying since yesterday to add the new NgRX 8 to my Angular application. But I am stuck on one thing and I don't know what the problem is.
I have tried to build a very simple example for movies:
export class Movie {
public id: number;
public name: string;
constructor() {}
}
My actions are:
export const getAll = createAction('[Movies Page] Load Movies');
export const getAllSuccess = createAction('[Movies Page] Load Movies Success', props<{ payload: Movie[] }>());
export const getAllFail = createAction('[Movies Page] Load Movies Fail', props<{ error: any }>());
And my effect is:
#Injectable()
export class MovieEffects {
constructor(private actions$: Actions) {
}
loadMovies$ = createEffect(() =>
this.actions$.pipe(
ofType(getAll),
switchMap(() =>
of([movies_array]).pipe(
map(result => getAllSuccess({ payload: result })),
catchError(error => of(getAllFail({ error })))
)
)
)
);
}
I have added all of action cases to my reducer:
export interface AppState<T> {
list: T[];
isNull: boolean;
count: number;
type: StateType;
}
export const initialState: AppState<Movie> = {
type: StateType.Loading,
isNull: true,
count: 0,
list: new Array<Movie>()
};
const movieRed = createReducer(
initialState,
on(MovieActionTypes.getAll, state => ({
...state,
type: StateType.Loading
})),
on(MovieActionTypes.getAllSuccess, (state: AppState<Movie>, action) => ({
...state,
type: StateType.Loaded,
isNull: action.payload == null,
count: action.payload != null ? action.payload.length : 0,
list: action.payload
})),
on(MovieActionTypes.getAllFail, state => ({
...state,
type: StateType.Error
}))
);
export function movieReducer(state: AppState<Movie> = initialState, action) {
return movieRed(state, action);
}
And added the effects forRoot and reducers forRoot in my module.
Everything works fine. But when I am trying to read the state using:
this.movies$ = store.pipe(select('list'));
I result is always undefined and I see this message in my DevTool
I have no idea where the problem is. I have tried many solutions, none has worked for me.
Does anybody know why and how to fix it?
I have found the answer. I have missed defining the AppState.
export interface AppState {
movieReducer: ReducerState<Movie>;
}
and in the module:
StoreModule.forFeature('movieReducer', movieReducer),
I am trying to fetch data with apollo and then write it to realm. I have created a js file that I know works, because it has worked before. But, when I try to write to a particular model I get an error message. More details as follows:
Code (Not entire code) LocationQuery.js:
const realm = new Realm({ schema: [testBuilding1], schemaVersion: 1 });
let buildingTypeArray = [];
const temp = [];
class LocationQuery extends Component {
static get propTypes() {
return {
data: React.PropTypes.shape({
loading: React.PropTypes.bool,
error: React.PropTypes.object,
sites: React.PropTypes.array,
}).isRequired,
};
}
render() {
if (this.props.data.loading) {
return (null);
}
if (this.props.data.error) {
return (<Text>An unexpected error occurred</Text>);
}
if (this.props.data.sites) {
this.props.data.sites.map((value) => {
buildingTypeArray.push(value.locations);
});
buildingTypeArray.forEach((locationValues) => {
realm.write(() => {
realm.create('testBuilding1', {
building: '273',
});
});
});
}
return null;
}
}
const locationQueryCall = gql`
query locationQueryCall($id: String!){
sites(id: $id){
locations {
building
type
}
}
}`;
const ViewWithData = graphql(locationQueryCall, {
options: props => ({
variables: {
id: 'SCH1',
},
}),
})(LocationQuery);
export default connect(mapStateToProp)(ViewWithData);
The error I get is a big red screen that read:
console.error: "Error in observe.next.... blah blah blah"
The Model I am using:
export const testBuilding1 = {
name: 'testBuilding1',
properties: {
building: 'string',
},
};
The weird thing is that the code works when I use this model:
export const locationScene = {
name: 'locationScene',
properties: {
building: 'string',
},
};
I am calling LocationQuery.js in another piece of code passing it through at render.
Thank you in advance for the help!