Refactoring using Async and await in React? - javascript

Im (very) new to react having come from a Java background. I am trying to refactor some existing code to use Async and await.
The error is coming right before my render function() (highlighted with *****) and am getting a "/src/App.js: Unexpected token, expected "," error and cant for the life of me figure out what is going on. Ive tried messing around with } ) and ; and cant quite track it down. Any help is appreciated.
import React, { Component } from "react";
import { FixedSizeGrid } from "react-window";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
specialties: [],
isLoaded: false,
error: null
};
}
async componentDidMount() {
const response = await fetch (url)
.then(response => response.json())
.then(body => {
const specialties = body.data.specialties;
return specialties;
})
.then(specialties => {
return specialties.map(({ _id, name }) => {
return [_id, name];
})
.then(transformed => {
this.setState({
specialties: transformed,
isLoaded: true,
error: null
});
})
.catch(error => {
this.setState({
specialties: [],
isLoaded: true,
error: error
});
});
}
render() {***********************here
if (this.state.error) {
return <span style={{ color: "red" }}>{this.state.error.message}</span>;
}
if (!this.state.isLoaded) {
return "Loading...";
}
const ITEM_HEIGHT = 35;
return (
<FixedSizeGrid
columnWidth={300}
rowHeight={35}
itemData={this.state.specialties}
height={ITEM_HEIGHT * this.state.specialties.length}
width={600}
itemSize={() => ITEM_HEIGHT}
columnCount={2}
rowCount={this.state.specialties.length}
>
{SpecialtyYielder}
</FixedSizeGrid>
);
}
}
const SpecialtyYielder = ({ columnIndex, rowIndex, data, style }) => {
return (
<div
style={{
...style,
backgroundColor:
(rowIndex + columnIndex) % 2 ? "beige" : "antiquewhite",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
{data[rowIndex][columnIndex]}
</div>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

You're missing a bracket and paren:
async componentDidMount() {
const response = await fetch (url)
.then(response => response.json())
.then(body => {
const specialties = body.data.specialties;
return specialties;
})
.then(specialties => {
return specialties.map(({ _id, name }) => {
return [_id, name];
})
}) // missing closing bracket and paren
.then(transformed => {
this.setState({
specialties: transformed,
isLoaded: true,
error: null
});
})
.catch(error => {
this.setState({
specialties: [],
isLoaded: true,
error: error
});
});
}
Async/Await
Basically everywhere you used then, you can just use await instead, but in a way such that you don't need a bunch of callbacks and the logic is like synchronous code:
async componentDidMount() {
try {
const response = await fetch (url)
const body = await response.json()
const specialties = body.data.specialties;
const transformed = specialties.map(({ _id, name }) => {
return [_id, name]
})
this.setState({
specialties: transformed,
isLoaded: true,
error: null
})
}
catch(error) {
this.setState({
specialties: [],
isLoaded: true,
error: error
})
}
}

Looks like you might need a better text editor ;). It's in your componentDidMount. At the very end you're missing a ), to close off your .then block and then another curly brace to close componentDidMount
async componentDidMount() {
const response = await fetch (url)
.then(response => response.json())
.then(body => {
const specialties = body.data.specialties;
return specialties;
})
.then(specialties => {
return specialties.map(({ _id, name }) => {
return [_id, name];
})
.then(transformed => {
this.setState({
specialties: transformed,
isLoaded: true,
error: null
});
})
.catch(error => {
this.setState({
specialties: [],
isLoaded: true,
error: error
});
});
})
}
This addresses your syntax error. The way you phrased the question made it seem like you thought the "resolution" to it was to use async/await. You obviously can still do a refactor. Are you interested in still exploring async/await?

You are missing }) in componentDidMount method:
import React, { Component } from "react";
import { FixedSizeGrid } from "react-window";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
specialties: [],
isLoaded: false,
error: null
};
}
async componentDidMount() {
const response = await fetch (url)
.then(response => response.json())
.then(body => {
const specialties = body.data.specialties;
return specialties;
})
.then(specialties => {
return specialties.map(({ _id, name }) => {
return [_id, name];
})
.then(transformed => {
this.setState({
specialties: transformed,
isLoaded: true,
error: null
});
})
.catch(error => {
this.setState({
specialties: [],
isLoaded: true,
error: error
});
});
})}
render() {
const ITEM_HEIGHT = 35;
return (
<FixedSizeGrid
columnWidth={300}
rowHeight={35}
itemData={this.state.specialties}
height={ITEM_HEIGHT * this.state.specialties.length}
width={600}
itemSize={() => ITEM_HEIGHT}
columnCount={2}
rowCount={this.state.specialties.length}
>
{SpecialtyYielder}
</FixedSizeGrid>
);
}
}
const SpecialtyYielder = ({ columnIndex, rowIndex, data, style }) => {
return (
<div
style={{
...style,
backgroundColor:
(rowIndex + columnIndex) % 2 ? "beige" : "antiquewhite",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
{data[rowIndex][columnIndex]}
</div>
);
};

Related

How Can I Parse SOAP Data in React Native

How Can I Parse SOAP Data in React Native? I need help pls someone help me, i tried, if i write console.log(channels[0]);
is output;
just {
The contents of the view appear blank on the screen.
constructor(props) {
super(props);
this.state = ({
channels: [],
});
}
componentDidMount() {
axios.post('xxxx', xmls, {
headers: {
'Content-Type': 'text/xml',
Accept: 'application/xml',
},
})
.then((response) => {
const channels = convert.xml2json(response.data, {alwaysArray:true,ignoreAttributes: true, compact: true, ignoreDeclaration: true, fullTagEmptyElement: true, spaces: 4 })
this.setState({ channels: [] })
console.log(channels);
})
.catch(err => console.log(err));
}
render() {
return (
<View>
{this.channels.map((v, id) => {
<View key={id}>{v.item}</View>
})}
</View>
)
}
}

How to properly time data rendering in react?

I am attempting to pull data from Open Data to put together a quick heat map. In the process, I want to add some stats. Almost everything runs well in that I have the data and am able to render the map, but I am unsure how to deal with calculations once I get the data since it takes time for data to come in. How do I set things up so that I can run a function on a state variable if it hasn't necessarily received data yet? Currently I am getting a null as the number that is passed as props to StatCard.
Below are my attempts:
App.js
import React, { Component } from 'react';
import Leaf from './Leaf';
import Dates from './Dates';
import StatCard from './StatCard';
import classes from './app.module.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data:[],
cleanData:[],
dateInput: '2019-10-01',
loading: false,
totalInspections: null,
calculate: false
};
}
componentDidMount() {
try {
this.fetchData();
} catch (err) {
console.log(err);
this.setState({
loading: false
})
}
}
fetchData=()=>{
const requestData = async () => {
await fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(res =>
//console.log(res)
this.setState({ data: res, loading: true})
)
}
const calculateInspections = () => {
this.setState({totalInspections: this.state.data.length})
}
//call the function
requestData();
if(this.state.data) {
calculateInspections();
}
}
handleDateInput = (e) => {
console.log(e.target.value);
this.setState({dateInput:e.target.value, loading: false}) //update state with the new date value
this.updateData();
//this.processGraph(e.target.value)
}
updateData =() => {
this.fetchData();
}
LoadingMessage=()=> {
return (
<div className={classes.splash_screen}>
<div className={classes.loader}></div>
</div>
);
}
//inspection_date >= '${this.state.dateInput}'&
// https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=inspection_date >= '2019-10-10T12:00:00'
render() {
return (
<div>
<div>{!this.state.loading ?
this.LoadingMessage() :
<div></div>}
</div>
{this.state.totalInspections && <StatCard totalInspections={this.state.totalInspections} /> }
<Dates handleDateInput={this.handleDateInput}/>
<Leaf data={this.state.data} />
</div>
);
}
}
export default App;
StatCard.js
import React from 'react';
const StatCard = ( props ) => {
return (
<div >
{ `Total Inspections: ${props.totalInspections}`}
</div>
)
};
export default StatCard;
Attempt Repair
componentDidMount() {
try {
this.fetchData();
} catch (err) {
console.log(err);
this.setState({
loading: false
})
}
}
componentDidUpdate () {
if(this.state.data) {
this.setState({totalInspections: this.state.data.length})
}
}
fetchData= async ()=>{
const requestData = () => {
fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(res =>
//console.log(res)
this.setState({ data: res, loading: true})
)
}
//call the function
await requestData();
}
So your problem is that isLoading state needs to be set synchronously before any async calls.
So in your componentDidMount:
componentDidMount() {
try {
this.setState({ loading: true }); // YOU NEED TO SET TRUE HERE
this.fetchData();
} catch (err) {
console.log(err);
this.setState({
loading: false
})
}
}
This ensures loading as soon as you make the call.
Then your call is made and that part is asynchronous.
As soon as data comes through, the loading is done:
.then(data => {
this.setState({
data: data,
loading: false, // THIS NEEDS TO BE FALSE
totalInspections: this.state.data.length
})
})
Furthermore, your render method can have multiple return statements. Instead of having conditional JSX, return your loading layout:
render() {
if (this.state.loading) {
return <div> I am loading </div>
}
return <div> Proper Content </div>;
}
Only render <StatCard /> if you have the data you need:
{this.state.totalInspections && <StatCard totalInspections={this.state.totalInspections} /> }
First of all, I don't think you need a separate function calculateInspections(). You can put that logic in the then callback.
fetchData = () => {
fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(data => {
this.setState({
data: data,
loading: true,
totalInspections: this.state.data.length
})
})
}
Secondly, setting this.state.totalInspections is effectively redundant, since you can simple do:
{this.state.data && <StatCard totalInspections={this.state.data.length} /> }
Lastly, avoid using componentDidUpdate() hook when you're new to react. Most of the time you end up shooting yourself in the foot.
Currently your Attempt Repair just got you into an infinite render loop. This happens because whenever you call setState(), it'll call componentDidUpdate() lifecycle hook after rendering. But within componentDidUpdate() you call again setState(), which induces a follow-up call to the same lifecycle hook, and thus the loop goes on and on.
If you must use componentDidUpdate() and call setState() inside, rule of thumbs, always put a stop-condition ahead of it. In you case, it'll be:
componentDidUpdate () {
if (this.state.data) {
if (this.state.totalInspections !== this.state.data.length) {
this.setState({ totalInspections: this.state.data.length })
}
}
}
Here is my solution.
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
dateInput: '2019-10-01',
loading: false,
error: false
};
}
async componentDidMount() {
try {
await this.fetchData(this.state.dateInput);
} catch (err) {
this.setState({ loading: false, error: true });
}
}
fetchData = (date) => new Promise(resolve => {
this.setState({ loading: true });
fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${date}'&$limit=50000`)
.then(res => res.json())
.then(res => {
this.setState({ data: res, loading: false, error: false });
resolve(res.data);
});
})
handleDateInput = e => {
this.setState({ dateInput: e.target.value }) //update state with the new date value
this.fetchData(e.target.value);
}
render() {
const { loading, data } = this.state;
return (
<div>
{loading && (
<div className={classes.splash_screen}>
<div className={classes.loader}></div>
</div>
)}
{data && <StatCard totalInspections={data.length} />}
<Dates handleDateInput={this.handleDateInput} />
<Leaf data={data} />
</div>
);
}
}
There are two ways of achieving this:
You can put calculator in componentDidUpdate() and write a condition to just calculate once
componentDidUpdate(prevProps, prevState) {
const data = this.state.data;
// this line check if we have data or we have new data,
// calculate length once
if (data.length || !isEqual(data, prevState.data)) {
calculateInspections()
}
}
// isEqual() is a lodash function to compare two object or array
You can stop your rendering until data is fetched
async componentDidMount() {
await fetchData()
}
fetchData = () => {
const requestData = async() => {
await fetch(`https://data.cityofnewyork.us/resource/p937-wjvj.json?$where=latitude > 39 AND latitude< 45 AND inspection_date >= '${this.state.dateInput}'&$limit=50000`)
.then(res => res.json())
.then(res =>
//console.log(res)
this.setState({
data: res,
loading: true,
totalInspections: res.length
})
)
}
// in above situation you just setState when you are sure
// that data has come
//call the function
requestData();
}

Can't upload file to firebase storage

I'am trying to upload files to firebase in react, But the file upload progress reaches 100% then suddenly it shows me an unknown error like
{
"error": {
"code": 400,
"message": "Bad Request. Could not create object",
"status": "CREATE_OBJECT"
}
}
this is the code I'm using to upload the file, This is the actual component where file uploading is done, The user opens a modal to select a file and then after selecting and pressing send in the modal the file uploading starts in the below component.
import React, { Component } from "react";
import { Segment, Button, Input, ButtonGroup } from "semantic-ui-react";
import firebase from "../../firebase";
import FileModal from "./FileModal";
import uuidv4 from "uuid/v4";
class MessageForm extends Component {
state = {
storageRef: firebase.storage().ref(),
message: "",
channel: this.props.currentChannel,
user: this.props.currentUser,
loading: false,
errors: [],
modal: false,
uploadState: "",
uploadTask: null,
percentUploaded: 0
};
uploadFile = (file, metadata) => {
const pathToUpload = this.state.channel.id;
const ref = this.props.messagesRef;
const filePath = `chat/public/${uuidv4}.jpg`;
this.setState(
{
uploadState: "uploading",
uploadTask: this.state.storageRef.child(filePath).put(file, metadata)
},
() => {
this.state.uploadTask.on(
"state_changed",
snap => {
const percentUploaded = Math.round(
(snap.bytesTransferred / snap.totalBytes) * 100
);
this.setState({ percentUploaded });
},
err => {
console.error(err);
this.setState({
errors: [...this.state.errors, err],
uploadState: "error",
uploadTask: null
});
},
() => {
console.log(this.state.uploadTask);
this.state.uploadTask.snapshot.ref
.getDownloadURL()
.then(downloadUrl => {
this.sendFileMessage(downloadUrl, ref, pathToUpload);
})
.catch(err => {
console.error(err);
this.setState({
errors: [...this.state.errors, err],
uploadState: "error",
uploadTask: null
});
});
}
);
}
);
};
sendFileMessage = (fileUrl, ref, pathToUpload) => {
ref
.child(pathToUpload)
.push()
.set(this.createMessage(fileUrl))
.then(() => {
this.setState({
uploadState: "done"
}).catch(err => {
console.error(err);
this.setState({
errors: [...this.state.errors, err]
});
});
});
};
openModal = () => {
this.setState({
modal: true
});
};
closeModal = () => {
this.setState({
modal: false
});
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
createMessage = (fileUrl = null) => {
const message = {
timestamp: firebase.database.ServerValue.TIMESTAMP,
user: {
id: this.state.user.uid,
name: this.state.user.displayName,
avatar: this.state.user.photoURL
}
};
if (fileUrl != null) {
message["image"] = fileUrl;
} else {
message["content"] = this.state.message.trim();
}
return message;
};
sendMessage = () => {
const { messagesRef } = this.props;
const { message, channel } = this.state;
if (message) {
this.setState({
loading: true
});
messagesRef
.child(channel.id)
.push()
.set(this.createMessage())
.then(() => {
this.setState({
loading: false,
message: "",
errors: []
});
})
.catch(err => {
console.error(err);
this.setState({
loading: false,
errors: [...this.state.errors, err]
});
});
} else {
this.setState({
errors: [...this.state.errors, { message: "Add a message" }]
});
}
};
render() {
const { errors, message, loading, modal } = this.state;
return (
<Segment className="message__form">
<Input
fluid
name="message"
style={{ marginBottom: "0.7em" }}
icon="add"
iconPosition="left"
placeholder="Write your message"
onChange={this.handleChange}
className={
errors.some(error => error.message.includes("message"))
? "error"
: ""
}
value={message}
/>
<ButtonGroup icon widths="2">
<Button
onClick={this.sendMessage}
disabled={loading}
color="orange"
content="Add reply"
labelPosition="left"
icon="edit"
/>
<Button
color="violet"
content="Upload Media"
labelPosition="right"
icon="cloud upload"
onClick={this.openModal}
/>
<FileModal
modal={modal}
closeModal={this.closeModal}
uploadFile={this.uploadFile}
/>
</ButtonGroup>
</Segment>
);
}
}
export default MessageForm;
Just a guess, but I suspect that your error might be related to the way you are storing the uploadTask in the component's state... and it makes me pretty uncomfortable - it seems to violate one of the core principles of using component state in React.
As you've probably heard already state should only be mutated via the setState command... and the problem with your approach is that the uploadTask portion of the state will be mutated during the upload execution. In fact, your code counts on it - you've written it so that as the uploadTask is updated, its percentage gets displayed on screen.
Overall, you've got the right idea - just take that uploadTask: this.state.storageRef.child(filePath).put(file, metadata) assignment out of your state... something like this:
uploadFile = (file, metadata) => {
const pathToUpload = this.state.channel.id;
const ref = this.props.messagesRef;
const filePath = `chat/public/${uuidv4}.jpg`;
this.setState(
{
uploadState: "uploading",
},
() => {
let uploadTask = this.state.storageRef.child(filePath).put(file, metadata);
uploadTask.on(
"state_changed",
snap => {
const percentUploaded = Math.round(
(snap.bytesTransferred / snap.totalBytes) * 100
);
this.setState({ percentUploaded });
},
err => {
console.error(err);
this.setState({
errors: [...errors, err],
uploadState: "error",
});
},
() => {
console.log(uploadTask);
uploadTask.snapshot.ref
.getDownloadURL()
.then(downloadUrl => {
this.sendFileMessage(downloadUrl, ref, pathToUpload);
})
.catch(err => {
console.error(err);
this.setState({
errors: [...this.state.errors, err],
uploadState: "error",
});
});
}
);
}
);
};
(Untested code, conceptual only)

How to manually remove Firebase firestore listeners?

I understand from this SO answer, that we must manually remove Firebase listeners. How can I do that in the following use case? My successful attempt is shown in the below code.
I tried to use some of the ideas from this answer too. But unsuccessfully.
What am I doing wrong?
import React, { Component } from 'react';
// redacted for brevity
import firebase from '#firebase/app';
import '#firebase/firestore';
class CRUDContainer extends Component {
state = {
items: [],
path: null,
isError: false,
isLoading: true,
};
componentWillUnmount () {
// cancel subscriptions and async tasks to stop memory leaks
this.unsubscribe(this.path);
}
unsubscribe = path => path && firebase.firestore().collection(path).onSnapshot(() => {})
getItems = path => {
const out = [];
const db = firebase.firestore();
if(!db) return;
db.collection(path)
.orderBy('timestamp', 'desc')
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
out.push(doc.data());
});
return out;
})
.then(result => {
const newState = {
path,
items: result,
isError: false,
isLoading: false,
};
this.setState(newState);
return result;
})
.then(() => {
this.unsubscribe(path);
return path;
})
.catch(error => {
console.error('Error getting documents: \n', error);
const newState = {
isError: true,
isLoading: false,
};
this.setState(newState);
});
};
Child = ({ match: { params: { id }}}) => {
// redacted for brevity
getItems(path);
return (
this.state.isLoading
?
<Loading/>
:
(
this.state.isError
?
<ErrorMessage/>
:
(items && (
<CRUDView items={items} />
)))
)
};
render() {
return <Route path="/:id" component={this.Child} />
}
}
export default CRUDContainer;

How to insert params in the fetch url in React Native?

My skills in React Native is basic, i want to insert the params id in the url to show the posts according to the category.
export default class PostByCategory extends Component {
static navigationOptions = ({ navigation }) => ({
title: `${navigation.state.params.Title}`,
});
constructor(props) {
super(props);
this.state = {
isLoading: true,
};
}
componentDidMount() {
return fetch(ConfigApp.URL+'json/data_posts.php?category='`${navigation.state.params.IdCategory}`)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson
}, function() {
});
})
.catch((error) => {
console.error(error);
});
}
You have to replace navigation.state.params.IdCategory with this.props.navigation.state.params.IdCategory.
It's not a good practice to manually concat your params to the url. I suggest you look at this question to learn how to properly construct your query string.
componentDidMount() {
return fetch(ConfigApp.URL+'json/data_posts.php?category='+this.props.navigation.state.params.IdCategory)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson
}, function() {
});
})
.catch((error) => {
console.error(error);
});
}

Categories