React Native - expo background - unable to access redux store value? - javascript

I am building an app with react native and expo background task manager.
I am trying to access my redux store from a functional component and dispatching values, but I keep running into an issue where the value seems to change but then I can't seem to access the true value of the variable. Below is the flow:
Click tracking -> background fires -> person gets checkedin (submitted: true) -> next ping should see the value of true and checkout (submitted: false).
Instead I keep getting a value of false in my task manager file so the code keeps checking me in instead of going back and forth.
Below is my code:
App.js
import React, { Component } from 'react';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View, Button, Platform, Alert } from 'react-native';
import * as Location from "expo-location";
import { configureBgTasks } from './task';
import * as TaskManager from 'expo-task-manager';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { connect } from "react-redux";
import ourReducer from './store/reducer';
const store = createStore(ourReducer);
const TASK_FETCH_LOCATION_TEST = 'background-location-task';
class App extends Component {
state = {
submitted: false
}
async componentDidMount() {
const { status } = await Location.requestPermissionsAsync();
if (status === 'granted') {
console.log('location permissions are granted...')
}
}
stopBackgroundUpdate = async () => {
Alert.alert('TRACKING IS STOPPED');
//Location.stopLocationUpdatesAsync(TASK_FETCH_LOCATION_TEST)
//UNREGISTER TASK
//const TASK_FETCH_LOCATION_TEST = 'background-location-task_global';
TaskManager.unregisterTaskAsync(TASK_FETCH_LOCATION_TEST);
}
//REFERENCES TO STATE
autoTrackingCheckin = () => {
console.log('^^firing checkin')
this.setState({ submitted: true });
store.dispatch({ type: "SUBMITTED", value: true })
}
autoTrackingCheckout = () => {
console.log('^^firing checkout')
this.setState({ submitted: false });
store.dispatch({ type: "SUBMITTED", value: false })
}
executeBackground = async () => {
//START LOCATION TRACKING
const startBackgroundUpdate = async () => {
Alert.alert('TRACKING IS STARTED');
if(Platform.OS==='ios') {
await Location.startLocationUpdatesAsync(TASK_FETCH_LOCATION_TEST, {
accuracy: Location.Accuracy.BestForNavigation,
//timeInterval: 1000,
distanceInterval: 2, // minimum change (in meters) betweens updates
//deferredUpdatesInterval: 1000, // minimum interval (in milliseconds) between updates
// foregroundService is how you get the task to be updated as often as would be if the app was open
foregroundService: {
notificationTitle: 'Using your location for TESTING',
notificationBody: 'To turn off, go back to the app and toggle tracking.',
},
pausesUpdatesAutomatically: false,
});
} else {
await Location.startLocationUpdatesAsync(TASK_FETCH_LOCATION_TEST, {
accuracy: Location.Accuracy.BestForNavigation,
timeInterval: 1000,
//distanceInterval: 1, // minimum change (in meters) betweens updates
//deferredUpdatesInterval: 1000, // minimum interval (in milliseconds) between updates
// foregroundService is how you get the task to be updated as often as would be if the app was open
foregroundService: {
notificationTitle: 'Using your location for TESTING',
notificationBody: 'To turn off, go back to the app and toggle tracking.',
},
pausesUpdatesAutomatically: false,
});
}
}
//WHERE THE MAGIC IS SUPPOSED TO HAPPEN
try {
//REFERENCES FOR VARIABLES AND FUNCTIONS
const submitted = this.state.submitted
const autoCheckin = this.autoTrackingCheckin
const autoCheckout = this.autoTrackingCheckout
const reducerSubmitted = store.getState().reducer.submitted
console.log('THE VARIABLE BEING PASSED...',reducerSubmitted)
configureBgTasks({ autoCheckin, autoCheckout })
startBackgroundUpdate();
}
catch (error) {
console.log(error)
}
}
render() {
//console.log('***********APP.JS STATUS:', this.state.submitted);
console.log('***********REDUCER APP.JS STATUS:', store.getState().reducer.submitted);
return (
<Provider store={ store }>
<View style={styles.container}>
<Button
onPress={this.executeBackground}
title="START TRACKING"
/>
<Button
onPress={this.stopBackgroundUpdate}
title="STOP TRACKING"
/>
<StatusBar style="auto" />
</View>
</Provider>
);
}
}
const mapStateToProps = (state) => {
const { reducer } = state
return { reducer }
};
const mapDispachToProps = dispatch => {
return {
storeCheck: (y) => dispatch({ type: "SUBMITTED", value: y })
};
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'space-evenly',
},
});
Reducer.js
import { combineReducers } from 'redux';
const INITIAL_STATE = {
submitted: false
};
const ourReducer = (state = INITIAL_STATE, action) => {
const newState = { ...state };
switch (action.type) {
case "SUBMITTED":
return {
...state,
submitted: action.value
}
break;
case "STORE_USER_ID":
return {
...state,
userId: [action.value, action.value1, action.value2, action.value3],
}
break;
}
return newState;
};
export default combineReducers({
reducer: ourReducer,
});
task.js
import * as TaskManager from 'expo-task-manager';
import { connect } from "react-redux";
const TASK_FETCH_LOCATION_TEST = 'background-location-task';
import ourReducer from './store/reducer';
import { createStore } from 'redux';
const store = createStore(ourReducer);
export const configureBgTasks = ({ autoCheckin, autoCheckout }) => {
TaskManager.defineTask(TASK_FETCH_LOCATION_TEST, ({ data, error }) => {
if (error) {
// Error occurred - check `error.message` for more details.
return;
}
if (data) {
//get location data from background
const { locations } = data;
//let myStatus = store.getState().reducer.submitted
//console.log('****LOCATION PINGING... submitted IS NOW:', submitted);
console.log('****REDUCER LOCATION PINGING... submitted IS NOW:', store.getState().reducer.submitted);
if (store.getState().reducer.submitted === false) {
autoCheckin();
//store.dispatch({ type: "SUBMITTED", value: true })
console.log('****CHECKING YOU IN...');
} else if(store.getState().reducer.submitted === true) {
autoCheckout();
//store.dispatch({ type: "SUBMITTED", value: false })
console.log('*****CHECKING YOU OUT...')
}
}
})
}
const mapStateToProps = (state) => {
const { reducer } = state
return { reducer }
};
const mapDispachToProps = dispatch => {
return {
storeCheck: (y) => dispatch({ type: "SUBMITTED", value: y })
};
};
//export default connect(mapStateToProps, mapDispachToProps)(configureBgTasks);
//export default configureBgTasks;
store.subscribe(configureBgTasks)

Related

useSelector doesn't update value in Next.js

I have a problem, createAsyncThunk function makes request to server (axios) and then get data, after that extraReducers handle builder.addCase in it and makes state.value = action.payload, then console.log(state.value) writes value from server. Great! It works, but when I use useSelector it sees existing value from initialState but get value only when it was first time initialized (null or just []) not updated after dispatch in wrapper.getServerSIdeProps. Same with just reducers and function in it. It works change state (console.log write it) but useSelector doesn't give me updated value.
UPDATE:
If you have same issue.
Just give up, don't use next-redux-wrapper. Context Api.
Slice code
import { createAsyncThunk, createSlice, PayloadAction } from '#reduxjs/toolkit';
import axios from 'axios';
import { HYDRATE } from 'next-redux-wrapper';
// types
import { IInitialStateV1 } from '../types/store';
import { ITrack } from '../types/tracks/track';
export const fetchData = createAsyncThunk('main/fetchData', async (): Promise<ITrack[]> => {
const { data } = await axios.get<ITrack[]>('http://localhost:5000/track');
return data;
})
const initialState: IInitialStateV1 = {
pause: true,
currentTime: 0,
volume: 0,
duration: 0,
active: null,
tracks: [],
}
export const mainSlice = createSlice({
name: 'main',
initialState,
reducers: {
setPause(state, action: PayloadAction<boolean>) {
state.pause = action.payload;
},
setTime(state, action: PayloadAction<number>) {
state.currentTime = action.payload;
},
setVolume(state, action: PayloadAction<number>) {
state.volume = action.payload;
},
setDuration(state, action: PayloadAction<number>) {
state.duration = action.payload;
},
setActive(state, action: PayloadAction<ITrack>) {
state.active = action.payload;
state.currentTime = 0;
state.duration = 0;
}
},
extraReducers: (builder) => {
// [HYDRATE]: (state, action) => {
// return {
// ...state,
// ...action.payload,
// }
// },
// [fetchData.fulfilled.toString()]: (state, action: PayloadAction<ITrack[]>) => {
// state.tracks = action.payload;
// }
builder.addCase(HYDRATE, (state, action: any) => {
return {
...state,
...action.payload,
}
}).addCase(fetchData.fulfilled, (state, action: PayloadAction<ITrack[]>) => {
// return {
// ...state,
// ...action.payload,
// }
state.tracks = action.payload;
});
}
})
export const { setPause, setTime, setVolume, setDuration, setActive } = mainSlice.actions;
export default mainSlice.reducer;
configurate store
import { AnyAction, configureStore, ThunkDispatch } from '#reduxjs/toolkit';
import { createWrapper, MakeStore, Context } from 'next-redux-wrapper';
import mainRed from './index';
const makeStore = () => configureStore({
reducer: {
main: mainRed
},
})
type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];
export type NextThunkDispatch = ThunkDispatch<RootState, void, AnyAction>;
export const wrapper = createWrapper<AppStore>(makeStore);
hooks for TypeScript
import { useDispatch, useSelector, TypedUseSelectorHook } from "react-redux";
import { RootState, AppDispatch } from "../store/reducer";
export const useTypeSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useTypeDispath = ()=> useDispatch<AppDispatch>();
getServerSideProps and useSelector (in page)
import { Container, ListItem, Stack, Box, Button } from "#mui/material";
import TrackList from "../../components/TrackList";
// interfaces
import { ITrack } from "../../types/tracks/track";
// import hooks
import { useRouter } from "next/router";
import { useTypeSelector } from "../../hooks/useTypeSelector";
// wrapper
import { NextThunkDispatch, wrapper } from "../../store/reducer";
import { fetchData, setVolume } from "../../store";
export default function Index(): JSX.Element {
const router = useRouter();
const tracks: ITrack[] = useTypeSelector(state => state.main.tracks);
return (
<div className="main">
<Container >
<Stack marginTop={20} sx={{ backgroundColor: "#C4C4C4", fontSize: '24px', fontWeight: 'bold' }}>
<Box p={5} justifyContent="space-between">
<ListItem>List of Tracks</ListItem>
<Button variant="outlined" sx={{ backgroundColor: 'blue', color: 'white' }} onClick={() => router.push('/tracks/create')} >Upload</Button>
</Box>
<TrackList tracks={tracks} />
</Stack>
</Container>
</div>
)
}
export const getServerSideProps = wrapper.getServerSideProps((store) => async () => {
const dispatch = store.dispatch as NextThunkDispatch;
// dispatch(fetchData());
dispatch(setVolume(2));
return {
props: {}
}
})

React Redux state stop changing after render of another component

So , Basically I am new to react and I am stuck in situation where I want to fetch more data from an API with sending page numbers when I click on button, everything works perfectly i am getting data and everything but the main issue comes when I click on of items it show the details of the game by rendering another component but when i close it and try to fetch more data by clicking on next button it doesn't work and doesn't update the redux store state anymore. and if don't open the detail by clicking on game item it works perfectly the redux store state changes and everything. i am new to this so i will appreciate everyone who can help me with it and hope i learn new things and please let me know if i have done anything wrong in my code or there's a better way to do it i will also appreciate that.
PopularGames components
import React, { useState, useEffect } from "react";
//ANIMATION AND STYLED
import styled from "styled-components";
import { motion, AnimatePresence, AnimateSharedLayout } from "framer-motion";
//REDUX and ROUTER
import {
AllPopularGame,
NextPage,
PrevPage,
} from "../Actions/popularGameActions";
import { useSelector, useDispatch } from "react-redux";
import { Link, useLocation, useHistory } from "react-router-dom";
//COMPONENTS
import Game from "./games";
import GameDetail from "./gameDetail";
const PopularGames = () => {
//GETTNG PATH
const Location = useLocation();
const History = useHistory();
const pathId = Location.pathname.split("/")[4];
//Redux store
const { allPopularGame, gameCount, currentPage } = useSelector(
(state) => state.popular
);
//No of pages
const totalPage = Math.ceil(gameCount / 36);
//STATES
const [totalPages] = useState(totalPage);
//SCROLL TO TOP
useEffect(() => {
window.scrollTo(0, 0);
}, [currentPage]);
//Handlers
const PrevHandler = () => {
if (currentPage <= 1) {
return;
} else {
dispatch(PrevPage());
History.push(`/popular/games?page=${currentPage - 1}`);
}
};
const NextHandler = () => {
if (currentPage <= totalPages) {
return;
} else {
dispatch(NextPage());
History.push(`/popular/games?page=${currentPage + 1}`);
}
};
//Fetch all popular games
const dispatch = useDispatch();
useEffect(() => {
dispatch(AllPopularGame(currentPage));
}, [dispatch, currentPage]);
// {`${currentPage} /popular/games/${popularGames.id}`}
return (
<Popular>
<h2>Popular Games </h2>
<AnimateSharedLayout type="crossfade">
<AnimatePresence>
{pathId && <GameDetail pathId={pathId} curPage={currentPage} />}
</AnimatePresence>
<Games>
{allPopularGame.map((popularGames) => (
<Link
to={`/popular/games/${currentPage}/${popularGames.id}`}
key={popularGames.id}
>
<Game
name={popularGames.name}
img={popularGames.background_image}
rating={popularGames.rating}
id={popularGames.id}
key={popularGames.id}
released={popularGames.released}
/>
</Link>
))}
</Games>
</AnimateSharedLayout>
<Page>
<Button onClick={PrevHandler}>
<span>Prev</span>
</Button>
<p>{currentPage}</p>
<Button onClick={NextHandler}>
<span>Next</span>
</Button>
</Page>
</Popular>
);
};
GAME ACTION
import axios from "axios";
//Import URL
import { allPopularGamesURL } from "../Api/api";
export const AllPopularGame = (page) => async (dispatch) => {
const fetchPopularGames = await axios.get(allPopularGamesURL(page));
dispatch({
type: "POPULAR_GAMES",
payload: {
popular: fetchPopularGames.data.results,
count: fetchPopularGames.data.count,
},
});
};
export const NextPage = () => (dispatch) => {
dispatch({
type: "NEXT_PAGE",
});
};
export const PrevPage = () => (dispatch) => {
dispatch({
type: "PREV_PAGE",
});
};
GAME REDUCER
const initState = {
allPopularGame: [],
gameCount: null,
currentPage: 1,
};
export const PopularGames = (state = initState, action) => {
switch (action.type) {
case "POPULAR_GAMES": {
return {
...state,
allPopularGame: action.payload.popular,
gameCount: action.payload.count,
};
}
case "NEXT_PAGE": {
return {
...state,
currentPage: state.currentPage + 1,
};
}
case "PREV_PAGE": {
return {
...state,
currentPage: state.currentPage - 1,
};
}
default: {
return {
...state,
};
}
}
};

How can I get step by step data from api in redux/react by infinite scroll

I want to get 20 posts by scroll down each time how can i do? my project have big Data and I use redux for get data, can I get data step by step? for example get 20 posts for first time and when a user scroll down load the next 20 posts.
I use React Hooks for develop
my posts component source is:
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import PostItem from './PostItem';
import { getPosts } from '../../actions/post';
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
return loading ? <Spinner /> : (
{posts.map(post => (
<PostItem key={post._id} post={post} />
))}
)
}
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPosts })(Posts)
my action code is:
import { setAlert } from './alert';
import {
GET_POSTS,
POST_ERROR
} from "../actions/types";
// Get Posts
export const getPosts = () => async dispatch => {
try {
const res = await axios.get('/api/ads');
dispatch({
type: GET_POSTS,
payload: res.data
});
} catch (err) {
dispatch({
type: POST_ERROR,
payload: { msg: err.response.satusText, status: err.response.satus }
});
}
}```
///////////////////////////////
///////////////AND REDUCER IS :
import {
GET_POSTS,
POST_ERROR
} from '../actions/types';
const initialState = {
posts: [],
loading: true,
error: {}
};
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_POSTS:
return {
...state,
posts: payload,
loading: false
}
case POST_ERROR:
return {
...state,
error: payload,
loading: false
}
default:
return state;
}
}
You can use react-infinite-scroller library. I've tried to change your Posts method, so maybe it would be useful.but as mentioned in comments you should add pagination to your API.
const Posts = ({ getPosts, post: { posts, loading } }) => {
useEffect(() => {
getPosts();
}, [getPosts]);
const itemsPerPage = 20;
const [hasMoreItems, sethasMoreItems] = useState(true);
const [records, setrecords] = useState(itemsPerPage);
const showItems=(posts)=> {
var items = [];
for (var i = 0; i < records; i++) {
items.push( <PostItem key={posts[i]._id} post={posts[i]} />);
}
return items;
}
const loadMore=()=> {
if (records === posts.length) {
sethasMoreItems(false);
} else {
setTimeout(() => {
setrecords(records + itemsPerPage);
}, 2000);
}
}
return <InfiniteScroll
loadMore={loadMore}
hasMore={hasMoreItems}
loader={<div className="loader"> Loading... </div>}
useWindow={false}
>
{showItems()}
</InfiniteScroll>{" "}
}
Working Codesandbox sample with fake data.

Timeout for RefreshView in React Native Expo App

My current React Native Expo app has a ScrollView that implements RefreshControl. A user pulling down the ScrollView will cause the onRefresh function to be executed, which in turns call an action creator getSpotPrices that queries an API using axios.
Problem: If there is a network problem, the axios.get() function will take very long to time out. Thus, there is a need to implement the timing out of either axios.get() or onRefresh.
How can we implement a timeout function into RefreshControl?
/src/containers/main.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { ScrollView, RefreshControl } from 'react-native';
import MyList from '../components/MyList';
import { getSpotPrices } from '../actions';
class RefreshableList extends Component {
onRefresh = () => {
this.props.getSpotPrices();
}
render() {
return (
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.props.isLoading}
onRefresh={this._onRefresh}
/>
}>
<MyList />
</ScrollView>
)
}
}
const mapStateToProps = (state) => {
return {
isLoading: state.currencies.isLoading,
}
}
const mapDispatchToProps = (dispatch) => {
return {
getSpotPrices: () => dispatch(getSpotPrices()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(RefreshableList);
/src/actions/index.js
import api from "../utils/api";
import * as types from "../types";
import Axios from "axios";
const getSpotPrice = async () => {
try {
const res = await Axios.get(`https://api.coinbase.com/v2/prices/spot`);
return parseFloat(res.data.data.amount);
} catch (err) {
throw new Error(err);
}
};
export const getSpotPrices = () => async dispatch => {
try {
const price = await getSpotPrice();
dispatch({
type: types.CURRENCIES_SET,
payload: price
});
} catch (err) {
dispatch({
type: types.CURRENCIES_FAILED_FETCH,
payload: err.toString()
});
} finally {
dispatch({
type: types.CURRENCIES_IS_LOADING,
payload: false
})
}
};
/src/reducers/currencies.js
import * as types from "../types";
const initialState = {
data: {},
isLoading: false,
};
export default (state = initialState, { type, payload }) => {
switch (type) {
case types.CURRENCIES_SET:
return {
...state,
data: payload,
error: "",
isLoading: false
};
case types.CURRENCIES_FAILED_FETCH:
return {
...state,
error: payload,
isLoading: false
};
case types.CURRENCIES_IS_LOADING:
return {
isLoading: payload
}
default:
return state;
}
};
Check if user is connected internet or not using the react-native-netinfo library
NetInfo.fetch().then(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
this.setState({ connected: state.isConnected });
});
// Subscribe
const unsubscribe = NetInfo.addEventListener(state => {
console.log("Connection type", state.type);
this.setState({ connected: state.isConnected });
});
// Unsubscribe
unsubscribe(); <- do this in componentwillunmount
Its generally a good practice to add a timeout, in all your api calls, in axios you can easily add a timeout option like:
await axios.get(url, { headers, timeout: 5000 })
so in your case modify the axios call as
await Axios.get(https://api.coinbase.com/v2/prices/spot, { timeout: 5000 } );
I have put timeout of 5 seconds you can modify the parameter according to your need.

Redux dispatches an API call failure even though the network tab in devtools shows the API call received a status of 200

I am new to redux and I am having a hard time understanding how to connect the payload of my API call to my state.
Right now my action.js file looks like this:
import ApiService from '../../services/ApiService';
import { reset } from 'redux-form';
//actions
export const getStock = () => {
return {
type: 'GET_STOCK'
}
}
export const getStockPending = () => {
return {
type: 'GET_STOCK_PENDING'
}
}
export const getStockFulfilled = (stock) => {
return {
type: 'GET_STOCK_FULFILLED',
payload: stock
}
}
export const getStockRejected = () => {
return {
type: 'GET_STOCK_REJECTED'
}
}
// async function calls
export function fetchStocksWithRedux() {
const action_type = "GET_STOCK";
const stock = 'AAPL';
return (dispatch) => {
dispatch({type: `${action_type}_PENDING`});
return ApiService.get(`/search?query=${stock}`)
.then(([response, json]) =>{
if(response.status === 200){
dispatch(getStockFulfilled(json))
}
else{
dispatch(getStockRejected())
}
})
}
}
and my reducer.js file looks like this:
const initialState = {
inProgress: false,
stock: {},
stocks: ['NKE', 'AMZN', 'AAPL'],
error: {}
}
export default (state = initialState, action) => {
switch(action.type) {
case 'GET_STOCK_PENDING':
return {
...state,
inProgress: true,
error: false
}
case 'GET_STOCK_FULFILLED':
return {
...state,
stock: action.payload,
inProgress: false
}
case 'GET_STOCK_REJECTED':
return {
...state,
inProgress: false,
error: action.error
}
default:
return state;
}
}
When I go to call my method fetchStocksWithRedux in my component, the network tab in my dev tools shows a 200 status and the response I'm expecting, but the reducer dispatches the 'GET_STOCK_REJECTED' action, but the error hash is empty. What do you think is going wrong?
Here is my component, for reference:
import React, { Component } from 'react';
import { fetchStocksWithRedux } from '../../redux/modules/Stock/actions';
import { connect } from 'react-redux';
class Dashboard extends Component {
componentDidMount() {
this.props.fetchStocksWithRedux()
}
render() {
return (
<div className="uk-position-center">
</div>
)
}
}
export default connect(
state => ({
stocks: state.stocks,
stock: state.stock
})
, { fetchStocksWithRedux }
)(Dashboard);
Thanks. Any advice or guidance would be greatly appreciated!

Categories

Resources