React how to render async data from api? - javascript

I am using preact(light version of react) but syntax is almost the same. I am having an issue displaying verified after setting state from promise result. This is my container component:
import { h, Component } from "preact";
import { VerifierService } from "services/verifierService";
var CONFIG = require("Config");
//import * as styles from './profile.css';
interface PassportProps { token?: string; path?: string }
interface PassportState { appId?: string; verified?: boolean }
export default class Passport extends Component<PassportProps, PassportState> {
constructor(props) {
super(props);
this.state = { appId: CONFIG.Settings.AppId };
}
async componentDidMount() {
console.log("cdm: " + this.props.token);
if (this.props.token != undefined) {
await VerifierService.post({ token: this.props.token })
.then(data => {
this.setState({ verified: data.result });
console.log(JSON.stringify(data, null, 4));
})
.catch(error => console.log(error));
}
}
render() {
return <div>Test: {this.state.verified}</div>;
}
}
I can see console.log as true inside of promise result, but i can't display it in view.

Your data in your console.log is true, so therefor data.result will give you undefined. Try to just set the data in setState.
await VerifierService.post({ token: this.props.token })
.then(data => {
this.setState({ verified: data });
console.log(JSON.stringify(data, null, 4));
})
.catch(error => console.log(error));

Related

Not able to get the id of the generated firebase document

I'm trying to get the id of the generated firebase document, and I'm using addDoc to create a new doc.
I'm generating a new document on button click and that button calls the initializeCodeEditor function.
Anyone please help me with this!
Button Code:
import { useNavigate } from "react-router-dom"
import { useAuthContext } from "../../hooks/useAuthContext"
import { useFirestore } from "../../hooks/useFirestore"
import Button from "./Button"
const StartCodingButton = ({ document, setIsOpen }) => {
const { user } = useAuthContext()
const { addDocument, response } = useFirestore("solutions")
const navigate = useNavigate()
const initializeCodeEditor = async () => {
await addDocument({
...document,
author: user.name,
userID: user.uid,
})
if (!response.error) {
console.log(response.document) // null
const id = response?.document?.id; // undefined
navigate(`/solution/${id}`, { state: true })
}
}
return (
<Button
className="font-medium"
variant="primary"
size="medium"
onClick={initializeCodeEditor}
loading={response.isPending}
>
Start coding online
</Button>
)
}
export default StartCodingButton
addDocument code
import { useReducer } from "react"
import {
addDoc,
collection,
doc,
Timestamp,
} from "firebase/firestore"
import { db } from "../firebase/config"
import { firestoreReducer } from "../reducers/firestoreReducer"
const initialState = {
document: null,
isPending: false,
error: null,
success: null,
}
export const useFirestore = (c) => {
const [response, dispatch] = useReducer(firestoreReducer, initialState)
// add a document
const addDocument = async (doc) => {
dispatch({ type: "IS_PENDING" })
try {
const createdAt = Timestamp.now()
const addedDocument = await addDoc(collection(db, c), {
...doc,
createdAt,
})
dispatch({ type: "ADDED_DOCUMENT", payload: addedDocument })
} catch (error) {
dispatch({ type: "ERROR", payload: error.message })
}
}
return {
addDocument,
response,
}
}
firestoreReducer
export const firestoreReducer = (state, action) => {
switch (action.type) {
case "IS_PENDING":
return { isPending: true, document: null, success: false, error: null }
case "ADDED_DOCUMENT":
return { isPending: false, document: action.payload, success: true, error: null }
}
throw Error("Unknown action: " + action.type)
}
I have recreated this issue and found out this is happening because the response object in the useFirestore hook is not being updated until the next render cycle.
In order to get the updated response object, you can use the useEffect hook to trigger an update to the component whenever the response object changes.
So I recommend you to call initializeCodeEditor and make your app wait until response object change I used useEffect here
const initializeCodeEditor = async () => {
await addDocument({
author: user.name,
userID: user.uid,
})
//skip following if block it's just for understanding
if (!response.error) {
console.log(response.document) // will obviously be null here as at first it is set null
const id = response?.document?.id; // will obviously be undefined
navigate(`/solution/${id}`, { state: true })
}
}
useEffect(() => {
if (!response.error) {
setId(response?.document?.id);
console.log("From App.js useEffect: " + response?.document?.id); // getting the document id here too
}
}, [response])
//and in firestoreReducer
case "ADDED_DOCUMENT":{
console.log("from Reducer: " + action.payload.id); //getting the document id here
return { isPending: false, document: action.payload, success: true, error: null }
}
OR you can use callback also without introducing useEffect like this:
const initializeCodeEditor = async () => {
await addDocument({
author: user.name,
userID: user.uid,
}, (response) => {
console.log("From App: " + response?.document?.id); //Will run as callback
if (!response.error) {
setId(response?.document?.id);
}
})
}
This way, the callback function will be called after the addDocument function has completed and the response object will have the updated document id.

Retrieve data from Firestore, and setState

export class Diet extends Component {
constructor(props) {
super(props);
this.state = {
loaded: false,
Ft: 0,
};
}
async componentDidMount() {
const id = firebase.auth().currentUser.uid;
await firebase.firestore
.collection("users")
.doc(id)
.get()
.then(function (doc) {
if (doc.exists) {
this.setState({
Ft: users.Ft,
});
} else {
alert("error");
}
});
}
Hello, I am trying to retrieve the Ft from my Firestore document and store the value in this.state, so afterward I can use it in an expression later on on the page, any idea on what I'm doing wrong?
Error: [Unhandled promise rejection: TypeError: _firebase.default.firestore.collection is not a function. (In '_firebase.default.firestore.collection("users")', '_firebase.default.firestore.collection' is undefined)]
I think you're looking for
async componentDidMount() {
const id = firebase.auth().currentUser.uid;
firebase.firestore()
.collection("users")
.doc(id)
.get()
.then(function (doc) {
if (doc.exists) {
this.setState({
Ft: doc.data().Ft
});
} else {
alert("error");
}
});
}
You can try consoling your returned data to know what firestore is returning. Another mistake that you're doing is that you're using await together with then/catch and they don't work together in the same function. Run this snippet to correct the mistake and check the console for what firestore is actually returning.
export class Diet extends Component {
constructor(props) {
super(props);
this.state = {
loaded: false,
Ft: 0,
};
}
async componentDidMount() {
const id = firebase.auth().currentUser.uid;
firebase.firestore
.collection("users")
.doc(id)
.get()
.then(function (doc) {
if (doc.exists) {
console.log(doc.data());
//As you're expecting to get Ft then you can set the state like this
this.setState({
Ft: doc.data().Ft
});
} else {
alert("error");
}
});
}}
or use try/catch
export class Diet extends Component {
constructor(props) {
super(props);
this.state = {
loaded: false,
Ft: 0,
};
}
async componentDidMount() {
const id = firebase.auth().currentUser.uid;
let fetchedData = await firebase.firestore.collection("users").doc(id).get()
try {
if(fetchedData.exists){
console.log(fetchedData.data());
//As you're expecting to get Ft then you can set the state like this
this.setState({
Ft: doc.data().Ft
});
}
} catch (error) {
alert("error", error);
}
}
}

React - Loading data before render

I'm new to react and I have a question about a best practice that sees me make a mistake .
I call an API to retrieve information and modify an array in the state once the response is returned by the API. In the "render" I have to retrieve the information from this array (when it is completed) or it sends me back an error because the array is empty when the render is initialized.
class MyClass extends React.Component {
constructor(props) {
super(props)
this.state = {
activeIndex: 0,
items: []
}
}
componentDidMount() {
axios
.get(`API_ADDRESS`, {
headers: {
Authorization: `Token XXX`,
},
})
.then(function(response) {
this.setState({
items: response.results,
})
})
.catch(error => {
notification.warning({
message: error.code,
description: error.message,
})
})
}
changeDialog = (e, index) => {
e.preventDefault()
this.setState({
activeIndex: index,
})
}
render() {
const { activeIndex, items } = this.state
const {
first_name: firstName,
last_name: lastName,
phone,
email,
address,
} = items[activeIndex]
The error indicates :
TypeError: _items$activeInde is undefined
How can I solve this error related to data loading? (trying to keep the destrying elements method)
Thanks a lot
Eliott
Because API that you fetch from server is async. The first time render of Component, data that you setState in axios still not yet updated, it just updated when Component render the second time.
So you must check state in render Component like this to make sure that if activeIndex is defined then declare variable with items[activeIndex] :
activeIndex && const {
first_name: firstName,
last_name: lastName,
phone,
email,
address,
} = items[activeIndex]
Two issues:
beware of this inside the Promise returned by axios. You use function(){} so the this inside is not the component's instance. Change it to an arrow function.
add a guard so you won't destructure undefined when activeIndex points to an item element that is not there (which happens in the initial loading before the axios fetches the data).
Fix:
// ... (code not shown remains unmodified)
componentDidMount() {
axios
.get(`API_ADDRESS`, {
headers: {
Authorization: `Token XXX`,
},
})
.then(response => { // changed this line
this.setState({
items: response.results,
})
})
// ... (code not shown remains unmodified)
render() {
const { activeIndex, items } = this.state
if (!items[activeIndex]) { // added this line
return <div>Hold tight while items are being fetched...</div>; // added this line
} // added this line
const {
first_name: firstName,
// ... (code not shown remains unmodified)
just change your component like so:
constructor(props) {
super(props)
this.state = {
activeIndex: 0,
items: [],
isFetching: false
}
}
componentDidMount() {
// staring your fetching
this.setState({isFetching: true});
axios
.get(`API_ADDRESS`, {
headers: {
Authorization: `Token XXX`,
},
})
.then(function(response) {
// finish fetching when your response is ready :)
this.setState({
items: response.results,
isFetching: false
});
})
.catch(error => {
// finish fetchnig
this.setState({isFetching: false})
notification.warning({
message: error.code,
description: error.message,
})
})
}
changeDialog = (e, index) => {
e.preventDefault()
this.setState({
activeIndex: index,
})
}
render() {
// if your component is while fetching shows a loading to the user
if(this.state.isFetching) return <div>Loading...</div>;
// if there is no results shows a msg to the user
if(this.state.items.length === 0) return <div>there is not items!!!</div>
const { activeIndex, items } = this.state
const {
first_name: firstName,
last_name: lastName,
phone,
email,
address,
} = items[activeIndex]

Having issue with state variable in react js. Cannot update the variable's value to true

import getAuthentication from './getAuthentication';
class Home extends React. Component {
constructor() {
super();
//this.authentication = false;
this.state = {
username: '',
password: '',
check:false,
authentication:false
};
this.err = '';
}
componentDidUpdate() {
console.log (this.state.authentication);
console.log(this.state.authentication == true);
if (this.state.check)
{
const promiseAuthentication = getAuthentication(
this.state.username,
this.state.password,
);
promiseAuthentication
.then(response => {
console.log (response.data.Success);
console.log(response.data.Success == true);
this.setState({check :false, authentication:response.data.Success});
})
.catch(error => {
// console.log(error);
this.err = error;
});
}
if (this.state.authentication == true) {
event.preventDefault();
history.push('/overview');
}
}
assignUsername = event => {
this.setState({ username: event.target.value });
};
assignPassword = event => {
this.setState({ password: event.target.value });
};
handleSubmit = () => {
this.setState({ check:true });
};
==============================================================
getAuthentication.js
import axios from 'axios';
function getAuthentication(username, password) {
const authenticationConfig = {
Email: username,
Password: password,
};
return axios.post(
'http://localhost:5002/login/confirmation',
authenticationConfig,
);
}
export default getAuthentication;
In the above code my this.state.Authentication is not getting updated to true
I am trying to update its value in axios promise.
Can someone please tell me what's wrong? I mean I have tried everything but I am not able to proceed.
How do I change the state of Authentication object and switch new window?
I have a second file that is returning the axios promise where promise value is "undefined".. How do I make async call and resolve this issue ??
componentDidUpdate is wrapped in if (this.state.check). Nothing in the code you pasted sets this.state.check to true. Set this.state.check: true.

Way to make inheritance in Vuex modules

Im building my app with VueJS and Vuex and I'm facing the issue when I have Multiple modules using the same data fields. Its about API configuration like dat.
getUsers ({ state, commit }) {
axios.get(urls.API_USER_URL).then( response => {
let data = response.data;
parseApi(state, data, 'user');
}).catch( err => {
console.log('getUser error: ', err);
})
},
And another function in other Modules is like
getPosts ({ state, commit }) {
axios.get(urls.API_POST_URL).then( response => {
let data = response.data;
parseApi(state, data, 'posts');
}).catch( err => {
console.log('getUser error: ', err);
})
},
I would like to know if I can just inheritence my Module and add additional datafields / functions in there?
My every module would have message and status field which I getting in response of my API.
export default {
state : {
message : "",
status : 0
},
parseApi: function(state, data, property) {
if (data.hasOwnProperty('message')) {
state.message = data.message;
}
if (data.hasOwnProperty('status')) {
state.status = data.status;
}
if (data.hasOwnProperty(property)) {
state[property] = data[property];
}
}
}
It would be something like that.
Is there a way to write this code once and have it in every module Im using?
EDITED:
I even cant get this apiParse function in there, I need to make muttation for those fields. But repeting it all time is pointless... Any advices?
I put my reusable vuex code in small classes. E.g.
crud.js
export default class {
constructor ( endpoint ) {
this.state = {
endpoint: endpoint,
meta: {},
status: null,
known: [],
currentId: null,
};
this.getters = {
id: state => id => state.known.find( o => o.id === id )
};
this.actions = {
async store( context, payload ) {
*(call to API)*
},
async update( context, payload ) {
*(call to API)*
},
*...etc*
};
this.mutations = {
STORED(state, item) {
state.known.push(item);
},
*...etc*
};
}
}
Then I can use it in all of my modules:
user.module.js
import Crud from '/crud';
var crud = new Crud('/api/users');
const state = {
...crud.state,
};
const getters = {
...crud.getters,
};
const actions = {
...crud.actions,
};
const mutations = {
...crud.mutations,
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
Developing a little bit more Erin's response, you can define a base class with common features like this:
export default class BaseModule {
protected state() {
return {
isLoading: false,
};
};
protected getters() {
return {
isLoading(s) {
return s.isLoading;
},
};
};
protected actions() {
return {};
};
protected mutations() {
return {
[START_TRANSACTION]: (s) => {
s.isLoading = true;
},
[END_TRANSACTION]: (s) => {
s.isLoading = false;
},
};
}
protected modules() {
return {};
};
public getModule = () => {
return {
namespaced: true,
state: this.state(),
getters: this.getters(),
actions: this.actions(),
mutations: this.mutations(),
modules: this.modules(),
};
}
}
You can now extend/override only the parts you need in derived classes, with class inheritance; for example, if you need to extend the modules...:
import BaseModule from './BaseModule';
import rowDensity from '#/store/modules/reusable/rowDensity';
export default class ItemListModule extends BaseModule {
protected modules() {
return {
...super.modules(),
rowDensity,
};
};
}
Finally, to use them as modules in the store, you can instantiate them and call .getModule():
import Vue from 'vue';
import Vuex from 'vuex';
import ItemListModule from './modules/ItemListModule';
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production';
export const MODULE_NAMESPACES = {
List: 'list',
};
export default new Vuex.Store({
modules: {
[MODULE_NAMESPACES.List]: new ItemListModule().getModule(),
},
strict: debug,
});
I figured out some inheritance with the state fields according to:
https://vuex.vuejs.org/en/modules.html#namespacing
export default {
namespaced: true,
state,
getters,
actions,
mutations,
modules : {
apiResponses
}
}
I exported apiResponses module after the module user with namespaced and next i did the same thing with posts.
The namespaces inherited those message / status states and their mutations and which i just called in my user and post module. Now they are working corectly.
My message muttation form apiResponses:
[types.SET_MESSAGE] (state, message) {
state.message = message;
},
Works inside actions of my user modules
if (data.hasOwnProperty('message')) {
commit(types.SET_MESSAGE, data.message);
}
Then in my commponent I just call.
computed: {
...mapGetters({
user : 'user/user',
userMessage : 'user/message',
post: 'post/monitoring',
postMessage : 'post/message',
}),
},
EDITED
The last part of my issue is like that.
I got action inside apiResponse Module
let actions = {
getResponseParsed({commit}, payload) {
console.log(payload)
if (payload.data.hasOwnProperty('message')) {
commit(types.SET_MESSAGE, payload.data.message);
}
if (payload.data.hasOwnProperty('status')) {
commit(types.SET_STATUS, payload.data.status);
}
if (payload.data.hasOwnProperty(payload.property)) {
commit(payload.mutation, payload.data[payload.property]);
}
}
}
And then inside my user and other module i called it like:
getUser ({ state, commit, dispatch }) {
axios.get(urls.API_GET_USER_URL).then( response => {
let data = response.data;
dispatch('getResponseParsed', {
data : data,
mutation : types.SET_USER,
property : 'user'
});
});
},
And the last thing, we need to make this new module reusable to according to docs we need to create it like a components.
export default {
state() {
return {
message : '',
status : 0,
}
},
getters,
mutations,
actions
}
With the state as function :)
Hope somone else got same issue :D
here is what I've done:
first of all, I created a mainApi.js whose duty is to just make connection with apis
mainApi.js
import axios from "#/plugins/axios";
export default {
get(url ,id){
return axios.get(`/${url}/${id}`);
},
getAll(url, filter) {
return axios.get(`/${url}`, {params: {...filter}});
},
create(url ,teBeCreated){
return axios.post(`/${url}`, teBeCreated);
},
update(url ,toBeUpdated){
return axios.put(`/${url}/${toBeUpdated.oid}`, toBeUpdated);
},
delete(url ,id){
return axios.delete(`/${url}/${id}`);
},
}
second: I wrote a base class to define needed functions to store data. then this class can be inherited by other store modules.
gate.js
import mainApi from '#/api/main'
import store from '#/store'
export default class {
constructor() {
this.state = {
view: null,
list: [],
};
this.getters = {
view: (state) => state.view,
list: (state) => state.list,
}
this.mutations = {
SET_VIEW(state, payload) {
state.view = payload;
},
SET_LIST(state, payload) {
state.list = payload;
},
UN_SET_VIEW(state) {
state.view = null;
},
UN_SET_LIST(state) {
state.list = [];
},
}
this.actions = {
get({ commit }, { url, id }) {
return new Promise((resolve, reject) => {
mainApi.get(url, id)
.then(response => {
commit('SET_VIEW', response.data.data);
resolve(response)
})
.catch(error => {
console.log("error in get method in gate store: ", error);
commit('UN_SET_VIEW');
reject(error)
})
});
},
getAll({ commit }, { url, filter }) {
return new Promise((resolve, reject) => {
mainApi.getAll(url, filter)
.then(response => {
commit('SET_LIST', response.data.data);
resolve(response)
})
.catch(error => {
console.log("error in getAll method in gate store: ", error);
commit('UN_SET_LIST');
reject(error)
})
});
},
create({ commit }, { url, params }) {
return new Promise((resolve, reject) => {
mainApi.create(url, params)
.then(response => {
resolve(response)
})
.catch(error => {
console.log("error in create method in gate store: ", error);
reject(error)
});
});
},
update({ commit }, { url, params }) {
return new Promise((resolve, reject) => {
mainApi.update(url, params)
.then(response => {
resolve(response)
})
.catch(error => {
console.log("error in update method in gate store: ", error);
reject(error)
})
})
},
delete({ commit }, { url, id }) {
return new Promise((resolve, reject) => {
mainApi.delete(url, id)
.then(response => {
resolve(response);
})
.catch(error => {
console.log("error in delete method in gate store: ", error);
reject(error)
})
});
},
}
}
third: now, we can define as many separate store modules as we need. as you can see below, in each module we just need to get the data retrieved from views and pass them to mainApi (gate.js base class's functions and methods are all part of our modules) and manipulate with received data.
someStore.js
import Gate from '#/store/modules/gate'
let gate = new Gate();
const url = 'customUrl'
const gateStates = { ...gate.state }
const gateGetters = { ...gate.getters }
const gateMutations = { ...gate.mutations }
const state = {
...gateStates,
};
const getters = {
...gateGetters,
};
const mutations = {
...gateMutations,
};
const actions = {
get: ({ commit }, id) => gate.actions.get({ commit }, { url, id }),
getAll: ({ commit }) => gate.actions.getAll({ commit }, {url, filter: {}}),
create: ({ commit }, params) => gate.actions.create({ commit }, { url, params }),
update: ({ commit }, params) => gate.actions.update({ commit }, { url, params }),
delete: ({ commit }, id) => gate.actions.delete({ commit }, { url, id })
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
finally we should import our modules and define them as "vuex store modules" so:
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import someModule from './modules/someModule'
Vue.use(Vuex)
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
someModule
},
plugins: {}
})
in this example I used anotherPromise because I needed the server responses directly in my views. if you want to just use responses in your store, there is no need for these Promises and they should be removed as below:
in gate.js
change this
get({ commit }, { url, id }) {
return new Promise((resolve, reject) => {
mainApi.get(url, id)
.then(response => {
commit('SET_VIEW', response.data.data);
resolve(response)
})
.catch(error => {
commit('UN_SET_VIEW');
console.log("error in getOne method in gate store: ", error);
reject(error)
})
});
},
to this
get({ commit }, { url, id }) {
mainApi.get(url, id)
.then(response => {
commit('SET_VIEW', response.data.data);
})
.catch(error => {
commit('UN_SET_VIEW');
console.log("error in getOne method in gate store: ", error);
})
},
in this way, you have list and view parameters in each module and they can be easily called in your views:
someView.vue
created() {
store.dispatch('someModule/get', this.$route.params.id)
}
computed: {
view() {
return store.getters('someModule/view')
}
}
As a personal challenge I wanted to be able to create a pure ES6 class that could express this need (meaning no annotation allowed). I thus created an AbstractModule class defining the high level operations:
export default class AbstractModule {
constructor(namespaced = true) {
this.namespaced = namespaced;
}
_state () {
return {}
}
_mutations () {
return {}
}
_actions () {
return {}
}
_getters () {
return {}
}
static _exportMethodList (instance, methods) {
let result = {};
// Process methods when specified as array
if (Array.isArray(methods)) {
for (let method of methods) {
if (typeof method === 'string') {
result[method] = instance[method].bind(instance);
}
if (typeof method === 'function') {
result[method.name] = method.bind(instance);
}
// else ignore
}
}
// Process methods when specified as plain object
if (typeof methods === "object") {
for (const [name, method] of Object.entries(methods)) {
if (typeof method === 'string') {
result[name] = instance[method].bind(instance);
}
if (typeof method === 'function') {
result[name] = method.bind(instance);
}
}
}
// Process methods when specified as single string
if (typeof methods === 'string') {
result[name] = instance[methods].bind(instance);
}
// Process methods when specified as single callback
if (typeof methods === 'function') {
result[name] = methods.bind(instance);
}
return result;
}
static module() {
let instance = new this();
console.log(instance);
return {
namespaced: instance.namespaced,
state: instance._state(),
mutations: AbstractModule._exportMethodList(instance, instance._mutations()),
actions: AbstractModule._exportMethodList(instance, instance._actions()),
getters: AbstractModule._exportMethodList(instance, instance._getters())
}
}
}
From this I created my own class module by redefining the parent methods I wanted to customize this way:
export default class QuestionModule extends AbstractModule{
constructor(question) {
super();
this.question = question;
}
selectLine (state, line) {
this.question.selectLine(line);
}
unselectLine (state, line) {
this.question.unselectLine(line);
}
submit ({ state, commit, rootState }) {
/** API call */
}
_state () {
return this.question;
}
_mutations () {
return [this.selectLine, this.unselectLine, this.validate];
}
_actions () {
return this.submit;
}
}
Final step is to declare my class module into the Vuex store (through a call to the module static method):
const store = new Vuex.Store({
modules: {
question: QuestionModule.module()
},
strict: process.env.NODE_ENV !== 'production'
});

Categories