How to populate dropdown options dynamically using a promise - javascript

import { getCustomer } from './Customers';
let optionItems=[];
export const LOV: React.FunctionComponent = () => {
const loadCustomer = async () => {
const data = await getCustomer();
for (var i=0; i < data.value.length ; ++i)
{
optionItems.push({
key: data.value[i].CustomerId,
text: data.value[i].CustomerName
});
}
}
useEffect(() => {
loadCustomer();
}, [])
return (
<SearchableDropdown options={optionItems}/>
);};
Code in Customers.tsx
export const getCustomer = async (): Promise<any> => {
const response = await
$.ajax({
url: apiURL,
type: "GET",
headers: headers,
data: null,
cache: false,
beforeSend: function (request) {
request.setRequestHeader("Authorization", 'Bearer ' + accessToken);
}
})
.done( function (data) {
return data;
})
.fail(function (jqXHR) {
if (jqXHR.status == 401) {
promptAuth(jqXHR.getResponseHeader("WWW-Authenticate"));
}
else {
console.log("NOT 401");
}
});
return response;
}
I'm trying to populate a dropdown dynamically using a promise. I'm using Fluent-react Dropdown. getCustomer() loads values to const data. However, I can't see any values in the dropdown even though data is not null. Please help.
Fluent Dropdown => https://developer.microsoft.com/en-us/fluentui#/controls/web/dropdown

Your optionsItems need to be reactive. Try using useState-hook
export const LOV: React.FunctionComponent = () => {
const [optionItems, setOptionItems] = useState([]);
const loadCustomer = async () => {
const data = await getCustomer();
const options = [];
for (var i=0; i < data.value.length ; ++i)
{
options.push({
key: data.value[i].CustomerId,
text: data.value[i].CustomerName
});
}
setOptionItems(options)
}
useEffect(() => {
loadCustomer();
}, [])
return (
<SearchableDropdown options={optionItems}/>
);};

Please make sure your data.value[i].CustomerId and data.value[i].CustomerName are valid strings. You need to supply string, but it looks like your API call returns null there. After you fix this problem, together with Stutje's solution your app should start to work.

Related

FETCH PATCH request returns my data as being null

app.patch('/api/todos/:id', async (req, res) => {
try {
const data = await pool.query("UPDATE todolist SET task = $1 WHERE id = $2;", [req.body.task, req.params.id])
res.json(req.body)
} catch (error) {
console.error(error.message)
}
})
I am trying to make a fetch PATCH request, but every time I do, instead of grabbing the value from the alert window and storing its value in my database, it returns null, or an empty string. Not sure why it is doing this, because it works perfectly well on Postman. Any advice would be appreciated.
import React from "react";
class UpdateBtn extends React.Component {
render() {
const updateTodo = (e, alert) => {
fetch('api/todos/' + e, {
method: 'PATCH',
header: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ task: alert })
})
.then(res => res.json())
.catch(error => console.error(error.message))
}
const handleUpdate = (e) => {
const alert = window.prompt("Update Task:")
if (alert.length === 0) {
return undefined;
}
updateTodo(e.target.id, alert)
// window.location.reload()
}
return (
<button
className="updateBtn"
id={this.props.id}
value={this.props.value}
onClick={handleUpdate}>Update</button>
)
}
}
export default UpdateBtn;

useEffect and setState with hook function

I want to try and use react to fetch data efficiently using useEffect appropriately.
Currently, data fetching is happening constantly, instead of just once as is needed, and changing when there is a input to the date period (calling different data).
The component is like this,
export default function Analytics() {
const {
sentimentData,
expressionsData,
overall,
handleChange,
startDate,
endDate,
sentimentStatistical,
} = useAnalytics();
return (
UseAnalytics is another component specifically for fetching data, basically just a series of fetches.
i.e.,
export default function useAnalytics() {
....
const { data: sentimentData } = useSWR(
`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`,
fetchSentiment
);
....
return {
sentimentData,
expressionsData,
overall,
handleChange,
setDateRange,
sentimentStatistical,
startDate,
endDate,
};
}
Thanks in advance,
The apirequest is like this,
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
I think using useEffect here is the right approach. i.e.,
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
I'm confused about how to update the constants properly, something like this seems like one approach, but not sure about how I can use useEffect to update these variables properly, or if I should be doing this inside of useAnalytics?
i.e.,
const [analytics, setAnalytics] = useState({
sentimentData: {},
expressionsData: {},
overall: {},
handleChange: () => {},
startDate: '',
endDate: '',
sentimentStatistical:{},
});
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
const {
sentimentData,
expressionsData,
overall,
handleChange,
startDate,
endDate,
sentimentStatistical,
} = useAnalytics();
Realised SWR is a hook, need to use SWR documentation :P
You have to store the requested information in states inside your custom hook. Then you could consume this hook wherever you want. This should work.
Define custom hook
const useAnalitycs = () => {
const [analytics, setAnalytics] = useState({
sentimentData: {},
expressionsData: {},
overall: {},
startDate: '',
endDate: '',
sentimentStatistical:{},
});
const handleChange = () => {
/* */
};
useEffect(() => {
const fetchData = async () => {
// const response = await apiCall();
// setAnalytics(...)
};
fetchData();
}, []); // called once
return {
...analytics,
handleChange
};
};
Consume useAnalytics hook
const ComponentConsumerA = () => {
/*
const { state/methods you need } = useAnalytics()
...
*/
};
const ComponentConsumerB = () => {
/*
const { state/methods you need } = useAnalytics()
...
*/
};

Add both settimeout and timeout to in the array loop

I am looping the array using bluebird map method and then using each row as a payload to my apicall function. Everything works perfectly but I want to incorporate timeout method when response from the api takes more than 10 seconds and also settimeout method to delay 2 seconds after each api call. Please let me know how can I acheive this. I am fine without using bluebird. Thanks in advance.
handleSubmit = () => {
Promise.map(this.props.data, row => {
return apiCall(api, row).then((response) => {
if(response){
console.log(response)
} else{
console.log("failed")
}
})
}, { concurrency: 1 } )
}
apiCall: (api, input ) => {
switch (process.env.NODE_ENV) {
case 'production': { // Production environment
return new Promise((resolve) => {
window.runApi(api, input, (response) => {
if (typeof response === 'string') {
const jsonResponse = JSON.parse(response);
resolve(jsonResponse);
} else {
resolve(response);
}
});
});
}
default: {
const requestBody = input;
if (input !== "") {
requestBody.username = "user";
requestBody.password = "password";
}
const requestUrl = `api`;
return fetch(requestUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
})
.then(res => res.json());
}
}
}

Cannot get value from local storage inside function without converting to async function

I have made a wrapper for fetch function for my API calls in react-native. I dont want to pass JWT token everytime that I make an API call, so I thought that fetching it inside wrapper will fix it for me, but I cannot get it to work because of async nature...
useFetch.js
// import qs from "querystring";
import { getUserAuthToken } from "../storage";
const responseChecker = async (response) => {
let error = "";
let data = {};
let statusCode = null;
if (!response.ok) {
error = "Something went wrong";
statusCode = response.status;
} else {
statusCode = response.status;
data = await response.json();
}
return { statusCode, error, data };
};
const fetchAuthToken = getUserAuthToken();
const useFetch = (baseURL, authHeader = null) => {
console.log(fetchAuthToken);
**//Cannot get this token in time for the API call ^**
const defaultHeader = {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
key: "1c419c7e-3a34-49f0-9192-b48d4534dff3",
Authorization: authHeader ? authHeader :fetchAuthToken,
};
const customFetch = (
url,
method = "GET",
body = false,
headers = defaultHeader,
) => {
const options = {
method,
headers,
credentials: "include",
};
if (body) options.body = body;
return fetch(url, options);
};
const get = async (endpoint) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "GET");
return responseChecker(response);
};
const post = async (endpoint, body = {}) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "POST", body);
return responseChecker(response);
};
const put = async (endpoint, body = {}) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "PUT", body);
return responseChecker(response);
};
return {
get,
post,
put,
};
};
export default useFetch;
storage.js
import AsyncStorage from "#react-native-community/async-storage";
export const getUserAuthToken = async () => {
try {
const userToken = await AsyncStorage.getItem("userAuthToken");
return userToken;
} catch (e) {
console.log("error");
}
};
exportAPI.js
import useFetch from "./fetch";
const LOCAL_IP = "192.168.0.131";
export const authAPI = (header) => useFetch(`http://${LOCAL_IP}:8000`, header);
export const activityAPI = useFetch(`http://${LOCAL_IP}:8000`);
Steps.js
import React, { useEffect, useState } from "react";
import { Text, Platform } from "react-native";
import { CardXLarge } from "../../../components/Cards/";
import fitnessKitApis from "../../../utilities/fitnessKits";
import { activityAPI } from "../../../utilities/apis";
const StepCard = (props) => {
const fetchStepsFromFitnessKits = async () => {
if (Platform.OS === "android") {
await fitnessKitApis.historicSteps().then((res) => {
setSteps(res);
});
} else {
await fitnessKitApis.historicSteps((result) => setSteps(result));
}
};
const [steps, setSteps] = useState(0);
useEffect(() => {
fetchStepsFromFitnessKits();
const requestParams = { date: new Date(), steps };
const { data, statusCode, error } = activityAPI.get(
"/v1/user/steps/",
requestParams,
);
// console.log(data, statusCode, error);
}, [steps]);
return (
<CardXLarge>
<Text>{steps}</Text>
</CardXLarge>
);
};
export default StepCard;
I know I can pass authHeader from component but that will result in adding 2-3 lines of code in every component which is not super convenient.
Thanks
If you don't want to use async/await in a function to get items from asyncStorage. You can use either callback or promise in place of async/await.
Callback:
AsyncStorage.getItem('data1', (error, data1) => {
// perform your logic here.
});
Promise:
AsyncStorage.getItem('data1').then(data1=>{
// perform your logic here.
}).catch(error=>{
// handle error
})
I have personally used callback for getItem and worked perfectly. I have not tested the promise version but I expect to do the same work as callback.
...
const fetchAuthToken = getUserAuthToken();
...
Your getUserAuthToken is an asynchronous function and here it is not being awaited. To guarantee that asynchronous call is finished you have to await it or use callbacks as #HungrySoul suggested.
You can't await something outside of an asynchronous function.
Solution that I would suggest is creating a class UseFetch and passing the arguments through the constructor. The argument being here the JWT token that you are getting from the AsyncStorage.
Also, another thing that can be done and is a good practice - use redux for managing the state and keeping the JWT token. You might look into that. It will take a bit longer but it will make your code more elegant.
Edit: Or, you might try something like this.
Keep in mind that you have to wait for promises to resolve before you use what was promised.
Here we are using a closure. You will have to pass an argument (which is an async function) to the useFetchBuilder. That function will be awaited and provide the JWT. You can use the getUserAuthToken for that purpose.
Keep in mind that you have to use await or wait for the promise to resolve before using this function. Problem might be somewhere else in your code - maybe the life cycle methods.
I hope this helped.
const responseChecker = async (response) => {
let error = "";
let data = {};
let statusCode = null;
if (!response.ok) {
error = "Something went wrong";
statusCode = response.status;
} else {
statusCode = response.status;
data = await response.json();
}
return { statusCode, error, data };
};
const useFetchBuilder = async (userTokenProvider) => {
const userToken = await userTokenProvider();
return (baseURL, authHeader = null) => {
const defaultHeader = {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
key: "1c419c7e-3a34-49f0-9192-b48d4534dff3",
Authorization: authHeader ? authHeader : userToken,
};
const customFetch = (
url,
method = "GET",
body = false,
headers = defaultHeader,
) => {
const options = {
method,
headers,
credentials: "include",
};
if (body) options.body = body;
return fetch(url, options);
};
const get = async (endpoint) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "GET");
return responseChecker(response);
};
const post = async (endpoint, body = {}) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "POST", body);
return responseChecker(response);
};
const put = async (endpoint, body = {}) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "PUT", body);
return responseChecker(response);
};
return {
get,
post,
put,
};
};
}
export default useFetchBuilder;
I moved getUserAuthToken function down to each request method function in useFetch function where I can await for the response. Then it all worked perfectly.. Also I could have use getUserAuthToken but usingAsyncStorage.getItem seems much cleaner
modified fetch.js
// import qs from "querystring";
import AsyncStorage from "#react-native-community/async-storage";
const responseChecker = async (response) => {
let error = "";
let data = {};
let statusCode = null;
if (!response.ok) {
error = "Something went wrong";
statusCode = response.status;
} else {
statusCode = response.status;
data = await response.json();
}
return { statusCode, error, data };
};
const useFetch = (baseURL, authHeader = null) => {
const defaultHeader = {
Accept: "application/json",
// "Content-Type": "application/x-www-form-urlencoded",
"Content-Type": "application/json",
key: "1c419c7e-3a34-49f0-9192-b48d4534dff3",
Authorization: authHeader,
};
const customFetch = (
url,
method = "GET",
body = false,
headers = defaultHeader,
) => {
const options = {
method,
headers,
credentials: "include",
};
if (body) options.body = JSON.stringify(body);
return fetch(url, options);
};
const get = async (endpoint) => {
await AsyncStorage.getItem("userAuthToken").then((result) => {
defaultHeader.Authorization = result;
});
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "GET");
return responseChecker(response);
};
const post = async (endpoint, body = {}) => {
await AsyncStorage.getItem("userAuthToken").then((result) => {
defaultHeader.Authorization = result;
});
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "POST", body);
return responseChecker(response);
};
const put = async (endpoint, body = {}) => {
await AsyncStorage.getItem("userAuthToken").then((result) => {
defaultHeader.Authorization = result;
});
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "PUT", body);
return responseChecker(response);
};
return {
get,
post,
put,
};
};
export default useFetch;

How can I get the response from dispatch?

I have a component which has a form where at the moment to do clic on submit button, I call a function handleSubmit (it is on my component), this function call an action through of dispatch and this action, I make a call to a service (HTTP Request).
handleSubmit
handleSubmit = (e) => {
e.preventDefault()
const { validateFields } = this.props.form;
validateFields((err, params) => {
if (!err) {
const { user, dispatch } = this.props;
let response = dispatch(actions.addDevice(params))
console.log(response); //Response is undefined
}
});
}
actions.addDevice
function addDevice(params){
return (dispatch, getState) => {
let { authentication } = getState();
dispatch(request({}));
service.addDevice(params, authentication.user.access_token)
.then(
response => {
if(response.status === 201) {
dispatch(success(response.data));
}
return response;
},
error => {
dispatch(failure(error.toString()));
dispatch(alertActions.error(error.toString()));
}
)
}
function request(response) { return { type: constants.ADD_DEVICE_REQUEST, response } }
function success(response) { return { type: constants.ADD_DEVICE_SUCCESS, response } }
function failure(error) { return { type: constants.ADD_DEVICE_FAILURE, error } }
}
service.addDevice
function addDevice(params, token){
return axios({
url: 'http://localhost:5000/user/add-device',
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token},
data: {
data1: params.data1,
data2: params.data2,
data3: params.data3
}
})
.then(function(response) {
return response;
})
.catch(function(error) {
return error.response;
});
}
I would like to get the response in my component to be able to make validations but as the request is async, I never can get the response and only prints an undefined variable. How can I get the response sync? Or what do I need do to be able to make validations?
You are not returning the promise service.addDevice.
So you can do return service.addDevice... and in the handleSubmit you do dispatch(...).then((data) => ...do something with the data...)
let response = dispatch(actions.addDevice(params))
this is asynchronous. So it is not surprising to return undefined from console.log(). console.log() execute even before dispatch process is completed. Use promise or async await syntax. I would recommend using the async-await syntax.
handleSubmit = (e) => {
e.preventDefault()
const { validateFields } = this.props.form;
validateFields(async (err, params) => {
if (!err) {
const { user, dispatch } = this.props;
let response =await dispatch(actions.addDevice(params))
console.log(response); //Response is undefined
}
});
}
Please replace your code with this code
handleSubmit
handleSubmit = (e) => {
e.preventDefault()
const { validateFields } = this.props.form;
validateFields((err, params) => {
if (!err) {
const { user, dispatch } = this.props;
dispatch(actions.addDevice(params)).then((response)=>{
console.log(response);
})
}
});
}
actions.addDevice
function addDevice(params){
return (dispatch, getState) => {
let { authentication } = getState();
dispatch(request({}));
return service.addDevice(params, authentication.user.access_token)
.then(
response => {
if(response.status === 201) {
dispatch(success(response.data));
}
return response;
},
error => {
dispatch(failure(error.toString()));
dispatch(alertActions.error(error.toString()));
}
)
}
function request(response) { return { type: constants.ADD_DEVICE_REQUEST, response } }
function success(response) { return { type: constants.ADD_DEVICE_SUCCESS, response } }
function failure(error) { return { type: constants.ADD_DEVICE_FAILURE, error } }
}

Categories