Calling redux actions without bindActionCreator - react-native

I want to store the GPS position of the user in my redux-store. To get the coords I use this:
navigator.geolocation.watchPosition( (data) => {
//
}, null, {
timeout: 60000,
distanceFilter: 10
});
I have a reducer for the position:
import createReducer from '../lib/createReducer'
import * as types from '../actions/types'
export const position = createReducer({}, {
[types.SET_POSITION](state, action) {
return {
latitude: action.latitude,
longitude: action.longitude
};;
}
})
And an action:
import * as types from './types'
export function watchPosition() {
return (dispatch, getState) => {
???
}
}
export function setPosition({ latitude, longitude }) {
console.log('JA');
return {
type: types.SET_POSITION,
latitude,
longitude
}
}
I want to init this watchPosition in my Home-Screen. I don't bind the actions there (no connect() ).
How to call this action and init the reducer when new position is available?

You czn import your store object in your commponent or another executive place of code and then use store.dispatch(() => { return{ type: ACTION_TYPE, payload: data } }).
or you can use reduxThunk middleware to touch the store: store.dispatch({ type: ACTION_TYPE, payload: data }).
Hope this help you

Related

Asynchronous problem when dispatch the hook with Context API

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.

How to mock vue composable functions with jest

I'm using vue2 with composition Api, vuex and apollo client to request a graphql API and I have problems when mocking composable functions with jest
// store-service.ts
export function apolloQueryService(): {
// do some graphql stuff
return { result, loading, error };
}
// store-module.ts
import { apolloQueryService } from 'store-service'
export StoreModule {
state: ()=> ({
result: {}
}),
actions: {
fetchData({commit}) {
const { result, loading, error } = apolloQueryService()
commit('setState', result);
}
},
mutations: {
setState(state, result): {
state.result = result
}
}
}
The Test:
// store-module.spec.ts
import { StoreModule } from store-module.ts
const store = StoreModule
describe('store-module.ts', () => {
beforeEach(() => {
jest.mock('store-service', () => ({
apolloQueryService: jest.fn().mockReturnValue({
result: { value: 'foo' }, loading: false, error: {}
})
}))
})
test('action', async ()=> {
const commit = jest.fn();
await store.actions.fetchData({ commit });
expect(commit).toHaveBeenCalledWith('setData', { value: 'foo' });
})
}
The test fails, because the commit gets called with ('setData', { value: undefined }) which is the result from the original apolloQueryService. My Mock doesn't seem to work. Am I doing something wrong? Appreciate any help, thanks!
Try this :
// store-module.spec.ts
import { StoreModule } from store-module.ts
// first mock the module. use the absolute path to store-service.ts from the project root
jest.mock('store-service');
// then you import the mocked module.
import { apolloQueryService } from 'store-service';
// finally, you add the mock return values for the mock module
apolloQueryService.mockReturnValue({
result: { value: 'foo' }, loading: false, error: {}
});
/* if the import order above creates a problem for you,
you can extract the first step (jest.mock) to an external setup file.
You should do this if you are supposed to mock it in all tests anyway.
https://jestjs.io/docs/configuration#setupfiles-array */
const store = StoreModule
describe('store-module.ts', () => {
test('action', async ()=> {
const commit = jest.fn();
await store.actions.fetchData({ commit });
expect(commit).toHaveBeenCalledWith('setData', { value: 'foo' });
})
}

Redux reducer state returns an empty Object in component

I am fairly new to React Native and Redux. I am fetching data on Restaurants from an API route using axios,and my goal is to display the Restaurants, but when I console.log in the components I get an empty object {}.
Here is my data sample:
[{
"id_supplier":1,
"supplier_name":"Grill Park",
"supplier_description":"Order food at Grill-Park. Masters of Italian Pizza.",
"supplier_image":"restaurant1.jpg",
"createdAt":"2020-08-28T21:00:00.000Z",
"updatedAt":"0000-00-00"
},
{
"id_supplier":2,
"supplier_name":"Bobbies burger",
"supplier_description":"Order a burger on the go. Beef, Cheese, Hamburger, Vegan Burgers",
"supplier_image":"restaurant2.jpg",
"createdAt":"2020-08-28T21:00:00.000Z",
"updatedAt":"0000-00-00"
},
{
"id_supplier":3,
"supplier_name":"Chicken n licken",
"supplier_description":"Renowned for serving tasty fried chicken pieces and a wide range of other menu items.",
"supplier_image":"restaurant3.jpg",
"createdAt":"2020-08-28T21:00:00.000Z",
"updatedAt":"0000-00-00"
},
{
"id_supplier":4,
"supplier_name":"French House Tarvern",
"supplier_description":"Grab a sandwich at our Pick 'n' Mix Sandwich hip and fun environment",
"supplier_image":"restaurant4.jpg",
"createdAt":"2020-08-28T21:00:00.000Z",
"updatedAt":"0000-00-00"
}]
Here is my model:
export interface RestaurantModel {
id_supplier: number;
supplier_name: string;
supplier_description: string;
supplier_image: string;
createdAt: Date;
updatedAt: Date;
}
export interface RestaurantState{
restaurants_avail: RestaurantModel;
//add others
}
My action file looks as follows
export const getRestaurants = ()=>{
return async(dispatch : Dispatch<RestaurantAction>) =>{
try{
const response = await axios.get<RestaurantModel>(`${BASE_URL}/restaurants`)
console.log("respond now", response.data)//This returns an array of the restaurants
if(response){
dispatch({
type: 'ON_GET_RESTAURANTS',
payload: response.data
})
} else {
dispatch({
type: 'ON_RESTAURANT_ERROR',
payload: 'RESTAURANTS UNAVAILABLE'
})
}
}catch(error){
dispatch({
type: 'ON_RESTAURANT_ERROR',
payload: error
})
}
}
}
When I console log the value of of the response.data an array of the restaurants is returned.
My reducer code looks as follows
const initialState: RestaurantState = {
restaurants_avail: {} as RestaurantModel,
}
const RestaurantReducer = (state: RestaurantState = initialState, action: RestaurantAction) =>{
switch(action.type){
case 'ON_GET_RESTAURANTS':
console.log(action.payload)//this returns the updated state as expected
return{
...state,
restaurants_avail: action.payload
}
default:
return state
}
}
I have all my reducers combined in rootreducer then my store looks as follows.
const store = createStore(rootReducer,composeWithDevTools(
applyMiddleware(thunk))
)
console.log("Value of store state", store.getState())
export {store};
store.getState() returns empty objects.
Here is how I intend to print out the array in my Component.
interface HomeProps {
restaurantReducer: RestaurantState;
getRestaurants: Function;
}
const _HomeScreen: React.FC<HomeProps> = (props) => {
const { restaurants_avail } = props.restaurantReducer;
console.log("restaurants array",restaurants_avail)//This returns an empty object
useEffect(() => {
console.log(props.getRestaurants())//This return an array object with all the restaurants
})
I don't understand where am going wrong as console.log(restaurants_avail) should print out the array, but it instead returns an empty {}
Here is the React Native Debugger state chart.

Cannot read property 'xxx.xxx' of undefined

Upgrading meteor (from 1.4 to 1.7) and react (from 15.3.2 to 16.8.6). Using Meteor Atmosphere.
At one part of the code whereby to process a delete instruction, the console having the following familiar but clueless error:
Uncaught TypeError: Cannot read property 'displayConfirmation' of undefined
at remove (ticket.js:48)
at onClick (list.jsx:180)
at HTMLUnknownElement.callCallback (modules.js?hash=199fa8ade393a4d3c92b5b590836441c4936d1d6:54371)
at Object.invokeGuardedCallbackDev (modules.js?hash=199fa8ade393a4d3c92b5b590836441c4936d1d6:54420)
at invokeGuardedCallback (modules.js?hash=199fa8ade393a4d3c92b5b590836441c4936d1d6:54475)
at invokeGuardedCallbackAndCatchFirstError (modules.js?hash=199fa8ade393a4d3c92b5b590836441c4936d1d6:54489)
at executeDispatch (modules.js?hash=199fa8ade393a4d3c92b5b590836441c4936d1d6:54572)
at executeDispatchesInOrder (modules.js?hash=199fa8ade393a4d3c92b5b590836441c4936d1d6:54597)
at executeDispatchesAndRelease (modules.js?hash=199fa8ade393a4d3c92b5b590836441c4936d1d6:57461)
at executeDispatchesAndReleaseTopLevel (modules.js?hash=199fa8ade393a4d3c92b5b590836441c4936d1d6:57470)
Expectation: Pop up of a dialog box asking for confirmation before delete.
Below are part of the codes:
components/list.jsx
...
onClick={() => actions.delete && remove()}><img src={require('/crm/images/icon_delete.png')}/> Delete all selected</span>
...
actions/ticket.js
import * as React from 'react';
import { push, goBack } from 'react-router-redux';
import { reset, SubmissionError } from 'redux-form';
import { notify, confirm } from '../../core/actions';
import {Tickets} from '../../../../lib/collections';
import {
SELECT_TICKETS, UNSELECT_TICKETS, CHANGE_TICKETS_PAGE, SORT_TICKETS,
LOAD_TICKET, UNLOAD_TICKET,
LOAD_ACTIVITIES, UNLOAD_ACTIVITIES,
CHANGE_CATEGORY, CHANGE_STATUS, CHANGE_DATE,
} from './actionTypes';
export default {
remove(context) {
const {Meteor, Store} = context;
let tickets = Store.getState().tickets.list.selectedTickets;
confirm.displayConfirmation(context, { // <-- It can't seem to recognize this
title: 'Removing Tickets',
message: "<p>Are you sure you want to delete below tickets?<ul>{tickets.map((ticket, i) => <li key={'msg-' + i}>{ticket.ticketNo}</li>)}</ul></p>",
callback: () => {
Meteor.call('tickets.delete', tickets.map(ticket => ticket._id), (err) => {
if (err) {
return;
}
notify.sendNotify(context, `${tickets.map(ticket => ticket.ticketNo).join(', ')} ${tickets.length > 1 ? 'have' : 'has'} been deleted.`);
unselect(context);
});
}
});
},
};
../../core/actions/index.js
import notify from './notify';
import confirm from './confirm';
export default {
notify,
confirm
};
../../core/actions/confirm.js
let dismissConfirmation = ({Store}) => {
Store.dispatch({
type: DISMISS_CONFIRMATION
});
};
export default {
displayConfirmation({Store}, {title, message, callback}) {
Store.dispatch({
type: DISPLAY_CONFIRMATION,
title,
message,
callback
});
},
dismissConfirmation,
confirm(context) {
let {Store} = context;
Store.getState().confirm.callback();
dismissConfirmation(context);
}
};
Any help mostly appreciated!
-- EDIT --
Have tried to change confirm.js to:
../../core/actions/confirm.js
export const dismissConfirmation = ({Store}) => {
Store.dispatch({
type: DISMISS_CONFIRMATION
});
};
export const displayConfirmation = ({Store}, {title, message, callback}) => {
Store.dispatch({
type: DISPLAY_CONFIRMATION,
title,
message,
callback
});
};
export const confirm = (context) => {
let {Store} = context;
Store.getState().confirm.callback();
dismissConfirmation(context);
};
But still getting the same undefined error.
If I tried to change confirm.displayConfirmation to displayConfirmation at actions/ticket.js, will then get the following error:
Uncaught TypeError: displayConfirmation is not a function
Change you confirm.js to this.
export const dismissConfirmation = ({Store}) => {
Store.dispatch({
type: DISMISS_CONFIRMATION
});
};
export const displayConfirmation({Store}, {title, message, callback}) {
Store.dispatch({
type: DISPLAY_CONFIRMATION,
title,
message,
callback
});
},
export const confirm(context) {
let {Store} = context;
Store.getState().confirm.callback();
dismissConfirmation(context);
}
Now you can import these functions in other files like this
import {
displayConfirmation,
confirm,
dismissConfirmation
} from '../../core/actions';
You are mixing the concept of named exports with default export please read this article Named exports vs default exports
As suggested by #mzparacha, below are the final changes.
../../core/actions/confirm.js
export const dismissConfirmation = ({Store}) => {
Store.dispatch({
type: DISMISS_CONFIRMATION
});
};
export const displayConfirmation = ({Store}, {title, message, callback}) => {
Store.dispatch({
type: DISPLAY_CONFIRMATION,
title,
message,
callback
});
};
export const confirm = (context) => {
let {Store} = context;
Store.getState().confirm.callback();
dismissConfirmation(context);
};
However on the import part, did it as below instead:
actions/ticket.js
import * as confirm from '../../core/actions/confirm';
...
And the rest remains. Works like charm. Thank you #mzparacha

ngrx store state undefined

I am not sure why my state in my store is undefined when I try to access it. I have been looking at this for sometime now and I cannot figure it out.
my actions are
export const GetMerchants = createAction('[Merchant] - Get Merchants');
export const GetMerchantsSuccess = createAction(
'[Merchant] - Get Merchants Success',
props<{ payload: Merchant[] }>()
);
export const GetMerchantsFailure = createAction(
'[Merchant] - Get Merchants Failure',
props<{ payload: Error }>()
);
My reducers and state def are
export default class MerchantListState {
merchants: Array<Merchant>;
merchantError: Error;
}
export const initializeMerchantListState = (): MerchantListState => {
return {
merchants: new Array<Merchant>(),
merchantError: null
};
};
export const intialMerchantListState = initializeMerchantListState();
const _reducer = createReducer(
intialMerchantListState,
on(actions.GetMerchants, (state: MerchantListState) => {
return {
...state
};
}),
on(actions.GetMerchantsSuccess, (state: MerchantListState, { payload }) => {
let newstate = { ...state,
merchants: [ ...state.merchants, payload],
merchantError: null
};
return newstate;
}),
on(actions.GetMerchantsFailure, (state: MerchantListState, { payload }) => {
console.log(payload);
return { ...state, merchantError: payload };
}),
);
export function merchantListReducer(state: MerchantListState, action: Action) {
return _reducer(state, action);
}
My effects
#Injectable()
export class MerchantListEffects {
constructor(private apiService: ApiService, private apiRouteService: ApiRouteService, private action$: Actions) { }
GetMerchants$: Observable<Action> = createEffect(() =>
this.action$.pipe(
ofType(actions.GetMerchants),
mergeMap(action => this.apiService.get(this.apiRouteService.toMerchants()).pipe(
map((data: Merchant[]) => { console.log(data); return actions.GetMerchantsSuccess({ payload: data }); }
), catchError((error: Error) => { return of(actions.GetMerchantsFailure({ payload: error })) })
)
)));
}
When I inject the state into the component
private store: Store<{ merchantList: MerchantListState }>
I get an undefined merchant$ observable when I try to do this
this.merchants$ = store.pipe(select('merchantList'));
this.merchantSubscription = this.merchants$.pipe(
map(x => {
console.log(x.merchants);
})
)
.subscribe();
On a button click I am loading the merchants with this dispatch
this.store.dispatch(actions.GetMerchants());
I have my reducer and effects defined in AppModule
StoreModule.forRoot({ merchantList: merchantListReducer }),
EffectsModule.forRoot([MerchantListEffects])
Is it something that I am missing?
First Parameter of createReducer is a value, not a function.
API > #ngrx/store
createReducer
If you use a function, you have to call it:
const _reducer = createReducer(
intialMerchantListState()
I prefare the way to define direct a value initialState:
export const initializeMerchantListState: MerchantListState = {
merchants: new Array<Merchant>(),
merchantError: null
};