asyncstorage with useReducer does not save the values - react-native

i am new to reactnative, i was trying to build todo app using useReducer and asyncstorage but not able to achieve that, i have read every article and related questions i am able to do it using useState but did not got the result with useReducer.
Any help/suggestions will be appreciated. Thank You .
const MainScreen = () => {
const getData = async () => {
try {
const data = await AsyncStorage.getItem('itemlist');
return (output = JSON.parse(data));
} catch (e) {
}
};
React.useEffect(() => {
getData();
}, []);
const [text, setText] = React.useState('');// for textinput
const { dispatch } = useContext(NotesContext);
const handleSubmit = async () => {
try {
const output = JSON.stringify(state);
await AsyncStorage.setItem('itemlist', output);
} catch (error) {
console.log(error);
}
if (text) {
dispatch({ type: 'ADD_TODO', payload: text });
}
setText('');
};
//this is my reducer function below
export const TodoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return state.concat({
task: action.payload,
id: uuid.v4(),
complete: false,
});
case 'DELETE_TODO':
return state.filter((todo) => todo.id !== action.payload);
default:
throw new Error();
}
};

Related

Multiple useEffect in react-native to achieve mentioned functionality

I need help with the async nature of Async storage and axios api. Here's the functionality that I am trying to achieve ->
send request to two separate api to get some data.
display that data on the screen with some additional text
api request are authenticated so a token is passed as Authentication Header
I have attached the current implementation, I am having the a number of errors in this
Errors:
Login_token not set in state after fetching from Async Storage.
Data not set in state after api call
both resulting in either failed api calls or undefined state errors on render
This is my code.
import React, { FunctionComponent, useEffect, useCallback, useState} from 'react';
import { StyleSheet, View} from 'react-native';
// chat
import { GiftedChat } from 'react-native-gifted-chat';
// navigation
import { RootStackParamList } from '../../navigators/RootStack';
import { StackScreenProps } from '#react-navigation/stack';
export type Props = StackScreenProps<RootStackParamList, "Chat">;
// api
import { Convo_details, Send_Msg, List_Msg, Expert_Public_Profile } from '../../api/UserApi';
import Spinner from 'react-native-loading-spinner-overlay';
import AsyncStorage from '#react-native-async-storage/async-storage';
import uuid from 'react-native-uuid';
const Chat: FunctionComponent<Props> = ({ navigation, route, ...props }) => {
// console.log(props.route.params);
const [login_token, setlogin_token] = useState('')
const [conversation_id, setconversation_id] = useState('')
const [conversation_details, setconversation_details] = useState({})
const [currentuser, setcurrentuser] = useState({})
const [loading, setLoading] = useState(false);
const [expertuid, setexpertuid] = useState('')
const [ExpertProfile, setExpertProfile] = useState({})
const [messages, setMessages] = useState([]);
useEffect(() => {
getlogintoken()
console.log("####################################","getlogintoken");
}, [])
/* conversationid */
useEffect(() => {
if (route.params != null) {
setconversation_id(route.params[0])
}
console.log("####################################","conversation id");
}, [])
/* expert uid */
useEffect(() => {
if (route.params != null) {
setexpertuid(route.params[1])
}
console.log("####################################","expert uid");
}, [])
/* expert public profile */
useEffect(() => {
getexpertpublicprofile()
getConvo_details()
console.log("####################################","convo_details");
}, [])
useEffect(() => {
// get current user
AsyncStorage.getItem("currentuser").then(res => {
if (res != null) setcurrentuser(res)
else alert("Current user not found")
})
console.log("####################################","current user");
}, [])
// set welcome msg
useEffect(() => {
if (Object.keys(conversation_details).length != 0 && Object.keys(ExpertProfile).length != 0)
setwelcomemsg()
}, [])
const onSend = useCallback(async (messages = []) => {
// console.log(messages[0].text);
setMessages(previousMessages => GiftedChat.append(previousMessages, messages))
const data = {
conversation_id: "f98d6851-a713-4f58-9118-77a779ff175f",//conversation_id,
message_type: "TEXT",
body: messages[0].text
}
const res: any = await Send_Msg(data, login_token)
.catch(error => {
alert(`Send_Msg -> ${error}`)
console.log(error);
return
})
if (res.status == 200) {
console.log(res.data);
} else console.log(res);
}, [])
const getexpertpublicprofile = async () => {
setLoading(true)
const res: any = await Expert_Public_Profile(expertuid, login_token)
.catch(error => {
setLoading(false)
console.log("Expert public profile ->");
alert(`Expert public profile ->${error.message}`)
console.log(error);
return
})
setLoading(false)
if (res.status === 200) setExpertProfile(res.data)
else {
alert(`get expert public profile${res.data.message}`)
console.log("getexpertpublicprofile -->");
console.log(res.data);
}
}
const getlogintoken = () => {
AsyncStorage.getItem("login_token").then(res => {
if (res != null) {
setLoading(false)
setlogin_token(res)
}
else alert("No login token found")
})
}
const getConvo_details = async () => {
setLoading(true)
const res: any = await Convo_details(conversation_id, login_token)
.catch(error => {
setLoading(false)
alert(`Convo_details-->${error.message}`)
console.log("Convo_details -->");
console.log(error);
return
})
setLoading(false)
if (res.status === 200) setconversation_details(res.data)
else {
alert(`get convo details-> ${res.data.message}`)
console.log("getConvo_details -->");
console.log(res.data);
}
}
const setwelcomemsg = () => {
try {
let user = JSON.parse(currentuser)
let messages = [
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `About Me - ${ExpertProfile.bio}`,
user: {
_id: conversation_details.recipient.user_uid,
}
},
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `My name is ${conversation_details.recipient.name}`,
user: {
_id: conversation_details.recipient.user_uid,
}
},
{
_id: uuid.v4().toString(),
conversation_id: conversation_details.conversation_id,
created_at: new Date(),
from: conversation_details.recipient.user_uid,
type: "TEXT",
text: `Hi ${user.full_name}`,
user: {
_id: conversation_details.recipient.user_uid,
}
}]
setMessages(previousMessages => GiftedChat.append(previousMessages, messages))
} catch (error) {
console.log("try -> set welcome msg");
console.log(error);
return
}
}
return (
<View style={styles.maincontainer}>
<Spinner
visible={loading}
textContent={'Loading...'}
textStyle={{ color: '#FFF' }}
/>
<GiftedChat
messages={messages}
onSend={messages => onSend(messages)}
user={{
_id: currentuser.user_uid,
}}
isTyping={false}
scrollToBottom={true}
showAvatarForEveryMessage={true}
renderAvatar={() => null}
/>
</View>
);
}
export default Chat;
const styles = StyleSheet.create({
maincontainer: {
flex: 1,
},
});
When axios returns, it usually give the response as res.data, so in your case, try either res.data or res.data.yourToken (I'm not sure how it's your object).
Gurav,
As far as your code above, The api call's will trigger even before you get currentuser or loginToken. You have to handle the api call after getting the currentuser and loginToken. This can be gracefully handled with async, await.
example code:
useEffect(() => {
getData()
}, [])
useEffect(() => {
if(login_token && currentuser) {
//The api call goes here after you get the logintoken andcurrentuser.
// The above condition is just an example but will vary based on your requirements
}
}, [login_token, currentuser])
const getData = async () => {
await getlogintoken()
await getcurrentuser()
}
const getlogintoken = async () => {
await AsyncStorage.getItem("login_token").then(res => {
if (res != null) {
setLoading(false)
setlogin_token(res)
}
else alert("No login token found")
})
}
const getcurrentuser = async () => {
await AsyncStorage.getItem("currentuser").then(res => {
if (res != null) setcurrentuser(res)
else alert("Current user not found")
})
}

Unable to update state in redux when using #reduxjs/toolkit

I currently started learning redux. My code was working perfectly with core redux, then I tried out #reduxjs/toolkit and now I'm unable to access the function to change the state in the store. Here is my code of reducer.
const seasonEdits = createSlice({
name: "seasons",
initialState: [],
reducers: {
addSeason(state, action) {
state.push(action.payload);
console.log("this here");
},
removeSeason(state, action) {
state.filter((season) => season.id !== action.payload);
},
markComplete(state, action) {
state.map((season) => {
if (season.id == action.payload) season.isWatched = !season.isWatched;
});
},
},
});
export const { addSeason, removeSeason, markComplete } = seasonEdits.actions;
export default seasonEdits.reducer;
and my store.js file
import { configureStore } from "#reduxjs/toolkit";
import seasonReducer from "./reducer";
export default store = configureStore({
reducer: {
seasons: seasonReducer,
},
});
and the add.js file which has add functionality. Calling a handleSubmit function which is creating an object and adding it to an array which is the state in store.
const handleSubmit = async () => {
try {
if (!name || !totalNoSeason) {
return alert("Please add both fields");
}
const seasonToAdd = {
id: shortid.generate(),
name,
totalNoSeason,
isWatched: false,
};
addSeason(seasonToAdd);
navigation.navigate("Home");
} catch (error) {
console.log(error);
}
};
const mapDispatchToProps = (dispatch) => {
return {
addSeason: (data) => dispatch(addSeason(data)),
};
};
Add.propTypes = {
addSeason: propTypes.func.isRequired,
};
export default connect(null, mapDispatchToProps)(Add);
The issue is that array.map() and array.filter() return new arrays! Right now your reducers are calling those functions, and then just throwing away the new arrays:
removeSeason(state, action) {
// The return value is thrown away and ignored!
state.filter((season) => season.id !== action.payload);
},
You need to return the new value:
removeSeason(state, action) {
// Now RTK will see the new return value
return state.filter((season) => season.id !== action.payload);
},
See https://redux-toolkit.js.org/usage/immer-reducers#resetting-and-replacing-state for more details.

Expo Location, Expo Task Manager with Context API react native

import React, { createContext, useContext, useState, useEffect } from 'react';
import {
authenticationService,
getUserProfileService,
} from '../services/user-service';
import { getDonationHistory } from '../services/donation-service';
import AsyncStorage from '#react-native-async-storage/async-storage';
import * as TaskManager from 'expo-task-manager';
import * as Location from 'expo-location';
import * as BackgroundFetch from 'expo-background-fetch';
const UserInfoContext = createContext();
const UserInfoProvider = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userProfile, setUserProfile] = useState({
donorId: '',
icNo: '',
fName: '',
lName: '',
bloodType: '',
});
const [errorMessage, setErrorMessage] = useState(null);
const [location, setLocation] = useState({ latitude: '', longitude: '' });
let updateLocation = (loc) => {
setLocation(loc);
};
console.log(location);
const sendBackgroundLocation = async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status === 'granted') {
const { status } = await Location.requestBackgroundPermissionsAsync();
if (status === 'granted') {
await Location.startLocationUpdatesAsync('LocationUpdate', {
accuracy: Location.Accuracy.Balanced,
timeInterval: 10000,
distanceInterval: 1,
foregroundService: {
notificationTitle: 'Live Tracker',
notificationBody: 'Live Tracker is on.',
},
});
}
}
};
const _requestLocationPermission = async () => {
(async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status == 'granted') {
let { status } = await Location.requestBackgroundPermissionsAsync();
if (status == 'granted') {
} else {
console.log('Permission to access location was denied');
}
} else {
console.log('Permission to access location was denied');
}
})();
};
sendBackgroundLocation();
const getUserProfile = () => {
return userProfile;
};
useEffect(() => {
(async () => await _requestLocationPermission())();
retrieveAuthTokens();
retrieveUserProfile();
});
let retrieveAuthTokens = async () => {
try {
const authTokens = await AsyncStorage.getItem('authTokens');
authTokens ? setIsLoggedIn(true) : setIsLoggedIn(false);
} catch (error) {
console.log(error.message);
}
};
let retrieveUserProfile = async () => {
try {
const userProfile = JSON.parse(await AsyncStorage.getItem('userProfile'));
userProfile
? setUserProfile({
donorId: userProfile.donorId,
icNo: userProfile.appUser.username,
fName: userProfile.fName,
lName: userProfile.lName,
bloodType: userProfile.bloodType,
})
: null;
} catch (error) {
console.log(error.message);
}
};
let loginUser = (values) => {
authenticationService(values)
.then(async (data) => {
if (data !== undefined && data !== null) {
const tokens = data.data;
await AsyncStorage.setItem('authTokens', JSON.stringify(tokens));
getProfile(values.icNo);
getHistories(userProfile.donorId);
setErrorMessage(null);
} else {
setErrorMessage('Wrong email/password!');
}
})
.catch((error) => console.log(error.message));
};
let logoutUser = async () => {
try {
await AsyncStorage.clear().then(console.log('clear'));
setIsLoggedIn(false);
} catch (error) {
console.log(error.message);
}
};
getProfile = (icNo) => {
getUserProfileService(icNo)
.then(async (res) => {
if (res !== undefined && res !== null) {
const profile = res.data;
await AsyncStorage.setItem('userProfile', JSON.stringify(profile));
setIsLoggedIn(true);
}
})
.catch((error) => console.log(error.message));
};
const getHistories = (userId) => {
getDonationHistory(userId)
.then(async (res) => {
if (res !== undefined && res !== null) {
const historyData = res.data;
await AsyncStorage.setItem(
'donationHistories',
JSON.stringify(historyData)
);
} else {
console.log('no data');
}
})
.catch((error) => console.log(error.message));
};
let contextData = {
loginUser: loginUser,
logoutUser: logoutUser,
isLoggedIn: isLoggedIn,
errorMessage: errorMessage,
userProfile: userProfile,
getHistories: getHistories,
};
return (
<UserInfoContext.Provider value={contextData}>
{children}
</UserInfoContext.Provider>
);
};
export const useUserInfo = () => useContext(UserInfoContext);
export default UserInfoProvider;
function myTask() {
try {
const backendData = 'Simulated fetch ' + Math.random();
return backendData
? BackgroundFetch.BackgroundFetchResult.NewData
: BackgroundFetch.BackgroundFetchResult.NoData;
} catch (err) {
return BackgroundFetch.BackgroundFetchResult.Failed;
}
}
async function initBackgroundFetch(taskName, interval = 60 * 15) {
try {
if (!TaskManager.isTaskDefined(taskName)) {
TaskManager.defineTask(taskName, ({ data, error }) => {
if (error) {
console.log('Error bg', error);
return;
}
if (data) {
const { locations } = data;
console.log(
locations[0].coords.latitude,
locations[0].coords.longitude
);
//-----------------doesnt work ----------------------------
UserInfoProvider.updateLocation({
latitude: locations[0].coords.latitude,
longitude: locations[0].coords.longitude,
});
//-----------------doesnt work ----------------------------
}
});
}
const options = {
minimumInterval: interval, // in seconds
};
await BackgroundFetch.registerTaskAsync(taskName, options);
} catch (err) {
console.log('registerTaskAsync() failed:', err);
}
}
initBackgroundFetch('LocationUpdate', 5);
I'm trying to update the location state in the UserInfoProvider and this location info will be sent to the firestore along with the userprofile details retrieved in this provider.
However, it seems that i cant access the
UserInfoProvider.updateLocation()
outside the UserInfoProvider component.
Is there anyway I can get both the userprofile info and the location info retrieved from background task together and send them to firestore?
At the moment, only the
console.log(
locations[0].coords.latitude,
locations[0].coords.longitude
);
in the background task seems to be working.
Error I got:
TaskManager: Task "LocationUpdate" failed:, [TypeError: undefined is
not a function (near '...UserInfoProvider.updateLocation...')] at
node_modules\react-native\Libraries\LogBox\LogBox.js:149:8 in
registerError at
node_modules\react-native\Libraries\LogBox\LogBox.js:60:8 in errorImpl
at node_modules\react-native\Libraries\LogBox\LogBox.js:34:4 in
console.error at
node_modules\expo\build\environment\react-native-logs.fx.js:27:4 in
error at node_modules\expo-task-manager\build\TaskManager.js:143:16 in
eventEmitter.addListener$argument_1 at
node_modules\regenerator-runtime\runtime.js:63:36 in tryCatch at
node_modules\regenerator-runtime\runtime.js:294:29 in invoke at
node_modules\regenerator-runtime\runtime.js:63:36 in tryCatch at
node_modules\regenerator-runtime\runtime.js:155:27 in invoke at
node_modules\regenerator-runtime\runtime.js:190:16 in
PromiseImpl$argument_0 at
node_modules\react-native\node_modules\promise\setimmediate\core.js:45:6
in tryCallTwo at
node_modules\react-native\node_modules\promise\setimmediate\core.js:200:22 in doResolve at
node_modules\react-native\node_modules\promise\setimmediate\core.js:66:11
in Promise at node_modules\regenerator-runtime\runtime.js:189:15 in
callInvokeWithMethodAndArg at
node_modules\regenerator-runtime\runtime.js:212:38 in enqueue at
node_modules\regenerator-runtime\runtime.js:239:8 in exports.async at
node_modules\expo-task-manager\build\TaskManager.js:133:57 in
eventEmitter.addListener$argument_1 at
node_modules\react-native\Libraries\vendor\emitter_EventEmitter.js:135:10 in EventEmitter#emit at
node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:414:4
in __callFunction at
node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:113:6
in __guard$argument_0 at
node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:365:10
in __guard at
node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:112:4
in callFunctionReturnFlushedQueue
By reviewing UserInfoProvider context value:
let contextData = {
loginUser: loginUser,
logoutUser: logoutUser,
isLoggedIn: isLoggedIn,
errorMessage: errorMessage,
userProfile: userProfile,
getHistories: getHistories,
};
updateLocation function not available and you can't access.
You need to use useUserInfo to consume UserInfoProvider value.

How can I test my custom react hook that uses fetch?

I have created a custom react hook which uses fetch from whatwg-fetch. I have tests for the components that make use of the hook and can mock the whole hook, but now am trying to write tests for the hook itself and my goal is to mock the fetch response. This is my hook.
import { useState, useEffect } from "react";
import "whatwg-fetch";
export default function useFetch(url) {
const [data, setData] = useState(undefined);
const [response, setResponse] = useState(undefined)
const [isLoading, setLoading] = useState(true);
const [error, setError] = useState(undefined);
useEffect(() => {
try {
const fetchData = async () => {
const result = await fetch(url);
setResponse(result);
const responseText = await result.text();
setData(responseText);
setLoading(false);
};
fetchData();
} catch (error) {
setError(error);
}
}, [url]);
return { data, response, isLoading, error };
}
export { useFetch }
Currently, this is how my test looks like. Feels like I cannot mock the fetch to return the desired value.
I have tried writing tests by looking at several tutorials with no luck. I have tried the following tutorials:
Test Custom Hooks Using React Hooks Testing Library
Testing custom react hooks with jest
A Complete Guide to Testing React Hooks
UPDATE: Changed tests, my first test passes (resolve) my second one does not. Based on the third tutorial.
NEW TESTS
import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";
import useFetch from "./useFetch";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
let container = null;
describe("useFetch tests", () => {
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
unmountComponentAtNode(container);
container.remove();
container = null;
});
it("useFetch returns data on success", async () => {
function fetchMock(url) {
return new Promise((resolve) => setTimeout(() => {
resolve({
status: 200,
ok: true,
text: () => Promise.resolve({
data: "data from api"
})
});
}, 300));
}
jest.spyOn(global, "fetch").mockImplementation(fetchMock);
act(() => {
render(<TestComponent url="url1" />, container);
});
expect(container.textContent).toBe("loading");
await sleep(500);
expect(container.textContent).toBe("data from api");
});
it("useFetch return error on fail", async () => {
// const a = 200 + Math.random() * 300;
// console.log(a)
// let promise = new Promise(function (resolve, reject) {
// // after 1 second signal that the job is finished with an error
// setTimeout(() => reject("error"), a);
// });
// function fetchMock(url) {
// return promise;
// }
function fetchMock(url) {
return new Promise((resolve) => setTimeout(() => {
resolve({
status: 404,
ok: true,
text: () => Promise.resolve({
data: "data from api"
})
});
}, 200 + Math.random() * 300));
}
jest.spyOn(global, "fetch").mockImplementation(fetchMock);
act(() => {
render(<TestComponent url="url1" />, container);
});
expect(container.textContent).toBe("loading");
await sleep(500);
expect(container.textContent).toBe("error");
});
});
function TestComponent({ url }) {
const {
data, response, isLoading, error
} = useFetch(url);
if (isLoading) {
return <div>loading</div>;
}
if (data) {
return <div>{data.data}</div>
}
if (error) {
return <div>error</div>
}
return <div></div>;
}
OLD TESTS
import { useFetch } from "../../../src/utils/custom-hooks/useFetch";
describe("useFetch tests", () => {
beforeEach(() => {
jest.spyOn(window, "fetch");
});
it("200", () => {
window.fetch.mockResolvedValueOnce({
ok: true,
status: 200,
})
const { result, rerender } = renderHook(
(url) => useFetch(url),
{
url: "url1"
}
);
expect(result.current.response).toBe(undefined);
rerender("url2");
expect(result.current.status).toBe(200);
});
});

Hash value null error in react-native hooks useEffect

the hash data I received using asyncStorage in my react native project is wrong. It actually works when I refresh the page. My request is probably running earlier than hash. However, I could not find a solution to this.
const [info, setInfo] = useState('');
const [hash, setHash] = useState(null);
const _retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('hash');
setHash(value)
} catch (error) {
// Error retrieving data
}
};
useEffect(() => {
_retrieveData()
setTimeout(() => {
console.log(hash) //
axios.get(apiUrl + 'loginInfo?hash=' + hash)
.then(response => {
setInfo(response.data.message);
console.log('res', response);
});
}, 1000);
}, []);
The _retriveData method should be inside of the useEffect.
const [info, setInfo] = useState('');
const [hash, setHash] = useState(null);
useEffect(() => {
const _retrieveData = async () => {
try {
const hash = await AsyncStorage.getItem('hash');
return hash
} catch (error) {
console.log('error',error);
// Error retrieving data
}
};
const hash = _retrieveData()
setTimeout(() => {
console.log(hash) //
axios.get(apiUrl + 'loginInfo?hash=' + hash)
.then(response => {
setInfo(response.data.message);
console.log('res', response);
});
}, 1000);
}, []);
I solved the problem by getting help from a friend.
const [info, setInfo] = useState({});
const [hash, setHash] = useState(null);
const _retrieveData = useCallback(async () => {
try {
const value = await AsyncStorage.getItem('hash');
setHash(value);
} catch (error) {
// Error retrieving data
}
}, []);
const getInfo = useCallback(async () => {
try {
const response = await axios.get(apiUrl + 'loginInfo?hash=' + hash);
setInfo(response.data.message)
} catch (e) {
console.log(e);
}
}, [hash]);
useEffect(() => {
if (!hash) {
_retrieveData();
}
}, [hash])
useEffect(() => {
if (hash) {
setTimeout(() => {
getInfo();
},500)
}
}, [hash, getInfo]);