Use static fetch service - javascript

I have created an express mongoose api. I want to use that api from my React-application.
I want to create a service that would manage those api requests. But I am new in react-native and I can't use that service. I tried creating a static class but I cannot make it works. Here is an example :
// apiService.js
class ApiService {
static fetchUsers = () => {
return fetch('XXX/users')
.then((response) => {
return response.json()
.then((data) => {
return data;
})
})
.catch((error) => {
console.error(error);
});
}
}
export default ApiService;
And my screen
// UserScreen.js
import ApiService from '../services/apiService';
export default class UserScreen extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
isLoading: true,
}
}
componentDidMount = () => {
let users = ApiService.fetchUsers();
this.setState({data: users});
this.setState({isLoading: false});
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator/>
</View>
)
} else {
return (
<View style={{ flex: 1, marginTop: 100 }}>
{
this.state.data.map((val, key) => {
return <TouchableOpacity
style={styles.homeButton}
key={key}
onPress={() => this.redirectHandler(val)}>
</TouchableOpacity>
})
}
</View>
)
}
}
}
I tried using async and wait but I can't find a way to retrieve data. The data are well retrieve in the apiService but I can't share them with the UserScreen.
How can I use a (static or not) class/function in react-native and get the data from the screen
Update
Here is what I tried with async
class ApiService {
static fetchUsers = async () => {
try {
let response = await fetch('XXXXX/users/');
let json = await response.json();
return json;
} catch (error) {
console.error(error);
}
}
}
export default ApiService;
And in my Userscreen
componentDidMount = async () => {
try {
let users = await ApiService.fetchUsers();
this.setState({isLoading: false});
this.setState({data: users});
} catch (error) {
console.log(error);
}
}

The problem lies in the setState that you are performing twice. If you look at the logic of the component, first we check for isLoading, if true we show some message/spinner otherwise we are showing a list of users/data.
Sequence of the Set State:
this.setState({isLoading: false});
this.setState({data: users});
Note that each setState triggers a re-render of the component, so in this case first we set isLoading to false (1st Re-Render) and then we set the data (2nd Re-Render)
The problem is, when 1st Re-Render is done, isLoading is set to false and the condition which we talked about above, now enters the "showing the user/data list" part. Another thing to note here is we have defined users: [] in state and when we are setting the users array (from the api call), we set that in a variable called data. The variable data is never defined in state so essentially it is "undefined".
Issue in your code:
this.state.data.map(...)
You cannot map over an undefined variable and this is where the problem lies. Doing so will throw an error saying "cannot read property map of undefined".
To fix this:
When setting the users list, instead of doing this.setState({ data: users }) just do this.setState({ users: users }) and change this.state.data.map( to users.map(
Also, unnecessary re-renders are costly and in case of React Native, they are costlier. Merge your setState(...) calls when possible. For example,
this.setState({
isLoading: false,
users: users
})

Related

NextJS getServerSideProps pass data to Page Class

i know this has probably been already asked, but i'm at a point where i don't know what to do.
I'm not a (very) experienced developer in javascript or NextJS.
My Problem(1):
I got the method: export const getServerSideProps: GetServerSideProps = async () => {} implemented to fetch some data from a integrated API (pages/api from NextJS). The code itself is probably not well(or worse) written, but it works. (for now at least)
export const getServerSideProps: GetServerSideProps = async () => {
try {
// get userID
await fetch("http://localhost:32147/api/v1/user/get?requestedField=userID&fieldName=username&fieldValue=<value removed>").then(
(userIDResponse: Response): any => {
// get userID as json
userIDResponse.json().then((userIDResult: Response): any => {
// get messages
fetch(
"http://localhost:32147/api/v1/message/get?requestedField=*&fieldName=userID&fieldValue=" +
JSON.stringify(userIDResult[0].userID)
).then((messageResponse: Response): any => {
// get messages as json
messageResponse.json().then((messageResult) => {
return {
props: { messages: messageResult },
{/* marker1 */}
}
})
})
})
}
)
} catch (error) {
console.log(error)
}
}
just to be clear, this method works, data fetching works but just if i access it at marker1
that one part where i return the props:
return {
props: { messages: messageResult },
}
i can't do that 'cause nextjs is gonna break because of getServerSideProps() didn't return anything.
I tried to store the final data into a variable, that i declared on the first line of this method, but it ended up being empty the whole time.
How can i solve this?
My Problem(2): if i set a manual value at the end of this method for testing, it doesn't get passed to the main Page Class (index.tsx)
i can just access it using this.props.<prop name>, in this case: this.props.messages, right?
The whole index.tsx:
import React, { Component } from "react"
import { GetServerSideProps } from "next"
import Router from "next/router"
import Head from "next/head"
import Navbar from "../lib/Navbar"
import MessagesModal from "../lib/MessagesModal"
export const getServerSideProps: GetServerSideProps = async () => {
try {
// get userID
await fetch("http://localhost:32147/api/v1/user/get?requestedField=userID&fieldName=username&fieldValue=<value removed>").then(
(userIDResponse: Response): any => {
// get userID as json
userIDResponse.json().then((userIDResult: Response): any => {
// get messages
fetch(
"http://localhost:32147/api/v1/message/get?requestedField=*&fieldName=userID&fieldValue=" +
JSON.stringify(userIDResult[0].userID)
).then((messageResponse: Response): any => {
// get messages as json
messageResponse.json().then((messageResult) => {
return {
props: { messages: messageResult },
}
})
})
})
}
)
} catch (error) {
console.log(error)
}
}
interface HomeProps {
messages?: []
}
export default class Home extends Component<HomeProps> {
constructor(props) {
super(props)
}
state = {
messagesModal: false,
messages: [],
}
// triggers logout
triggerLogOut(): void {}
render(): JSX.Element {
return (
<>
<Head>
<title>OneDrive Event Connector</title>
</Head>
<Navbar
ItemClickCallback={(callbackItem: string): void => {
if (callbackItem === "messages") {
this.setState({ messageModal: !this.state.messageModal })
} else if (callbackItem === "log_out") {
this.triggerLogOut()
} else {
Router.push("/" + callbackItem)
}
}}
/>
<div className="app-content"></div>
<MessagesModal
messages={this.props.messages}
isOpen={this.state.messagesModal}
toggleModal={() => {
this.setState({ messageModal: !this.state.messagesModal })
}}
/>
</>
)
}
}
This is just a "fun" project for me to practise and learn.
Would be greate if anyone could give me even a hint on what is my problem/mistake here...
Thanks.
Kind regards
Oliver
i can't do that 'cause nextjs is gonna break because of getServerSideProps() didn't return anything.
exactly - in your code, you are returning values inside of a chain of promises - you need to make sure, that values are returned from each step
here's a working example - similar flow with swapped API - to help you understand how to return something, going back from the inside of your chained promises
export const getServerSideProps: GetServerSideProps = async () => {
try {
// initial fetch
const result = await fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((todosResponse: Response): any => {
return todosResponse.json().then((todo) => {
// fetch something more
return fetch(
"https://jsonplaceholder.typicode.com/users/" + todo.userId
).then((userResponse: Response): any => userResponse.json());
})
})
return {
props: { messages: result },
}
} catch (error) {
console.log(error)
}
}
My advise is also to read more on promises / async await in JS world
My Problem(2)
i can just access it using this.props., in this case: this.props.messages, right?
yes, that's right
interface HomeProps {
messages?: []
}
export default class Home extends Component<HomeProps> {
constructor(props) {
super(props)
}
render() {
return (
<>
{JSON.stringify(this.props.messages)}
</>
)
}
}

Consuming Paginated API in React Component

I'm just getting started with React. As a simple exercise, I wanted to create some components for viewing data retrieved from the JsonMonk API. The API contains 83 user records and serves them in pages of 10.
I am trying to develop a component for viewing a list of users one page at a time which I called UserList. The code for it is below:
class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 1,
users: [],
};
this.onPageNext = this.onPageNext.bind(this);
}
componentDidMount() {
this.fetchUsers(this.state.pageNumber)
.then((users) => this.setState({users: users}));
}
async fetchUsers(pageNumber) {
const response = await fetch(`https://jsonmonk.com/api/v1/users?page=${pageNumber}`);
const jsonResponse = await response.json();
return jsonResponse.data.records;
}
onPageNext() {
// ...
}
render() {
const postElements = this.state.users.map(
(props) => <User key={props._id} {...props} />);
return (
<div>
{postElements}
<div>
<button onClick={this.onPageNext}>Next</button>
</div>
</div>
);
}
}
The problem I am having pertains to the onPageNext method of my component. When the user clicks the "Next" button, I want to make a fetch for the next page of data and update the list.
My first attempt used an asynchronous arrow function passed to setState like so:
onPageNext() {
this.setState(async (state, props) => {
const nextPageNumber = state.pageNumber + 1;
const users = await this.fetchUsers(nextPageNumber);
return {pageNumber: nextPageNumber, users: users}
})
}
However, it does not seem React supports this behavior because the state is never updated.
Next, I tried to use promise .then syntax like so:
onPageNext() {
const nextPageNumber = this.state.pageNumber + 1;
this.fetchUsers(nextPageNumber)
.then((users) => this.setState({pageNumber: nextPageNumber, users: users}));
}
This works but the problem here is that I am accessing the class's state directly and not through setState's argument so I may receive an incorrect value. Say the user clicks the "Next" button three times quickly, they may not advance three pages.
I have essentially run into a chicken-or-the-egg type problem. I need to pass a callback to setState but I need to know the next page ID to fetch the data which requires calling setState. After studying the docs, I feel like the solution is moving the fetch logic out of the UsersList component, but I'm not entirely sure how to attack it.
As always, any help is appreciated.
You need to change onPageNext as below:
onPageNext() {
this.setState( prevState => {
return {pageNumber: prevState.pageNumber + 1}
}, () =>{
this.fetchUsers(this.state.pageNumber).then(users => this.setState({users: users}) )
});
}
Here is the Complete Code:
import React from "react";
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 1,
users: [],
};
this.onPageNext = this.onPageNext.bind(this);
}
componentDidMount() {
this.fetchUsers(this.state.pageNumber)
.then((users) => {
console.log(users, 'users');
this.setState({users: users})
}
);
}
async fetchUsers(pageNumber) {
const response = await fetch(`https://jsonmonk.com/api/v1/users?page=${pageNumber}`);
const jsonResponse = await response.json();
return jsonResponse.data.records;
}
onPageNext() {
this.setState( prevState => {
return {pageNumber: prevState.pageNumber + 1}
}, () =>{
this.fetchUsers(this.state.pageNumber).then(users => this.setState({users: users}) )
});
}
render() {
const postElements = this.state.users.map(
(user) => <User key={user._id} {...user} />);
return (
<div>
{postElements}
<div>
<button onClick={this.onPageNext}>Next</button>
</div>
</div>
);
}
}
function User(props) {
return (
<div>
<div style={{padding: 5}}>Name: {props.first_name} {props.last_name}</div>
<div style={{padding: 5}}>Email: {props.email}</div>
<div style={{padding: 5}}>Phone: {props.mobile_no}</div>
<hr/>
</div>
);
}
Here is the Code Sandbox

React fetch request returning an empty array

I have a 3D globe that when you click on a country, returns an overlay of users.
I've previously had everything working just a file of randomised, mock user data in a local file. However I've now received the actual database (a AWS one) and I can't seem to get the fetch to return what I need. It should accept a signal from country click then return a list of users from this and show them in the pop up.
I've taken out the actual database for now (confidential) but this is working correctly on postman and a simplified fetch request I created. It doesn't seem to work within the wider context of this app.
At this stage, it doesn't break but it just returns an empty array in the console.
console.log output for this.state...
this is the state {overlay: true, users: Array(0), country: "Nigeria"}
import { onCountryClick } from "../Scene3D/AppSignals";
import OutsideClickHandler from "react-outside-click-handler";
import Users from "../Users";
import "./style.scss";
class Overlay extends Component {
constructor(props) {
super(props);
this.state = { overlay: false, users: [], country: [] };
this.openOverlay = this.openOverlay.bind(this);
this.closeOverlay = this.closeOverlay.bind(this);
this.fetchUsers = this.fetchUsers.bind(this);
}
openOverlay() {
this.setState({ overlay: true });
}
closeOverlay() {
this.setState({ overlay: false });
}
fetchUsers() {
fetch(
"**AWS***"
)
.then(response => response.json())
.then(data => this.setState({ users: data.users }));
}
onCountryClick = (country, users) => {
this.openOverlay();
this.fetchUsers();
this.setState({ country });
console.log("users", this.state.users);
};
componentDidMount() {
this.onCountrySignal = onCountryClick.add(this.onCountryClick);
}
componentWillUnmount() {
this.onCountrySignal.detach();
}
render() {
const country = this.state.country;
const users = this.state.users;
return (
<OutsideClickHandler
onOutsideClick={() => {
this.closeOverlay();
}}
>
<div className="users-container">
{this.state.overlay && (
<>
<h1>{country}</h1>
{users}
</>
)}
</div>
</OutsideClickHandler>
);
}
}
export default Overlay;```
[1]: https://i.stack.imgur.com/NWOlx.png
The problem is here
onCountryClick = (country, users) => {
this.openOverlay();
this.fetchUsers();
this.setState({ country });
console.log("users", this.state.users);
};
fetchUsers function is asynchronous and taken out from the original flow of execution, And the original execution stack continue to execute the console.log("users", this.state.users) which is empty at this time because fetchUsers function is not yet done executing. to prove it try to console log inside fetchUsers and you'll see who executed first.

What is the proper/right way to use Async Storage?

I am a react-native newbie.
I'm trying to use Async Storage in my application.
I want the async storage to store token when user log in, it will navigate to homescreen. At homescreen, im trying to get the token through async storage and print it on console but all i get is promise. I just want to know what is the proper way to use Async storage especially in storing token? I know the alternative for this problem is using Redux state management, but I'm trying to learn the basic method.
I've tried to store the token in a variable in ComponentWillMount(), but it still does not work.
class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
token = getToken();
}
render() {
const { navigate } = this.props.navigation;
console.log(token);
return (
<View style={styles.container}>
<Text> HomeScreen </Text>
</View>
);
}
}
const getToken = async () => {
let token = "";
try {
token = await AsyncStorage.getItem("token");
} catch (error) {
console.log(error);
}
return token;
};
First, I should note that componentWillMount is deprecated and you can use constructor or componentDidMount instead.
if you log token in getToken and after getting it, it will work fine. if you want to check if the user is logged in, you can do like this
constructor(props) {
super(props);
this.state = {};
getToken().then((token)=>{
console.log(token)
//check if user is logged in
})
}
or you can do this in componentDidMount.
I hope this can help you
Try to use it with Async and await
setValue: function (key, value) {
AsyncStorage.setItem(key, value)
},
getValue: async (key) => {
let value = '';
try {
value = await AsyncStorage.getItem(key) || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return value;
}
You should use it something like this,
import { AsyncStorage, Text, View, TextInput, StyleSheet } from 'react-native';
//for storing Data
setName = (value) => {
AsyncStorage.setItem('name', value);
this.setState({ 'name': value });
}
//for Retrieving Data
componentDidMount = () => AsyncStorage.getItem('name').then((value) => this.setState({ 'name': value }))
Here it is another simple example.

Render component after fetch React Native

I have an app where a user is prompted to enter a code, it calls an external service and returns data from the api. Currently I'm able to enter the code and call the api and get a response back, I can also poll it every 10 seconds and return the data (no sockets on api service).
I have two components, showLanding and showResult.
When the app initialises, I want to show showLanding, prompt for a code and return showResult.
The problem I'm having is I'm not able to get it to 'update' the view.
I have tried ReactDOM.render or render() within the getScreen function, I feel as though I've missed something glaringly obvious when reading the React Native doco.
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {showLanding: true, showResult: false}
}
render() {
if (this.state.showLanding) {
return (
<View
tapCode={this.state.tapCode}
style={styles.appContainer}>
{this.state.showLanding && <LandingComponent/>}
</View>
);
}
if (this.state.showResult) {
return (
<View
tapCode={this.state.tapCode}
style={styles.appContainer}>
{this.state.showResult && <ResultComponent/>}
</View>
);
}
}
}
export class LandingComponent extends React.Component {
getResult = (value) => {
this.state = {
tapCode: value.tapcode
}
console.log(this.state);
this.getScreen(this.state.tapCode);
}
getScreen = (tapcode) => {
setInterval(() => (
fetch('https://mywebservice.com/' + this.state.tapCode)
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
})
.then(this.state = {
showLanding: false,
showResult: true,
tapCode: tapcode,
resultResponse: this.responseJson
})
.catch((error) => {
console.error(error);
})
), 10000);
}
render() {
return (
<View style={styles.landingContainer}>
landing view, prompt for code
</View>
);
}
}
export class ResultComponent extends React.Component {
render() {
return (
<View style={styles.resultContainer}>
Show json output
</View>
);
}
}
There are plenty solutions, but you should definitely consider using a navigation library like react-navigation or react-native-router-flux to handle routing transitions between components.
My "dirty" suggestion would be: Let the App-Component render your Landing-Page and put the state property 'showResults' in there. Show the code-input if showResult is false, if not, show results.
export class LandingComponent extends React.Component {
constructor(props){
super(props);
this.state = {
showResults: false,
tapcode: null,
resultResponse
}
getResult = (value) => {
this.setState({tapCode: value.tapcode})
this.getScreen(this.state.tapCode);
}
getScreen = (tapcode) => {
setInterval(() => (
fetch('https://mywebservice.com/' + this.state.tapCode)
.then((response) => response.json())
.then((responseJson) => {
console.log(responseJson);
})
.then(function(res) {
this.setState({
showResult: true,
tapCode: tapcode,
resultResponse: res.json()})
return res;
})
.catch((error) => {
console.error(error);
})
), 10000);
}
render() {
return (
this.state.showResults ? <ResultComponent/> :
<View style={styles.landingContainer}>
call fetch here....
</View>
);
}
}
And please never mutate your state directly! Call setState() instead.
You need to move the API call up to App. Right now, showLanding is part of the App's state, but you're setting in the LandingComponent so you're not triggering a re-render.

Categories

Resources