ReactJS - response in async call - javascript

I'm learning axios and async calls, please sorry if this is too basic. I have this axios call:
trackComponent.jsx
getTrack(event) {
const {select, userId} = this.props
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/track/${select}/${userId}/${this.props.spotifyToken}`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
this.setState({
playlist: res.data.data[0].playlist,
artists: res.data.data[0].artists,
previews: res.data.data[0].previews,
youtube_urls: res.data.data[0].youtube_urls,
})
})
.catch((error) => { console.log(error); });
};
Now, I'm refactoring my code, and I've implemented an apiService in front of all my component calls in order to deal with authorization, like so:
trackComponent.jsx
import {apiService} from '../ApiService'
async track(event) {
if (this.props.isAuthenticated) {
const {userId, spotifyToken} = this.props;
const {artist} = this.state
const tracks = await apiService.getTrack(userId, spotifyToken, artist) ;
this.setState({tracks});
} else {
this.setState({tracks: []});
}
}
an in ApiService.js I have:
async getTrack(userId, spotifyToken, select) {
return this.axios.get(
`${process.env.REACT_APP_WEB_SERVICE_URL}/track/${artist}/${userId}/${spotifyToken}`
);
}
Now how do I tweak this new async track(event) in the component in order to keep my 'response' and set the following states,
playlist: res.data.data[0].playlist,
artists: res.data.data[0].artists,
previews: res.data.data[0].previews,
youtube_urls: res.data.data[0].youtube_urls,
which were being passed as response inside then() of the first getTrack(event)?

Provided the API call executes successfully, tracks (the value you're returning from getTrack) will contain the responses you're looking for.
If you console.log it, you'll see the various fields. At that point, it's just a matter of accessing the values and setting state with them:
const tracks = await apiService.getTrack(userId, spotifyToken, artist);
const firstEntry = tracks.data.data[0];
this.setState({
playlist: firstEntry.playlist,
artists: firstEntry.artists,
...
});

Related

Unable to pass object in createAsyncThunk in Redux Toolkit

I am trying to pass two parameters in createProductReview action when submitHandler function runs.
One is id and other one is an object containing two items. The problem is that I am unable to pass the object in createProductReview action. It gives undefine when I console log it in reducer function. I want to know how can I pass these two arguments without getting error.
Please check out attached image for error
submitHandler function
const submitHandler = (e) => {
e.preventDefault();
dispatch(createProductReview({ id, { rating, comment } }));
};
createProductReview
export const createProductReview = createAsyncThunk(
'reviewProduct',
async ({ productId, review }, thunkAPI) => {
console.log(productId, review);
try {
const {
userLogin: { userInfo },
} = thunkAPI.getState();
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${userInfo.token}`,
},
};
await axios.post(`/api/products/${productId}/reviews`, review, config);
} catch (error) {
const newError =
error.response && error.response.data.message
? error.response.data.message
: error.message;
return thunkAPI.rejectWithValue(newError);
}
}
);
In javascript, you need to pass keys to the object so it should be like this
dispatch(createProductReview({ productId:id, review:{ rating, comment } }));
Specially, when you are destructuring it in the function. Since destructure works by getting the object with its key.
so for example:
const x = {temp:"1"}
const {temp} = x;
console.log(temp);
//1

How to pass on form inputs and add them to an array of objects?

I am wanting to make my website able to add a name and dog breed to an existing list of animals.
export const addNewPlayer = async (playerObj) => {
try {
const response = await fetch(
`${APIURL}players/`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Rufus',
breed: 'Irish Setter',
}),
}
);
const result = await response.json();
console.log(result);
} catch (err) {
console.error(err);
}
};
This is the function to create the new player
let form = document.querySelector('#new-player-form > form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
let playerData = {
name: form.elements.name.value,
breed: form.elements.breed.value
}
console.log(playerData)
const players = await fetchAllPlayers()
renderAllPlayers(players)
addNewPlayer(playerData);
renderNewPlayerForm()
});
This is the form that I have here too.
I am just stumped on how to change the "Rufus" and "Irish Setter" to user inputs. When logging the playerData, I can see it running when inspecting, but it only adds the spot for "Rufus".
Some of the code was given, and I am only stumped on the playerObj parameter that was first in the code. I do not see a use, and most of the stuff in addNewPlayer is also given in the API website that was a part of the project. I tried to make the name and breed empty strings but got an error from that.
All the information you need is in your playerData variable. So, just add the info from it inside your requisiton body. try this:
export const addNewPlayer = async (playerObj) => {
//...
body: JSON.stringify({
name: playerObj.name,
breed: playerObj.breed,
}),

React static class/method call and Network call

I am relatively new in React but I am trying to create a class/method for network call. Nothing complex just a way to make the code readable.
I have a class:
class Auth {
getToken(username, password) {
const endpointOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: `${username}`, password: `${password}` })
};
fetch(`${Constant.BASE_WP_URL}${Constant.TOKEN_ENDPOINT}`, endpointOptions)
.then(async response => {
const data = await response.json();
if (!response.ok) {
// get error message from body or default to response status
const error = (data && data.message) || response.status;
throw error;
}
return data;
})
.catch(error => {
throw error;
});
}
}
export default Auth;
I am trying to call it using :
import Auth from '../../data/network/Auth';
requestSignIn = (event) => {
event.preventDefault();
this.setState({loading: true})
try {
const authData = Auth.getToken(`${this.state.email}`, `${this.state.password}`);
sessionStorage.setItem('authToken', authData.token)
} catch (error) {
console.log("Connection to WP - Auth Token failed ")
console.error(error);
}
}
but React is complaining because getToken is not a function. I am trying to create a class Auth to have inside all methods/functions I need related to Auth process.
Also, is it the right way to handle the result ? is the try/catch as done works or should I do it differently as the getToken is an API call.
Any idea ?
pretty sure, it's easy but I can't find any interesting topics on Google.
Thanks
I think, if you want to use function directly in OOP of JavaScript, you must put static keyword in front of the function name.
In your auth file
static class Auth {
static getToken(username, password) {
...
}
}
In your index file
import Auth from '../../data/network/Auth';
const authData = Auth.getToken(`${this.state.email}`, `${this.state.password}`);
If you don't have static in front of the function name. You have to create a new instance of the class Auth in order to use the function inside.
import Auth from '../../data/network/Auth';
const AuthInit = Auth();
authData = AuthInit.getToken(`${this.state.email}`, `${this.state.password}`);
===========================
Update for applying asynchronous method
// ====== auth file
static class Auth {
static async getToken(username, password) {
...
// assign fetched data to data_fetch
const data_fetch = fetch(`${Constant.BASE_WP_URL}${Constant.TOKEN_ENDPOINT}`, endpointOptions)
.then(async response => {
const data = await response.json();
if (!response.ok) {
// get error message from body or default to response status
const error = (data && data.message) || response.status;
throw error;
}
return data;
})
.catch(error => {
throw error;
});
return data_fetch;
}
}
// ======= index file
import Auth from '../../data/network/Auth';
...
requestSignIn = async (event) => { // put async in front of your function
// the function outside (requestSignIn) must be async type
// in order to use await keyword for getToken() function
event.preventDefault();
this.setState({loading: true})
try {
// because your getToken function is now a async function, you can
// use "await" keyword in front of it to wait for fetching data to finish
const authData = await Auth.getToken(`${this.state.email}`, `${this.state.password}`);
sessionStorage.setItem('authToken', authData.token)
} catch (error) {
console.log("Connection to WP - Auth Token failed ")
console.error(error);
}
}
Hope this would help
but React is complaining because getToken is not a function
You've defined getToken as a method of an Auth instance, not a static function.
But you don't need an Auth class here at all, just use the proper exports/imports.
replace the Auth-class with:
export function getToken(username, password) {
//...
};
and you can either
/// import all exports from that file under the name `Auth`
import * as Auth from '../../data/network/Auth';
// ...
const authData = Auth.getToken(...);
or
// import these specific exports from that file.
import { getToken } from '../../data/network/Auth';
// ...
const authData = getToken(...);
The last option has the advantage that it can be tree-shaken. If You have some build-process, the compiler can eliminate all the pieces of code that you don't use; especially useful for libraries.
Edit:
Even if you want to keep the default import and import the entire thing, imo. it makes more sense to use a simple Object rather than a class with static methods.
function getToken(username, password) {
//...
}
export default {
getToken
};
In you class definition add static in front of your function to be
class Auth {
static async getToken(username, password) {
const endpointOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: `${username}`, password: `${password}` })
};
try {
const response = await fetch(`${Constant.BASE_WP_URL}${Constant.TOKEN_ENDPOINT}`, endpointOptions)
const data = await response.json();
if (!response.ok) {
const error = (data && data.message) || response.status;
throw error;
}
return data;
} catch (error) {
throw error
}
}
}
export default Auth;
then you will be able to call it as static function.
and requestSignIn will be using it in the following code
requestSignIn = async (event) => {
event.preventDefault();
this.setState({ loading: true })
try {
const authData = await Auth.getToken(`${this.state.email}`, `${this.state.password}`);
sessionStorage.setItem('authToken', authData.token)
} catch (error) {
console.log("Connection to WP - Auth Token failed ")
console.error(error);
}
}

Why would a query param be undefined in NextJS?

I'm calling a page withRouter(Page) and expect the variable for the page (the page is called [category].js) to be present on initial page load. Query itself is there, the key is there, but the value is 'undefined.' There seem to be a few calls to getInitialProps on the server side with 2/3 being undefined.
The react component has a constructor, etc. it's not a functional component.
This is my current getInitialProps:
Category.getInitialProps = async ({ req, query }) => {
let authUser = req && req.session && req.session.authUser
let categoryData = {}
let categoryItemData = {}
let category = query.category
if(category){
let res = await fetch(url1,
{
method: 'POST',
credentials: 'include',
})
categoryData = await res.json();
let categoryItemsRes = await fetch(url2,
{
method: 'POST',
credentials: 'include',
})
categoryItemData = await categoryItemsRes.json();
}
return { query, authUser, categoryData, categoryItemData }
}
This might be redundant at this point, but I ran into this as well and found the docs explain this here
During prerendering, the router's query object will be empty since we do not have query information to provide during this phase. After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.
You might try this instead:
export async function getServerSideProps(ctx) {
const { id } = ctx.query;
return {
props: {
id,
},
};
}
This way it gets the query params when rendering server side, so they're instantly available.
For others who use express custom server, to fix the undefined params, we have to set the dynamic route at server.js as follow:
# server.js
...
app.prepare().then(() => {
const server = express();
....
server.get('/product/:category', (req, res) => {
const { category } = req.params;
return app.render(req, res, `/product/${category}`, req.query)
})
...
}
And then, as Valentijn answers, we can get the category params.
# pages/product/[category].js
....
export async function getServerSideProps(ctx) {
const {category} = ctx.params;
return {
props: {
category
},
};
};
...
The key is dynamic path /product/${category}. Don't use /product/:category

Get specific value from array of objects [{"key":"value"}, {"key":"value"}]

I'm trying to interact with the API of processmaker.
I have made a simple form to authenticate and get the authorization token, which is needed to interact with the rest of the API.
I am able to use the token to output a json response of created projects after login. The response is an array of objects.
I need to get the prj_uid for an api request so I want to extract these, but I've had little luck using map.
How can I iterate over the response and get prj_name and prj_uid for each of the objects in the array?
import React, { useState, useEffect } from "react";
//import ResponsiveEmbed from "react-responsive-embed";
const Tasks = ({ loggedIn }) => {
const [hasError, setErrors] = useState(false);
const [projects, setProjects] = useState([]);
const url = "http://127.0.0.1:8080/api/1.0/workflow/project";
useEffect(() => {
let access_token = sessionStorage.getItem("access_token");
async function fetchData() {
const response = await fetch(url, {
method: "GET",
withCredentials: true,
timeout: 1000,
headers: {
Authorization: "Bearer " + access_token
}
});
response
.json()
.then(response => setProjects(response))
.catch(err => setErrors(err));
}
fetchData();
}, [loggedIn]);
console.log(JSON.stringify(loggedIn) + " logged in, displaying projects");
console.log(projects + " projects");
if (!loggedIn) {
return <h1>Error</h1>;
} else {
return (
<>
<p>Login success!</p>
<h2>Projects:</h2>
<span>{JSON.stringify(projects)}</span>
<div>Has error: {JSON.stringify(hasError)}</div>
</>
);
}
};
export default Tasks;
Stringified Response:
[
{
"prj_uid":"1755373775d5279d1a10f40013775485",
"prj_name":"BPMN Process",
"prj_description":"This is a processmaker BPMN Project",
"prj_category":"8084532045d5161470c0de9018488984",
"prj_type":"bpmn",
"prj_create_date":"2019-08-13 08:50:25",
"prj_update_date":"2019-08-13 09:04:16",
"prj_status":"ACTIVE"
},
{
"prj_uid":"7459038845d529f685d84d5067570882",
"prj_name":"Purchase Request",
"prj_description":"",
"prj_category":"2284311685392d2e70f52e7010691725",
"prj_type":"bpmn",
"prj_create_date":"2019-08-13 11:30:48",
"prj_update_date":"2019-08-13 12:20:05",
"prj_status":"ACTIVE"
}
]
Array.map() is your answer- you had it right.
its as simple as:
let mappedObject = result.map( el => ({ prj_name, prj_uid }) );
el is every element in the array, and we construct the new array with an object containing only prj_name and prj_uid. Because el alraeady has those properties with those names, we do not need to write { prj_name: el.prj_name } when we construct the new object, it is implied and will do the trick with only the property names there.
mappedObject will now hold an array of objects consists only of the asked properties.
You might wanna read more about map to understand it better- Array.map()
If loggedIn is the json object, then you can do this:
const uidNameArr = loggedIn.map((item) => { // returns an array of arrays with the values you want.
return [item.prj_uid, item.prj_name]
})
uidNameArr.forEach(([uid,name]) => {
console.log(`${name} has a uid of ${uid}`)
})

Categories