RN & Redux: Unable to put an object into state in the reducer - react-native

I'm getting data from the backend and want to store it in the state.
The server sends some user data (id, mail, name,.. ) as an object. This object I want to store as an attribute of the state object.
Here is the code,
starting with auth.js (action-file):
//loadUser(), which gets called via useEffect in App.js:
export const loadUser = () => async dispatch => {
try {
const token = await AsyncStorage.getItem('token')
await setToken(token)
const res = await axios.get(`${SERVER_IP}/api/auth`)
console.log('res.data', res.data) // see result of clog below
dispatch({
type: USER_LOADED,
payload: res.data
})
} catch (err) {
dispatch({
type: AUTH_ERROR
})
}
}
this is the result of console.log('res.data', res.data), which gets passed on to the reducer. This is also what I want:
Object {
"__v": 0,
"_id": "5d2b422322cdf413d4246566",
"avatar": "//www.gravatar.com/avatar/e14a77efcd408a95332f403e0db40b95?s=200&r=pg&d=mm",
"date": "2019-07-14T14:54:27.265Z",
"email": "awnwen#asdw.de",
"name": "awena",
}
This is what the reducer looks like:
// reducer:
import {
USER_LOADED,
AUTH_ERROR,
} from '../actions/types'
import {getToken, setToken, removeToken} from '../actions/auth'
const initialState = {
token: getToken(),
isAuthenticated: null,
loading: true,
user: null
}
export default async function (state = initialState, action) {
const { type, payload } = action
switch (type) {
case USER_LOADED:
return {
...state,
user: payload,
isAuthenticated: true,
loading: false
}
case AUTH_ERROR:
await removeToken()
return {
...state,
isAuthenticated: false,
token: null,
user: null,
loading: false
}
default:
return state
}
}
In case they are needed: token functions from auth.js actions-file (first file posted)
export const getToken = async () => {
try {
return await AsyncStorage.getItem('token')
} catch (error) {
console.log("Tokenerror: getToken() -" + error);
return null
}
}
export const setToken = async (token) => {
try {
await AsyncStorage.setItem('token', token);
setAuthToken(token)
} catch (error) {
console.log("Tokenerror: setToken() - " + error);
return null
}
}
export const removeToken = async () => {
try {
return await AsyncStorage.removeItem('token')
} catch (error) {
console.log("Tokenerror: removeToken() - " + error);
return null
}
}
I expect the auth state to be like:
{
auth: {
token: 'randomstringofnumbersandletters',
isAuthenticated: true,
loading: false,
user: {
name: 'john',
email: 'jdoe#gmail.com',
.....
}
}
}
This is what RND shows in auth state:
{
auth: {
_40: 0,
_65: 0,
_55: null,
_72: null
}
}
This is my first issue I am posting on stackoverflow, it says I should add more text because the text to code ratio is too bad.. Please feel free to ask questions if you need more information to help me resloving this issue.
Many thanks in advance, guys ;)
EDIT1:
As requested I console logged the payload in the reducer after the USER_LOADED case.
switch (type) {
case USER_LOADED:
console.log('payload: ', payload)
return {
...state,
user: payload,
isAuthenticated: true,
loading: false
}
It is returning the same object:
payload: Object {
"__v": 0,
"_id": "5d2b422322cdf413d4246566",
"avatar": "//www.gravatar.com/avatar/e14a77efcd408a95332f403e0db40b95?s=200&r=pg&d=mm",
"date": "2019-07-14T14:54:27.265Z",
"email": "awnwen#asdw.de",
"name": "awena",
}

The problem could be solved by removing "async" from
export default async function (state = initialState, action) {...}
in the reducer.
Now I get the user in state.
But the token in the auth state is not a string but an object/Promise.
This is what I get from logging the token property in the auth state object:
state.token Promise {
"_40": 0,
"_55": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNWQyYjc2MzRmN2U1MjcwOGUwNjNmZThmIn0sImlhdCI6MTU2MzEyOTM5NiwiZXhwIjoxODYzMTI5Mzk2fQ.YQayK3HBXIFl6aFSfkojTF8WrZkxXCGMf36Z0rXvRjY",
"_65": 1,
"_72": null,
}
I am not very experienced with promises. Is there a way to only store the value of the token as a string in my state?
I could build and ugly workaround and extract the value of the key with _55.
But on the one hand I don't understand what those keys in the promise are AND I could imagine the keys to change from one client to another.

Related

Urql config with guest token for SSG on next js

So I have a project using the latest Next js 13, React 18, Urql 3, and using typescript
Currently, I have issues when trying to query the urql from the getstaticprops function. My urql request needs a guest token, and I'm storing the token on session storage(other suggestions ?).
It has no issue when the query is running on the client, but I have it when querying inside the function.
My concern is related to the token reading, so the server cannot read the session storage value.
I'm asking what is the better and simplest way to make this work.
Does use cookies to store guest tokens will make this work?
Or the configuration that doesn't work?
This is my current config for urql.ts
import {
createClient,
ssrExchange,
dedupExchange,
cacheExchange,
fetchExchange,
} from "urql";
import { GRAPH_URL } from "#lib/constant/env";
import type { TypedDocumentNode } from "#urql/core";
const isServerSide = typeof window === "undefined";
const ssrCache = ssrExchange({
isClient: !isServerSide,
});
const client = createClient({
url: GRAPH_URL,
exchanges: [dedupExchange, cacheExchange, ssrCache, fetchExchange],
fetchOptions: () => {
const token = sessionStorage.getItem("accessToken");
return {
headers: {
authorization: token ? `Bearer ${token}` : "",
},
};
},
});
const query = async (
query: TypedDocumentNode<any, object>,
variables?: Record<string, string | string[] | unknown>
) => {
try {
const response = await client.query(query, variables as any).toPromise();
return response;
} catch (error) {
if (error instanceof Error) console.error(error.message);
}
};
const mutation = async (
mutation: TypedDocumentNode<any, object>,
variables?: Record<string, string | string[] | unknown>
) => {
try {
const response = await client
.mutation(mutation, variables as any)
.toPromise();
return response;
} catch (error) {
if (error instanceof Error) console.error(error.message);
}
};
export { client, query, mutation, ssrCache };
And this some of the code for the blog index page
export const getStaticProps = async () => {
await fetchArticlesSummary();
return {
props: {
urqlState: ssrCache.extractData(),
},
revalidate: 600,
};
};
export default withUrqlClient(() => ({
url: GRAPH_URL,
}))(BlogPage);
This is for the fetchArticlesSummary
export const fetchArticlesSummary = async () => {
try {
const {
data: { listArticles },
}: any = await query(getListArticle);
return listArticles.items;
} catch (error) {
return {
notFound: true,
};
}
};
I also doing a setup on _app.tsx
export default function App({ Component, pageProps }: AppProps) {
if (pageProps.urqlState) {
ssrCache.restoreData(pageProps.urqlState);
}
return (
<Provider value={client}>
<Component {...pageProps} />
</Provider>
);
}
Thank you
I have followed urql documentation about server-side configuration and many others but still don't have any solutions.

Successful GraphQL Mutation keeps Returning Undefined

I am sending a mutation over from a React Native Frontend to a NodeJs / GraphQL Backend. The mutation request goes through, and a mutation occurs, but back on the frontend, the value of that mutation is undefined when it should instead be returning an ID. My mutation looks like this...
export default {
Mutation: {
driverCreateCollisionAccident: async (_, {
accidentId,
specific_pictures,
contact_info,
collision_report,
extra_info
}, context) => {
const driver = await checkDriverAuth(context)
const foundAccident = await db.accident.findUnique({
where: {
id: accidentId
}
})
if (!foundAccident) {
throw new Error("Accident does not exist")
}
await handleDriverAccidentOwnership(driver.id, accidentId)
console.log("right before create collision mutation")
try {
return await db.collisionAccident.create({
data: {
specific_pictures: specific_pictures,
contact_info: contact_info,
collision_report: collision_report,
extra_info: extra_info,
accident: {
connect: {
id: accidentId
}
},
accidentId: accidentId
}
}).then( (resolved) => {
console.log(resolved)
return resolved
})
} catch (error) {
console.log(error)
throw new Error(error)
}
}
}
}
The most important part of that code is
.then( (resolved) => {
console.log(resolved)
return resolved
})
As the console.log returns exactly what it is supposed to, an object that looks like this...
id: '81b7cc7c-53d7-43d7-bb14-1438ef53a227',
specific_pictures: { 'Pic One': 'Test url' },
contact_info: {
address: 'Have Picture',
lastname: 'Have Picture',
firstname: 'Have Picture',
phone_number: '123456789',
insurance_provider: 'Have Picture',
driver_license_number: 'Have Picture',
insurance_policy_number: 'Have Picture'
},
extra_info: 'null',
collision_report: {
towed: true,
legal_fault: 'I caused the accident',
fire_or_explode: true
},
accidentId: '0b5fd832-9540-475e-9b34-ece6dfdc58df'
}
But for some reason, when I try to log the results of this mutation on the frontend, all I get is undefined, but no errors occur, and I still get the backend's console.logs to hit properly so the mutation itself is working. My front end code looks like this...
const handleSubmit = () => {
handleMutation().then( (resolved) => {
console.log(resolved)
})
}
const handleMutation = async () => {
await driverCreateCollisionAccident({
variables: {
accidentId: collisionData.accidentId,
specific_pictures: collisionData.specific_pictures,
contact_info: collisionData.contact_info,
collision_report: collisionData.collision_report,
extra_info: collisionData.extra_info,
}
})
}
I don't even need the full object returned, I JUST need an ID. Does anyone see what could be going wrong here?
Maybe you can try something like this
const result = await db.collisionAccident.create({
data: {
specific_pictures: specific_pictures,
contact_info: contact_info,
collision_report: collision_report,
extra_info: extra_info,
accident: {
connect: {
id: accidentId
}
},
accidentId: accidentId
}
})
return result
instead of
return await db.collisionAccident.create({
data: {
specific_pictures: specific_pictures,
contact_info: contact_info,
collision_report: collision_report,
extra_info: extra_info,
accident: {
connect: {
id: accidentId
}
},
accidentId: accidentId
}
}).then( (resolved) => {
console.log(resolved)
return resolved
})

How can I store the current user id in the Vue store?

I wish to save the UserId when logging in, however, I cannot access the store using this.$store from the store itself, presumably because it is not a Vue component but a JS file.
EDIT: Solution based on an anwer in the code below
export default createStore({
state: {
user: null,
userId: null
},
mutations: {
setUser(state, user) {
state.user = user;
console.log("User set in store");
},
setUserId(state, userId) {
state.userId = userId;
console.log("User ID set in store");
}
},
getters: {
getUser: state => {
console.log('Retrieving user...')
return state.user;
}
},
actions: {
async login(store, credentials) {
let response = await(await fetch("http://localhost:4000/api/login", {
method: "POST",
body: JSON.stringify(credentials),
credentials: "include",
})).json();
store.commit('setUserId', response.userId);
},
},
});```
Your actions have access to the store reference via parameter async login(store, credentials)
So you can call the mutation setUserId from store reference when you get a response
.then(function (response) {
store.commit('setUserId', response.json().id);
})

Why am I getting data undefined when I can see the data?

I have been staring at these action creators:
import * as types from './constants';
import * as endpoints from 'endpoints';
import * as requester from 'services/Requester';
import * as helpers from 'account-settings/helpers/data-helpers';
export function fetchPrefences({Key}) {
return dispatch => {
const url = `${endpoints.v2.INDIVIDUALS}/${Key}/preferences`;
requester.sendGet(url).then(data => {
const payload = helpers.sortPreferences(data);
dispatch({
type: types.SET_USER_PREFERENCES,
payload,
});
});
};
}
export function fetchTopics() {
return dispatch => {
requester.sendGet(endpoints.TOPICS_OF_CONCERN).then(data => {
dispatch({
type: types.SET_USER_TOPICS,
payload: data.Items,
});
});
};
}
export function handleStateChange(payload) {
return {
type: types.SET_NEW_PREFERENCES,
payload,
};
}
export function handleUpdateTopics({topics, involved}, updateBoth = false) {
return dispatch => {
return requester
.sendPut(endpoints.TOPICS_OF_CONCERN, {
Items: topics,
})
.then(data => {
dispatch({
type: types.SET_USER_TOPICS,
payload: data.Items,
});
if (updateBoth) {
dispatch(handleUpdatePreferences({involved}));
}
});
};
}
export function handleUpdateGetInvoved({involved}) {
return (dispatch, getState) => {
const {auth} = getState();
//prettier-ignore
const url = `${endpoints.v2.INDIVIDUALS}/${auth.user.Key}/preferences`;
return requester
.sendPut(url, {
Items: involved,
})
.then(data => {
const payload = helpers.sortPreferences(data);
dispatch({
type: types.SET_USER_PREFERENCES,
payload,
});
});
};
}
And it's clear I am getting data as undefined with this message:
What is not clear is why? When I do a curl, the data is there:
{"items":[{"category":"None","key":"2883040c-88b8-4899-bd47-114a560d085b","displayText":"Energy
Costs","isSelected":false,"order":1},{"category":"None","key":"a745a3d6-0f64-4595-8734-6082d9c914f7","displayText":"Regulations","isSelected":false,"order":7},{"category":"None","key":"51797a61-8016-4817-a46e-72dee3d8239a","displayText":"Minimum
Wage","isSelected":false,"order":5},{"category":"None","key":"381e24d0-2668-4a69-a993-7d5e1ecaec3b","displayText":"Taxes","isSelected":false,"order":8},{"category":"None","key":"dfaf22cb-111a-46f3-bce3-93fbf4a91490","displayText":"Unemployment
Insurance","isSelected":false,"order":9},{"category":"None","key":"c55b5d2a-a0f3-4c35-bf59-b433259b2059","displayText":"Workers
Compensation","isSelected":false,"order":10},{"category":"None","key":"d4b787d4-550b-4866-a5cc-c6a2de61a91a","displayText":"Healthcare","isSelected":false,"order":4},{"category":"None","key":"c2557854-421d-4b2f-810f-caadf938cded","displayText":"Government
Spending","isSelected":false,"order":3},{"category":"None","key":"cf91f638-c5fa-4252-be01-dce504ae369d","displayText":"Private
Property
Rights","isSelected":false,"order":6},{"category":"None","key":"0eae5ccf-2ba5-41bd-9111-efe7acafa512","displayText":"Finding
Qualified Employees","isSelected":false,"order":2}]}%
In Swagger, I check, the data is there:
{
"items": [
{
"category": "None",
"key": "2883040c-88b8-4899-bd47-114a560d085b",
"displayText": "Energy Costs",
"isSelected": false,
"order": 1
},
{
"category": "None",
"key": "a745a3d6-0f64-4595-8734-6082d9c914f7",
"displayText": "Regulations",
"isSelected": false,
"order": 7
},
{
"category": "None",
"key": "51797a61-8016-4817-a46e-72dee3d8239a",
"displayText": "Minimum Wage",
"isSelected": false,
"order": 5
},
{
"category": "None",
"key": "381e24d0-2668-4a69-a993-7d5e1ecaec3b",
"displayText": "Taxes",
"isSelected": false,
"order": 8
},
{
"category": "None",
"key": "dfaf22cb-111a-46f3-bce3-93fbf4a91490",
"displayText": "Unemployment Insurance",
"isSelected": false,
"order": 9
},
{
"category": "None",
"key": "c55b5d2a-a0f3-4c35-bf59-b433259b2059",
"displayText": "Workers Compensation",
"isSelected": false,
"order": 10
},
{
"category": "None",
"key": "d4b787d4-550b-4866-a5cc-c6a2de61a91a",
"displayText": "Healthcare",
"isSelected": false,
"order": 4
},
{
"category": "None",
"key": "c2557854-421d-4b2f-810f-caadf938cded",
"displayText": "Government Spending",
"isSelected": false,
"order": 3
},
{
"category": "None",
"key": "cf91f638-c5fa-4252-be01-dce504ae369d",
"displayText": "Private Property Rights",
"isSelected": false,
"order": 6
},
{
"category": "None",
"key": "0eae5ccf-2ba5-41bd-9111-efe7acafa512",
"displayText": "Finding Qualified Employees",
"isSelected": false,
"order": 2
}
]
}
I noticed that in the code the items property was written as Items, I tried to change it to items to match the data property, that did nothing.
A colleague suggested the issue could be in the requester object, I do have a question about it too:
import axios from 'axios';
import LocalStorage from './LocalStorage';
import env from 'env';
import * as appcenter from 'utils/appcenterLogger';
import * as titlesHelper from 'utils/titleCaser';
let expired = false;
export const instance = axios.create({
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
NFIBAppID: env.NFIBAppID,
},
});
let preHeaders = {};
async function mergeConfig(config) {
try {
const access = await LocalStorage.get('access');
preHeaders = access;
return {...config, headers: {...access}};
} catch (error) {
return {...config};
}
}
export async function sendGet(url, config = {}) {
if (expired) {
return;
}
const now = new Date();
return instance
.get(url, await mergeConfig(config))
.then(response => {
return saveHeaders(response, now, url);
})
.catch(error => {
return catchErros(error, now, url);
});
}
export async function sendPost(url, data, config = {}) {
if (expired) {
return;
}
const now = new Date();
return instance
.post(url, titlesHelper.lowerCaser(data), await mergeConfig(config))
.then(response => {
console.log(response);
return saveHeaders(response, now, url);
})
.catch(error => {
return catchErros(error, now, url);
});
}
export async function sendPut(url, data, config = {}) {
if (expired) {
return;
}
const now = new Date();
return instance
.put(url, titlesHelper.lowerCaser(data), await mergeConfig(config))
.then(response => {
return saveHeaders(response, now, url);
})
.catch(error => {
return catchErros(error, now, url);
});
}
export async function sendPatch(url, data, config = {}) {
if (expired) {
return;
}
const now = new Date();
return instance
.patch(url, data, await mergeConfig(config))
.then(response => {
return saveHeaders(response, now, url);
})
.catch(error => {
return catchErros(error, now, url);
});
}
export async function sendDelete(url, data, config = {}) {
if (expired) {
return;
}
const now = new Date();
return instance
.delete(url, await mergeConfig(config))
.then(response => {
return saveHeaders(response, now, url);
})
.catch(error => {
return catchErros(error, now, url);
});
}
export function saveHeaders({data, headers}, timeSent, url) {
try {
if (headers && headers.authorizationtoken) {
LocalStorage.save('access', {
AuthorizationToken: headers.authorizationtoken,
});
}
const timeReceived = new Date();
LocalStorage.save('lastTimeRequestSent', timeReceived);
appcenter.trackRequestTiming(timeSent, timeReceived, headers, url, false);
return titlesHelper.toTitleCase(data);
} catch (_e) {
return false;
}
}
function catchErros({error, timeSent}, url) {
try {
const timeReceived = new Date();
LocalStorage.save('lastTimeRequestSent', timeReceived);
appcenter.trackRequestTiming(timeSent, timeReceived, error, url, true);
if (error && error.response) {
saveHeaders({
headers: preHeaders,
});
const {data} = error.response;
const message = data.message || data.Message;
if (message.includes('TokenExpired')) {
expired = true;
}
}
return Promise.reject(titlesHelper.toTitleCase(error.response.data));
} catch (_e) {
return error;
}
}
export function resetTokenExpired() {
expired = false;
}
I am seeing Promise syntax being mixed with async/await syntax, could this be causing an issue?
I tried to see if perhaps the issue was with the authorization token, so I console logged it:
let preHeaders = {};
async function mergeConfig(config) {
try {
const access = await LocalStorage.get('access');
console.log(access);
preHeaders = access;
return {...config, headers: {...access}};
} catch (error) {
return {...config};
}
}
but I am successfully getting that back:
{AuthorizationToken: "<bunch-o-numbers>"}
What I know at this point is that without the logic inside of the saveHeaders() function, a registered users' password will return undefined.
To complicate things, this application uses action helpers, which I have never implemented, but I see that Items property in there all over the place, keep in mind that the original warning read Items, but I changed it everywhere to items to make it match the JSON item property in the hope that would be the fix.
However, I have now come across these action helper files with the following code, action-helpers.js:
import * as endpoints from 'endpoints';
import * as requester from 'services/Requester';
import compareDesc from 'date-fns/compare_desc';
export async function fetchTransaction() {
try {
const response = await requester.sendGet(endpoints.TRANSACTIONS);
const {Items = []} = response;
return Items.sort((a, b) => compareDesc(a.DateTime, b.DateTime));
} catch (error) {
return [];
}
}
and in data-helpers.js:
export function sortPreferences(data) {
const sorted = data.Items.sort((a, b) => a.Order - b.Order);
const communications = sorted.filter(
p => p.Category === 'CommunicationPreferences'
);
const privacy = sorted.filter(p => p.Category === 'MemberPrivacy');
const involved = sorted.filter(p => p.Category === 'GetInvolved');
const format = data.EmailFormatType === 'HTML' ? 'HTML' : 'Plain Text';
return {
communications,
privacy,
involved,
emailFormatType: format,
isEmailAllowed: data.IsEmailAllowed,
isPhoneAllowed: data.IsPhoneAllowed,
};
}
Most probably you are not getting the expected response from your requester function.
Try logging the response from the requester and see the output. You might have to use response.json() to resolve the promise correctly. This is assuming your requester class/function works like that.

Catch Axios exception in Vuex store and throw it to Vue.js method

How to catch axios exceptions in vuex store and throw it to vue.js method ? My goal is to get this exception to be able to reset computed values bound to input using this.$forceUpdate().
In my method, I have this:
methods: {
mymet: _.debounce(
function(table, id, key, event) {
const value = event.target.value;
this.$store.dispatch('UPDATE_TRANSACTIONS_ITEM', { table, id, key, value }).then(response => {
event.target.classList.remove("is-invalid")
event.target.classList.add("is-valid")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
this.$forceUpdate();
})
}, 500
)
}
In my vuex store, I have this:
const actions = {
UPDATE_TRANSACTIONS_ITEM ({ commit }, data) {
let company = {
[data.key]: data.value
}
axios.put(`/api/companies/${data.id}`, { company }).then( function ( response ) {
commit('SET_TRANSACTIONS_ITEM_UPDATE', { profile: data })
}).catch(function (error) {
throw error
})
}
}
const mutations = {
SET_TRANSACTIONS_ITEM_UPDATE (state, { profile }) {
state.company_data[profile.key] = profile.value
},
}
You need to make the actual action function asynchronous.
If you have the ability to use async functions, you can just await the axios call, and let the error bubble up (no need to throw anything in the action itself):
const actions = {
async UPDATE_TRANSACTIONS_ITEM ({ commit }, data) {
let company = {[data.key]: data.value};
await axios.put(`/api/companies/${data.id}`, { company }).then(() => {
commit('SET_TRANSACTIONS_ITEM_UPDATE', { profile: data })
});
}
}
Otherwise, you'll need to return a Promise and catch the error and pass it to the reject handler:
const actions = {
UPDATE_TRANSACTIONS_ITEM ({ commit }, data) {
return new Promise((resolve, reject) => {
let company = {[data.key]: data.value};
axios.put(`/api/companies/${data.id}`, { company }).then(() => {
commit('SET_TRANSACTIONS_ITEM_UPDATE', { profile: data });
resolve();
}, (error) => reject(error));
});
}
}