Unable to get payload from action in reducer inside react native app? - react-native

I am trying to get a json response from an api and get the data successfully but when I call a action within another action using redux-thunk, my data is not available inside the reducer. I need data in "data" property inside my reducer get in component. Check the code below.
This is my action
import { GET_REPORT, GET_DATA_SUCCESS } from './types';
export const getData = (text) => {
return (dispatch) => {
dispatch ({ type: GET_REPORT})
fetch('http://api.openweathermap.org/data/2.5/forecast?q='+text+'&cnt=1&units=metric&APPID={key}')
.then(response => response.json())
.then(data => getDataSuccess(dispatch, data.list[0]))
.catch((error) => console.log(error));
};
};
const getDataSuccess = (dispatch, data) => {
//console.log(data);
dispatch({
type: GET_DATA_SUCCESS,
payload: data
});
}
this is my reducer
import { GET_REPORT } from'../actions/types';
const INITIAL_STATE = {
data: '',
}
export default (state = INITIAL_STATE, action) => {
switch(action.type){
case GET_REPORT:
console.log(action.payload); // getting undefined
return {...state, data: action.payload};
default:
return state;
}
}
I need data in "data" property get in component.

you are missing GET_DATA_SUCCESS in your reducer

The action dispatch ({ type: GET_REPORT}) , doesn't contain a payload hence undefined. Either you need to make reducer to handle action GET_DATA_SUCCESS or modify the existing one.
To simplify, dispatch({
type: GET_DATA_SUCCESS,
payload: data
}); contains a payload whereas dispatch ({ type: GET_REPORT}) doesn't

Resolved it by adding new switch case for GET_DATA_SUCCESS and get the payload from getDataSuccess and removing the payload from GET_REPORT case.
Now switch case looks like this
switch(action.type){
case GET_REPORT:
return {...state};
case GET_DATA_SUCCESS:
console.log(action.payload);
return{...state, data: action.payload}
default:
return state;
}

Related

Why am I getting "redux-persist/createPersistoid: error serializing state"?

I have no idea why I'm getting this error. there's no file referenced no line referenced. I only know the action thats executed which results in the error.
The full error:
console.error "redux-persist/createPersistoid: error serializing
state", TypeError: JSON.stringify cannot serialize cyclic structures.
Here is the action that causes the error:
export const loadInitialDiscover = () => {
return (dispatch) => {
dispatch({ type: types.DISCOVER_LOADING });
return getNewest(dispatch);
};
};
const getNewest = (dispatch) => {
return firebase
.firestore()
.collection('users')
.where('role', '==', 'a')
.where('active', '==', true)
.orderBy('createdAt')
.limit(10)
.get()
.then((querySnapshot) => {
const newest = [];
querySnapshot.forEach((queryDocSnapshot) => {
const profile = queryDocSnapshot.data();
newest.push(profile);
});
dispatch({ type: types.LOAD_NEWEST, payload: newest });
})
.catch((err) => loadFail(dispatch, err.message));
};
implementation of action in screen:
componentDidMount () {
//console.log('INITIAL REDUX STATE: ' + JSON.stringify(this.props.newest));
this.props.newest == null ? this.props.loadInitialDiscover() : null;
}
notice this .stringify() is commented out
Ive removed literally any and every existing JSON.stringify, deleted build folder, restarted bundler, restarted simulator. the error persists.
here is relevant reducer:
const INITIAL_STATE = {
newest: null,
loading: false,
error: ''
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.DISCOVER_LOADING:
return {
...state,
loading: true,
error: ''
};
case types.LOAD_FAIL:
return {
...state,
loading: false,
error: action.info
};
case types.LOAD_NEWEST:
return {
...state,
loading: false,
error: '',
newest: action.payload
};
}
}
and here's the persist config:
const persistAuthConfig = {
key: 'auth',
storage: AsyncStorage
};
const persistUserConfig = {
key: 'user',
storage: AsyncStorage
};
const persistDiscoverConfig = {
key: 'discover',
storage: AsyncStorage
};
const AppReducer = combineReducers({
auth: persistReducer(persistAuthConfig, AuthReducer),
user: persistReducer(persistUserConfig, UserReducer),
discover: persistReducer(persistDiscoverConfig, DiscoverReducer)
});
export default AppReducer;
How do I get this error gone?
The Firestore query response contains cyclic data (ref parameter) that is not possible to serialised by JSON.stringify hence the error from Redux-Persist, (yes; redux-persist use it under the hood). Just remove the ref parameter containing the cyclic data from all the objects inserted into the array newest array.
Also, do a check on other parameters that cannot be serialised by JSON.stringify like ref.
The below may help you to achieve this:-
const newest = []; querySnapshot.forEach((queryDocSnapshot) => {
const profile = queryDocSnapshot.data();
const {ref, ...profileSerialisable} = profile;
newest.push(profileSerialisable);
});
Please refer here for more discussions based on this.

Make two actions work simultaneously - react native and redux

I've a button that sends two actions. First one adds the user infos in an array if certain condition is met and 2nd one sends the data to the server.
Since both actions are in onPress function, the 2nd action doesn't wait till it adds up the infos in an array. Henceforth, it always sends empty array.
How can I make this two actions work simultaneously.
<TouchableOpacity
onPress={() => {
if (true) {
this.props.AuthUserInfoGet(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo); //calculates & return SignUpUsers
}
this.props.SignUpCheck(SignUpUsers); //upload SignUpUsers but SignUpCheck is always empty here
}}
>
<Text>Upload</Text>
</TouchableOpacity>
const mapStateToProps = (state) => {
const {SignUpUsers} = state.Auth;
//it gives an empty array first and then expected value
console.log('SignUpUsersz', SignUpUsers);
return {SignUpUsers};
};
Action:
export const AuthUserInfoGet = (SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo) => {
return ({
type: SIGN_UP_USER_INFO_GET,
payloadName: SignUpName,
payloadDesignation: SignUpDesignation,
payloadEmail: SignUpEmail,
payloadMobile: SignUpMobileNo,
});
}
export const SignUpCheck = (userInfo) => {
console.log('userInfo', userInfo); // userInfo is always empty
}
Reducer:
const INITIAL_STATE = { SignUpUsers: [] }
case SIGN_UP_USER_INFO_GET:
return { ...state, SignUpUsers: [...state.SignUpUsers, {member_name: actions.payloadName, designation: actions.payloadDesignation,
email: actions.payloadEmail, mobile_number: actions.payloadMobile}] };
Given your current Redux-structure, I think what makes the most sense to use the componentDidUpdate life-cycle method.
The main reason is because your component ultimately needs to get updated data from Redux via props and needs to re-render. When you execute the first action, that user-data coming from the API is not immediately available in the current call-stack, so you'll always be passing an empty array (given your initial value of SignUpUsers: [])
Note that most React-Redux flows follow this path:
User-Event -> Action-Creator -> API (Data) -> Redux -> Component
Your click-event is at step 1 and triggers this action: this.props.AuthUserInfoGet(...args)
But React/Redux needs to go through that entire flow before you can use the new data.
This is where the componentDidUpdate() event comes in-handy because you can write logic when the component is re-rendered by new props or state.
Something like this would totally work:
componentDidUpdate(prevProps){
if(prevProps.SignUpUsers.length !== this.props.SignUpUsers.length){
//execute action
this.props.SignUpCheck(this.props.SignUpUsers)
}
}
For that I would suggest you take a look at redux-thunk middleware.
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
And based on your example, the code will end up like this:
<TouchableOpacity
onPress={() => this.props.uploadSignUpUsers(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo)}>
<Text>Upload</Text>
</TouchableOpacity>
const mapStateToProps = (state) => {
const { Auth: { SignUpUsers } } = state;
return { SignUpUsers };
}
Actions:
export const SIGN_UP_GET_USER_INFO_SUCCESS = "SIGN_UP_GET_USER_INFO_SUCCESS";
export const SIGN_UP_UPLOAD_SUCCESS = "SIGN_UP_UPLOAD_SUCCESS";
export const uploadSignUpUsers = (SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo) => {
return async (dispatch, getState) => {
// here you can make the api call or any other async calculations
const { data: AuthUserInfo, error } = await api.post(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo);
dispatch({
type: SIGN_UP_GET_USER_INFO_SUCCESS,
payloadName: AuthUserInfo.SignUpName,
payloadDesignation: AuthUserInfo.SignUpDesignation,
payloadEmail: AuthUserInfo.SignUpEmail,
payloadMobile: AuthUserInfo.SignUpMobileNo,
});
const { Auth: { SignUpUsers } } = getState()
// and now you can upload your SignUpUsers
const { data: uploadData, error } = await.api.post(SignUpUsers)
dispatch({
type: SIGN_UP_UPLOAD_SUCCESS,
...uploadData // spread upload data to make it available in reducers
});
}
}
Reducer:
const INITIAL_STATE = { SignUpUsers: [] }
case SIGN_UP_GET_USER_INFO_SUCCESS: {
const { payloadName, payloadDesignation, payloadEmail, payloadMobile } = actions
return {
...state,
SignUpUsers: [ ...state.SignUpUsers, {
member_name: payloadName,
designation: payloadDesignation,
email: payloadEmail,
mobile_number: payloadMobile
}]
}
}

What is the best practice for updating the local variables in redux-thunk after getting the response from api?

I have implemented the redux-thunk which works just fine in my react-native application. I have some 'this.counterValue', which value must be updated after getting the response from the api. As api fetch methods are implemented in another actions files, and response is achieved in that file. So, how must it be implemented to make this work fine.I don't want the change in 'this.counterValue' results in re-render of my application. I am new to react native, it would be great to be helped. Thanks.
Component file:
this.counterValue = 75; //local variable
this.props.fetchData('onStart'); // call to fetch data from actions files
Action file:
export const fetchData = (fetchType) => {
return async dispatch => {
dispatch(fetchingDataRequest());
fetch(AllApi),
{
method: 'GET',
headers:
{
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer '+ global.apiToken,
},
})
.then(response => {
return response.json()
})
.then(RetrivedData => {
dispatch(fetchingDataSuccess(RetrivedData.data));
})
.catch(error => {
dispatch(fetchingNotificationFailure(error));
});
}
}
using dispatch send the data to the reducer and in reducer update the state value which then you could use it in the component.
Reducer
import { reducerWithInitialState } from 'typescript-fsa-reducers'
import { DemoListViewActions } from './Actions'
export interface DemoListViewState {
data: any[]
selectedData: any
}
const initialState: DemoListViewState = {
data: [],
selectedData: undefined
}
const dataListRequestSuccessHandler = (state: DemoListViewState, payload: any[]): DemoListViewState => {
return {
...state,
data: payload
}
}
export const DemoListViewReducer = reducerWithInitialState(initialState)
.case(DemoListViewActions.dataListRequestSuccess, dataListRequestSuccessHandler)
.build()
Container
const mapStateToProps: MapStateToProps<IDemoListViewStateProps, OwnProps, RootState> = (state: RootState, ownProps: OwnProps) => {
const {data} = state.demoListView
return {
listData: data
}
}
Component
export interface IDemoListViewStateProps {
listData: any[]
}
just store it in props and state and you could manipulate it easily
After dispatching your action,you have to update your reducer
Import Action from './Action'
const initialState = {
people :[ ] // initiate empty array
}
export default function(state=initialState,action){
switch(action.type){
case Action.test:
return testUpdate(state,action)
default :
return state
}
}
//Update the result from the api,people array will be populated
with latest data
function testUpdate(state,action){
return {...state,people:action.payload.people}
}

How to fetch data from API based on params in the URL

I'm building an app in React Native fetching data from an API.
The API data is information about flights departures/arrivals.
What I'm trying to do is very simple, I want that the app has 2 tabs which switch screens between Arrivals and Departure. The 2 screens will show all the flights departures or arrivals.
At the moment I did the app only to show the departures flights and getting data from the URL like URL/flights/departures. What I cannot understand as I'm new in Native is how can I fetch data based on the URL params. What I mean is that I have the API URL and I would like to fetch the data if it is departures or arrivals adding to the URL like API/flights/{flightType} so when a screen changes the correct data is fetched. What I did for now is below but only for departures and I would like to understand how to change it to do as I need.
import axios from 'axios';
import { apiBaseURL } from "../Utils/Constants";
import {
FETCHING_FLIGHTS_DATA
,FETCHING_FLIGHTS_DATA_SUCCESS,
FETCHING_FLIGHTS_DATA_FAIL
} from "../Utils/ActionTypes";
export default function FetchFlightData() {
return dispatch => {
dispatch({ type: FETCHING_FLIGHTS_DATA});
return axios.get(`${apiBaseURL}/departures`)
.then(res => {
dispatch({ type: FETCHING_FLIGHTS_DATA_SUCCESS, payload: res.data})
})
.catch(err => {
dispatch({ type: FETCHING_FLIGHTS_DATA_FAIL, payload: err.data})
})
}
}
You can pass the flight type as a parameter to your function and when switching the tab call the FetchFlightData with the right type. You probably should separate the fetch success for each type or add the flight type as a parameter to your payload and then in the reducer make the difference between data of departures or data of arrivals
import axios from 'axios';
import { apiBaseURL } from "../Utils/Constants";
import {
FETCHING_FLIGHTS_DATA,
FETCHING_FLIGHTS_DATA_SUCCESS,
FETCHING_FLIGHTS_DATA_FAIL
} from "../Utils/ActionTypes";
export default function FetchFlightData(flightType) {
return dispatch => {
dispatch({ type: FETCHING_FLIGHTS_DATA});
return axios.get(`${apiBaseURL}/${flightType}`)
.then(res => {
dispatch({ type: FETCHING_FLIGHTS_DATA_SUCCESS, payload: {data:res.data,type:flightType}})
})
.catch(err => {
dispatch({ type: FETCHING_FLIGHTS_DATA_FAIL, payload: err.data})
})
}
}
//somewhere in the reducer
case FETCHING_FLIGHTS_DATA_SUCCESS:
return {
...state,
arrivals: action.payload.type === 'arrival' ? action.payload.data: state.arrivals,
departures: action.payload.type === 'departures' ? action.payload.data:state.departures
}
break;
// somewhere in your code when you detect a change of tab
FetchFlightData('arrivals');
FetchFlightData('departures');

Using dispatched actions

I have this on the bottom of my main file:
export default connect(state => ({
state: state.firebaseReducer
}),
(dispatch) => ({
actions: bindActionCreators(firebaseActions, dispatch)
})
)(Routing);
And this is my reducer that I imported
const initialState = null;
export function firebaseRef(state = initialState, action) {
switch (action.type) {
case 'FIREBASE_REF_SET':
return action.value;
default:
return state;
}
}
Action:
export function setFirebaseRef(ref) {
return {
type: 'FIREBASE_REF_SET',
value: ref
};
}
But when I try to do this in my main file:
componentDidMount() {
state.setFirebaseRef(ref);
}
It says "Can't find variable: state" . Am I calling it the wrong way? I want to be able to call the setFireBaseRef action.
connect injects parts of state and dispatch into props. You want to dispatch the action using:
this.props.dispatch(setFireBaseRef(ref))
Or, assuming firebaseActionscontains setFireBaseRef, you can just do:
this.props.actions.setFireBaseRef(ref)