How to assign / access data from Mobx in React Native? - reactjs

I am currently using mobx and react native, I have created the files below where a user can get their geo location. The questions I have are:
In the function fetchLocation is this the correct way of assigning the returned position? (In terms of mobx best practice)
In fetchLocation how can I console log geo as I have tried and I get undefined.
In LocationScreen.js I am trying to access the value for geo how can I do this? I am able to run the fetchLocation function fine.
How can I do something similar to this -
{this.props.store.geo ? Retrieved Geo Location : null}
Thanks in advance
LocationStore.js:
import { observable, action, computed } from 'mobx'
class LocationStore{
#observable geo = [];
#action fetchLocation() {
navigator.geolocation.getCurrentPosition(
(position) => {
console.log(position.coords.latitude, position.coords.longitude);
this.geo = {
lat: position.coords.latitude
}
console.log(this.geo);
},
(error) => this.error= {error},
{ enableHighAccuracy: false, timeout: 20000 },
);
}
}
export default new LocationStore()
LocationScreen.js

The clear concern here, is your use of #action without promise! I would suggest to ditch #action and #computed and just go for a stateless solution:
LocationStore.js:
import {observable} from 'mobx'
class LocationStore {
#observable geo = {};
#observable error = {};
#observable isLoading = false;
async fetchLocation() {
this.isLoading = true;
try {
await navigator.geolocation.getCurrentPosition((position) => {
this.geo = {
lat: position.coords.latitude
}
this.isLoading = false;
}
);
} catch (e) {
this.error = e
this.isLoading = false;
}
}
}
export default new LocationStore()
component.js
async fetchLocation(){
await this.props.LocationStore.fetchLocation();
}
render () {
const { geo, error, isLoading } = this.props.LocationStore
return (<View>
<Button onPress={this.fetchLocation.bind(this)}></Button>
{(!!isLoading)?<Text>{geo}</Text>:<Text>Loading</Text>}
</View>)
}
Check-out 'async / await' for more information:
https://mobx.js.org/best/actions.html

Related

How do I get the data from an API call, in a different file, in React

I'm trying to update some code, taking into account new sdk versions. I have the new api call in one file:
import { CognitoIdentityProviderClient, ListUsersCommand } from "#aws-sdk/client-cognito-identity-provider";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
await Auth.currentCredentials().then((data) => {
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id
};
const command = new ListUsersCommand(params);
client.send(command).then(
(data) => {
return data
},
(error) => {
console.log(error)
}
);
});
}
I'm trying to retrive the data in another file:
import ListUsers from "../../../API/cognito/ListUsers";
import ListUsersInGroup from "../../../API/cognito/ListUsersInGroup";
import { useState, useEffect, useRef } from "react";
import PortalUsersTable from "../../../components/tables/PortalUsersTable";
export default function ManageUsers() {
const [userDetails, setUserDetails] = useState("");
const refUsers = useRef();
const refUsersExec = useRef();
const refUsersAdmin = useRef();
const refUsersGroups = useRef();
useEffect(() => {
function getUsers() {
refUsers.current = ListUsers();
refUsersExec.current = ListUsersInGroup("usersAdmin");
refUsersAdmin.current = ListUsersInGroup("usersExec");
//setUsersTloOfficers(apiTloOfficers);
refUsersGroups.current = ListUsersInGroup("usersGroups");
let userData = [];
let arrUsersExec = [];
for (let a in refUsersExec.current.Users) {
arrUsersExec.push(refUsersExec.current.Users[a].Username);
}
let arrUsersAdmin = [];
for (let b in refUsersAdmin.current.Users) {
arrUsersAdmin.push(refUsersAdmin.current.Users[b].Username);
}
let arrUsersGroups = [];
for (let b in refUsersNtigGroups.current.Users) {
arrUsersGroups.push(refUsersGroups.current.Users[b].Username);
}
for (let i in refUsers.current.Users) {
let email = null;
for (let x in refUsers.current.Users[i].Attributes) {
if (refUsers.current.Users[i].Attributes[x].Name === "email") {
email = refUsers.current.Users[i].Attributes[x].Value;
break;
}
}
let memberExec = arrUsersExec.includes(refUsers.current.Users[i].Username);
let memberAdmin = arrUsersAdmin.includes(refUsers.current.Users[i].Username);
let memberGroups = arrUsersGroups.includes(refUsers.current.Users[i].Username);
userData.push({
id: i,
Username: refUsers.current.Users[i].Username,
AccountStatus: refUsers.current.Users[i].UserStatus,
Email: email,
Users: memberGroups,
Exec: memberExec,
Admin: memberAdmin,
});
}
setUserDetails(userData);
}
getUsers();
}, []);
return (
<>
<h2>Manage Portal Users</h2>
<PortalUsersTable userDetails={userDetails} />
</>
);
}
The logic to handle the API data is sound.
This is the old API call:
import AWS from "aws-sdk";
import awsmobile from "../../aws-exports";
import { Auth } from "aws-amplify";
export default async function ListUsers() {
let idToken = "";
await Auth.currentAuthenticatedUser().then((user) => {
idToken = user.signInUserSession.idToken.getJwtToken();
});
AWS.config.region = awsmobile.aws_cognito_region;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: awsmobile.aws_cognito_identity_pool_id,
RoleArn: "arn:aws:iam::xxxxxxxxx:role/xxxxxxxxxxxxx",
Logins: { "xxxxxxxxxxxxxxxxxxxx": idToken }
});
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
let params = {
UserPoolId: awsmobile.aws_user_pools_id,
AttributesToGet: ["email"]
};
return new Promise((resolve, reject) => {
cognitoidentityserviceprovider.listUsers(params, function (err, result) {
if (err) {
console.log(err);
//onError(err);
reject(err);
return;
}
if (result) {
resolve(result);
}
});
});
}
I can see the new API call is returning the correct data in the console. I think I'm not passing the data between files correctly.
I've tried various ways of changing the API call function, reading the cognito sdk description but it's not the API call that is incorrect.
How can I use the API call data in the separate file?
Even if your API call if correct, it looks like you are not returning anything from your function ListUsers. You are mixing async/await pattern with the then. I assume you have added a console.log right before the return data. Refactoring your function using async/await would look like this :
export default async function ListUsers() {
try {
const data = await Auth.currentCredentials();
const client = new CognitoIdentityProviderClient({
region: awsmobile.aws_project_region,
credentials: data,
});
const params = {
UserPoolId: awsmobile.aws_user_pools_id,
};
const command = new ListUsersCommand(params);
const commandData = await client.send(command);
return commandData;
} catch (error) {
console.log(error);
}
}

Accessing AsyncStorage react native

Hello I am new to react Native and I would like to access the user id that was stored to react Native storage so that I can pass it to the WebSocket connection but it is not returning the id
here is my sample code
import { io } from "socket.io-client/dist/socket.io";
import env from "../utils/env";
import AsyncStorage from "#react-native-async-storage/async-storage";
const getUserData = async () => {
try {
const value = await AsyncStorage.getItem("UserData");
if (value !== null) {
return value;
}
} catch (e) {
// remove error
}
console.log("Done.");
};
getUserData().then((res) => {
let response = JSON.parse(res);
console.log(response._id);
});
let socket = io(`${env.DEV_SERVER_URL}`, {
transports: ["websocket"],
query: `mobileId=${getUserData().then((res) =>{
let response = JSON.parse(res);
return response._id
})}`,
});
export default socket;
const storeData = async (data) =>{
await AsyncStorage.setItem('UserData', JSON.stringify(UserData));
}
try this to store your data you might have forgot to stringify your object before storing it

How to setState after getting data from Firestore

I am currently able to get user data from the Firestore however I'm having trouble saving the users document data. I'm getting an error below in my console
TypeError: this.setState is not a function
at Object.next (RepRequest.js:32)
at index.cjs.js:1344
at index.cjs.js:1464
I attempted to follow another user's question from
Can't setState Firestore data, however still no success.
I do have a two api request right after getting the data and I am able to setState then. I tried incorporating the Firestore request in the promise.all but was unable to successfully, which is why I have it separated. Maybe I'm headed down the wrong path, any guidance is appreciated.
import React, { useEffect, useState } from "react";
import app from "./config/base.js";
import axios from "axios";
export default class RepRequest extends React.Component {
constructor(props) {
super(props);
this.state = {
userInfo: [],
fedSens: [],
fedReps: []
};
}
componentDidMount() {
const items = [];
app.auth().onAuthStateChanged(function(user) {
if (user) {
console.log("User is signed in");
let db = app
.firestore()
.collection("user")
.doc(user.uid);
db.get().then(doc => {
if (doc.exists) {
console.log("Document data:", doc.data());
items.push(doc.data());
} else {
console.log("No doc exists");
}
});
}
this.setState({ userInfo: items });
});
Promise.all([
axios.get(
`https://api.propublica.org/congress/v1/116/senate/members.json`,
{
headers: { "X-API-Key": "9wGKmWl3kNiiSqesJf74uGl0PtStbcP2mEzSvjxv" }
}
),
axios.get(
`https://api.propublica.org/congress/v1/116/house/members.json`,
{
headers: { "X-API-Key": "9wGKmWl3kNiiSqesJf74uGl0PtStbcP2mEzSvjxv" }
}
)
]).then(([rest1, rest2]) => {
this.setState({
fedSens: rest1,
fedReps: rest2
});
});
}
render() {
if (this.state.fedReps.length <= 0)
return (
<div>
<span>Loading...</span>
</div>
);
else {
console.log(this.state.fedReps);
return <div>test</div>;
}
}
}
Your problem arises from mixing lambda function declarations ((...) => { ... }) and traditional function declarations (function (...) { }).
A lambda function will inherit this from where it was defined but a traditional function's this will be isolated from the context of where it was defined. This is why it is common to see var self = this; in legacy-compatible code because this usually didn't match what you wanted it to.
Here is an example snippet demonstrating this behaviour:
function doSomething() {
var anon = function () {
console.log(this); // 'this' is independent of doSomething()
}
var lambda = () => {
console.log(this); // inherits doSomething's 'this'
}
lambda(); // logs the string "hello"
anon(); // logs the 'window' object
}
doSomething.call('hello')
Solution
So you have two approaches available. Use whichever you are comfortable with.
Option 1: Use a lambda expression
app.auth().onAuthStateChanged(function(user) {
to
app.auth().onAuthStateChanged((user) => {
Option 2: Assign a "self" variable
const items = [];
app.auth().onAuthStateChanged(function(user) {
// ...
this.setState({ userInfo: items });
}
to
const items = [];
const component = this; // using "component" makes more sense than "self" in this context
app.auth().onAuthStateChanged(function(user) {
// ...
component.setState({ userInfo: items });
}

MobX don't update react DOM in fetch promise callback

I am trying to update a react dom by changing an observable mobx variable inside a fetch callback in a react typescript app but mobx don't show any reaction on variable change.
I define my variable like this:
#observable data:any = []
and in my constructor i change data value:
constructor(){
this.data.push(
{
count:0,
dateTime:'2017'
})
this.getData();
}
its work fine and update dom properly as expected.
in getData() method i write a fetch to retrive data from server :
#action getData(){
this.data.push(
{
count:1,
dateTime:'2018'
})
fetch(request).then(response=>response.json())
.then(action((data:Array<Object>)=>{
this.data.push(data)
console.log(data)
}));
}
so my view now shows 2 value the 2017 and 2018 object data but the 2019 data that I get from the server is not showing. the log shows the correct values and variable filled in a right way but mobx don't update view after I set any variable in fetch function callback and I don't know why?
p.s: I do the same in ECMA and there was no problem but in typescript mobx act differently
Check my approach:
import { action, observable, runInAction } from 'mobx'
class DataStore {
#observable data = null
#observable error = false
#observable fetchInterval = null
#observable loading = false
//*Make request to API
#action.bound
fetchInitData() {
const response = fetch('https://poloniex.com/public?command=returnTicker')
return response
}
//*Parse data from API
#action.bound
jsonData(data) {
const res = data.json()
return res
}
//*Get objects key and push it to every object
#action.bound
mapObjects(obj) {
const res = Object.keys(obj).map(key => {
let newData = obj[key]
newData.key = key
return newData
})
return res
}
//*Main bound function that wrap all fetch flow function
#action.bound
async fetchData() {
try {
runInAction(() => {
this.error = false
this.loading = true
})
const response = await this.fetchInitData()
const json = await this.jsonData(response)
const map = await this.mapObjects(json)
const run = await runInAction(() => {
this.loading = false
this.data = map
})
} catch (err) {
console.log(err)
runInAction(() => {
this.loading = false
this.error = err
})
}
}
//*Call reset of MobX state
#action.bound
resetState() {
runInAction(() => {
this.data = null
this.fetchInterval = null
this.error = false
this.loading = true
})
}
//*Call main fetch function with repeat every 5 seconds
//*when the component is mounting
#action.bound
initInterval() {
if (!this.fetchInterval) {
this.fetchData()
this.fetchInterval = setInterval(() => this.fetchData(), 5000)
}
}
//*Call reset time interval & state
//*when the component is unmounting
#action.bound
resetInterval() {
if (this.fetchInterval) {
clearTimeout(this.fetchInterval)
this.resetState()
}
}
}
const store = new DataStore()
export default store
as #mweststrate mentioned in the comments, it was an observer problem and when I add #observer on top of my react class the problem get fixed

React native not waiting for response from API before continuing

I have just started playing about with react native and I have a problem that functions aren't waiting for responses before continuing.
So in Chrome my console log displays:
userStore
this state contents
returned data from api / userstore [object Object]
Basically getUserDetails is executed and in that time while the api is being called the setData function runs, and it completes before the api result has been returned.
I would like the getUserDetails functio to complete before setData is called.
I have had a look at resources online, but am at a loss. The code I am using is below (This has been stripped down for ease of reading nb. I am using mobx)
UserScreen.js
constructor (props) {
super(props);
this.state = {
data: null
};
}
async componentDidMount() {
this.props.commonStore.setLoading(true);
await this.props.userStore.getUserDetails('1');
this.setData();
this.props.commonStore.setLoading(false);
}
setData() {
this.setState({
userDetails: this.props.userStore.userDetails
});
console.log('userStore' + this.props.userStore.userDetails)
console.log('this state contents '+ this.state.userDetails);
}
render () {
if(this.props.commonStore.isLoading===false) {
return (<View><Text>Ready!!</Text></View>)
}else{}
return (<View><Text>Loading</Text></View>)
}
}
UserStore.js
#action getUserDetails = (userID) => {
axios.get('http://192.168.1.9/user/' + userID)
.then(response => {
console.log('returned data from api / userstore ' +response.data.user);
this.userdetails = response.data.user;
}).catch(error => {
console.log(error);
this.error = error
}) }
Thanks
If you have stumbled upon the beauty of Mobx, you need to move towards a stateless solution i.e.:
UserScreen.js
componentDidMount() {
this.getUserDetails();
}
async getUserDetails(){
await this.props.UserStore.getUserDetails('1');
}
render () {
const { isLoading, userDetails, error } = this.props.UserStore
return (<View>
{(!!isLoading)?<Text>{userDetails}</Text>:<Text>Loading</Text>}
</View>)
}
UserStore.js
#observable userdetails = {};
#observable isLoading = false;
#observable error = {};
async getUserDetails(userID) {
this.isLoading = true;
try {
await axios.get('http://192.168.1.9/user/' + userID)
.then(response => {
console.log('returned data from api / userstore '+response.data.user);
this.userdetails = response.data.user;
this.isLoading = false;
})
.catch(error => {
console.log(error);
this.error = error
})
} catch (e) {
console.log('ERROR', e);
this.isLoading = false;
}
}
As you are passing the data into an observable array i.e. #observable userdetails = {}; Mobx will automatically update the state, once the promise / await is complete.
P.S. Mobx rules OK!! :o)

Resources