I'm trying to pass data from external api and convert it to a CSV file. This is what I have so far:
import React, { Component } from 'react';
import { CSVLink } from "react-csv";
import Header from './components/header'
import './App.scss'
class App extends Component {
constructor() {
super()
this.state = {
orders: []
}
this.getReports = this.getReports.bind(this)
}
getReports = async () => {
const response = await fetch('example.com')
const ordersData = await response.json()
this.setState({
orders: ordersData.data,
})
let order = this.state.orders.map(order => ({
ID: order.id,
Order_ID: order.order_id,
Date: order.created,
Amount: order.total_amount,
Payment_Provider: order.payments[0].provider_id,
Payment_ID: order.payments[0].id,
Refund_Reason: order.reason
}))
const objectToCsv = (order) => {
const csvRows = [];
const headers = Object.keys(order[0])
csvRows.push(headers.join(','));
// console.log(csvRows)
for (const row of order) {
const values = headers.map(header => {
const escaped = ('' + row[header]).replace(/"/g, '\\"')
return `"${escaped}"`
})
csvRows.push(values.join(','))
}
return csvRows.join('\n')
}
let csvData = objectToCsv(order)
console.log(csvData)
// console.log(order)
// console.log(this.state.orders)
}
render() {
return (
<div className="App">
<Header />
<div className="body">
{/* <button onClick={this.getReports}>CLICK</button> */}
<CSVLink data={csvData} onClick={this.getReports}>Click me</CSVLink>
</div>
</div>
);
}
}
export default App;
The problem I'm facing is that I can't pass the csvData variable to the data attribute in the CsvLink component since the variable is not global. I tried adding another csvData state where I passed the objectToCsv(order) and that stops the error, however when I download the CSV, the content is jiberish.
Any help is much appreciated!
I've added the following to my getReports function and removed the csvLink component and I was able to export the data to CSV file, but it's definitely not a nice UX. I still need to work on separating the inputs into columns.
const blob = new Blob([csvData], { type: 'text/csv' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.setAttribute('hidden', '')
a.setAttribute('href', url)
a.setAttribute('download', 'download.csv')
document.body.appendChild(a)
a.click()
Related
I have a react application, the result of which is: a list of names that are displayed using the fetch method, which in turn takes data from a json file - which button is pressed from that array the data is taken(first or second) + there is a search filter by firstname.
App.js:
import React from "react";
import TableData from "./TableData";
import TableSearch from "./TableSearch";
import "./styles.css";
class App extends React.Component {
state = {
data: [],
filteredData: [],
search: "",
shift: "first"
};
async componentDidMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch("/data/today.json");
const data = (await response.json()).group;
this.setState(
{
data,
shift: Object.keys(data)[0]
},
this.filter
);
};
updateSearch = e => {
this.setState({
search: e.target.value
});
};
filter = () => {
this.setState(({ search, data, shift }) => {
const s = search.toLowerCase();
return {
filteredData: data[shift].filter(n =>
n.firstName.toLowerCase().includes(s)
)
};
});
};
onClick = ({
target: {
dataset: { shift }
}
}) => {
this.setState(() => ({ shift }), this.filter);
};
render() {
const { search, shift, data, filteredData } = this.state;
return (
<div>
<TableSearch
value={search}
onChange={this.updateSearch}
onSearch={this.filter}
/>
{Object.keys(data).map(n => (
<button
data-shift={n}
onClick={this.onClick}
className={n === shift ? "active" : ""}
>
{n} shift
</button>
))}
<TableData data={filteredData} />
</div>
);
}
}
export default App;
But at the moment I need to simplify and reduce my application, since something needs to be change in the logic, so just the same, I need go to the initial version of the application.
I just need to leave a list of names on the screen, and remove the buttons and filter.
Well, I removed:
App.js:
import React from "react";
import TableData from "./TableData";
class App extends React.Component {
state = {
data: []
};
async componentDidMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch("/data/today.json");
const data = (await response.json()).group;
console.log(data);
this.setState({
data
});
};
render() {
return (
<div>
<TableData data={this.state.data} />
</div>
);
}
}
export default App;
And the list is not displayed ...
I think maybe this has something to do with the filter, but I'm not sure.
How then can I write the code so that list name only is displayed without buttons and filters? But at the same time, I cannot modify the TableData.js file since in this tasks it is necessary for further manipulations with the code.Well, at least not much to changeTableData.js
Given the code filteredData: data[shift] and shift: Object.keys(data)[0]:
filteredData is an array
data is an object like {first: [], second: []}
So, in the shortened version, you have to use an array, e.g.:
<TableData data={Object.values(this.state.data)[0] || []} />
And fix your initial state to match the type:
state = {
data: {}
}
I have a component that needs to display the details of a movie according to the id that is passed in the URL (parameter). I'm having difficulty doing the conditional on the RENDER method. It's probably quite simple, but I'm still not very familiar with the React flow. Can you give me an idea?
Ex: Codesandbox
import React, { Component } from "react";
import api from "../../services/api";
export default class Movie extends Component {
state = {
movies: [],
movieId: {}
};
async componentDidMount() {
const { id } = this.props.match.params;
const response = await api.get("");
const currentParams = this.props.match.params;
this.setState({
movies: response.data,
movieId: `${id}`
});
console.log(this.state.movies);
console.log(this.state.movieId);
}
render() {
const movies = this.state.movies,
currentParams = this.state.movieId;
return (
<div className="movie-info">
{this.state.movies.map(movie => (
if( movie.event.id === currentParams ) {
<h1 key={movie.event.id}>{movie.event.title}</h1>
}
))}
</div>
);
}
}
You might not want to use map in this case since you only want to render one movie. You could instead use the find method and render that single movie if it's found.
class Movie extends Component {
// ...
render() {
const { movies, movieId } = this.state;
const movie = movies.find(movie => movie.event.id === movieId);
return (
<div className="movie-info">
{movie ? <h1 key={movie.event.id}>{movie.event.title}</h1> : null}
</div>
);
}
}
I have a simple app that access the opentable api (http://opentable.herokuapp.com/api/restaurants). My app, when loaded, simply displays content specified from the query parameters. For example, appending ?city=toronto would give me all restaurants in Toronto. Here is a working, hardcoded example:
import React, { Component } from "react";
import Spinner from "./components/common/Spinner";
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [],
isLoading: false
};
}
componentDidMount() {
// // let city = this.props.match.params.city;
// // console.log(city);
// console.log(this.props.match.params.city);
fetch("http://opentable.herokuapp.com/api/restaurants?city=Toronto")
.then(res => res.json())
.then(json => {
this.setState({
isLoading: true,
items: json
});
});
}
render() {
const { isLoading, items } = this.state;
let itemsToArray = Object.values(items);
return !isLoading ? (
<div>
<Spinner />
</div>
) : (
<div className="App">
<ul>
{itemsToArray[3].map(item => (
<div>
<li key={item.id}>{item.name}</li>
</div>
))}
</ul>
</div>
);
}
}
export default App;
If I were to uncomment console.log(this.props.match.params.city);, it tosses an error TypeError: Cannot read property 'params' of undefined. Am I accessing the params incorrectly? I'd like to do something like,
componentDidMount() {
let city = this.props.match.params.city;
fetch(`http://opentable.herokuapp.com/api/restaurants?city=${city}`)
.then(...
If you are trying to use something like:
http://myapp/page?city=Toronto
Then, this.props.match.params.city won't work. The reason being, the use-case of match.params.city is supposed to be in the Routes.
import { Route } from "react-router-dom";
<Route path="/path/:city" component={App} />
In your componentDidMount() lifecycle method, try using:
const urlParams = new URLSearchParams(window.location.search);
let city = urlParams.get('city');
For the above code, have a look at How can I get query string values in JavaScript? In your code, if you try logging the value of city, it might be undefined if you haven't configured your route this way.
Sample Code
class App extends React.Component {
state = {
city: "None"
};
componentDidMount() {
const urlParams = new URLSearchParams(window.location.search);
let city = urlParams.get("city");
this.setState({
city
});
console.log(city);
}
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<h3>You are in {this.state.city}!</h3>
</div>
);
}
}
Working Demo: CodeSandbox
You can use this function to access the URL params
var getParams = function (url) {
var params = {};
var parser = document.createElement('a');
parser.href = url;
var query = parser.search.substring(1);
var vars = query.split('&');
if(vars == ''){
params = '';
return params;
}
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
params[pair[0]] = decodeURIComponent(pair[1]);
}
return params;
};
and call it
console.log(getParams(window.location.href));
What if you try to wrap your App Class component with withRouter? so, it will look like the following:
import React, { Component } from "react";
import { withRouter } from 'react-router-dom';
import Spinner from "./components/common/Spinner";
class App extends Component {
//....
}
export default withRouter(App);
I am trying to create a map with locations fetched from Foursquare API , the locations are displayed in two ways in the app, as a side bar containing a list items (location names) and markers on the map, there is a search box, and depending on the query on this search box data should be filtered so the locations and markers should be filtered to match the query in the same time, now i got the location list filtered correctly but not the markers on the map
here is the parent component
import React, { Component } from 'react';
import Map from './Map';
import List from './List'
import escapeRegExp from 'escape-string-
regexp';
import './App.css';
class App extends Component {
state = {
places: [],
query: '',
}
componentDidMount() {
this.fetchPlaces();
}
fetchPlaces() {
const client_id = "N4UIDVOE5XA3YVMBMMLIANAYLDEGSTDJY3KLFM0BAQJB1A4G" ;
const client_secret = "RVWSHIZAAKLLTW03ELYCPVY1GJ1QZ312AP0C1MLOCBP5JG4Q";
const api = "https://api.foursquare.com";
const request = fetch(`${api}/v2/venues/search?ll=30.044281,31.224291&categoryId=4bf58dd8d48988d181941735,4bf58dd8d48988d181941735,4bf58dd8d48988d13a941735&client_id=${client_id}&client_secret=${client_secret}&v=20180719`);
return request.then(response => {
//MDN
const myOk = response.ok;
if(myOk) {
return response.json();
}
}).then(places => {
this.setState({places: places.response.venues});
//on error fetching locations
}).catch(() => alert('error fetching data'));
}
updateQuery = (query) => {
this.setState({query})
}
render() {
const { places, query} = this.state;
let placesToShow;
if(query) {
const match = new RegExp(escapeRegExp(query), 'i');
placesToShow = places.filter(place => match.test(place.name));
}
else {
placesToShow = places;
}
return (
<div className="app">
<Map places={ placesToShow }/>
<List places ={ placesToShow }
onUpdateQuery={ this.updateQuery }/>
</div>
);
}
}
export default App;
and this is the child component
import React, { Component } from 'react';
class Map extends Component {
componentDidMount() {
//getting the script of the map
const script = document.getElementsByTagName('script')[0];
//load the map after the script loads
script.addEventListener('load', e => {this.initMap()});
}
initMap() {
const container = document.getElementById('map');
const map = new
window.google.maps.Map(container, {
center: { lat: 30.044281, lng: 31.224291 },
});
this.setState({ map });
}
createMarkers() {
const { map } = this.state;
const markers =
this.props.places.map(place => { return (new window.google.maps.Marker({
position: {lat: place.location.lat, lng: place.location.lng},
name: place.name,
map: map,
}));
})
const bounds = new window.google.maps.LatLngBounds();
const largeInfoWindow = new window.google.maps.InfoWindow();
markers.forEach(marker => {
bounds.extend(marker.position);
marker.addListener('click', e => {
largeInfoWindow.setContent(`<div>${marker.name }</div>`);
largeInfoWindow.open(map, marker);
});
})
map.fitBounds(bounds);
}
componentDidUpdate() {
this.createMarkers();
}
render(){
return(
<div id="map" />
);
}
}
export default Map;
so why markers are not filtered correctly, and how to achieve that???
Edit
It worked after i set all markers map to null before adding only the filtered ones
componentDidUpdate(prevProps, prevState) {
if(prevProps.places !== this.props.places) {
this.state.markers.forEach(marker => marker.setMap(null))
this.createMarkers();
}
}
I think the issue is that your createMarkers function does not remove markers. It only appends new markers and probably duplicates them as well. The easy thing to do would be to remove all markers at the start of createMarkers using marker.setMap(null) as described here. This isn't the most efficient solution, however, as you could compare the previous markers with the new markers and only adjust as necessary. Alternatively, you could utilize the react-google-maps library to handle the markers for you.
I'm making progress on this app. I'm able to access and render the list of ingredients now I need to do the same with the name of the recipe. Postman indicates that it is under recipes.body.matches[0].sourceDisplayName. I created another function, similar to what got me the ingredients. Getting the following error...
TypeError: Cannot read property 'map' of undefined
import React from 'react';
import Request from 'superagent';
import _ from 'lodash';
export class Yum extends React.Component {
constructor(){
super();
this.state = {
searchQuery: 'onion',
recipe: {
ingredients: []
}
};
this.search = this.search.bind(this);
this.queryUpdate = this.queryUpdate.bind(this);
}
componentWillMount(){
this.search(this.state.searchQuery);
}
render(){
//const title = 'Onion Soup'; // Get this from somwhere else ?
const {recipe, searchQuery} = this.state; // Get state properties
const displayName = _.get(recipe, 'sourceDisplayName').map((sourceDisplayName) => {
return (<h4>{displayName}</h4>)
});
const listItems = _.get(recipe, 'ingredients', []).map((ingredient, sourceDisplayName) => {
return (<h5>{ingredient}</h5>);
});
return(
<div>
<input onChange={this.queryUpdate} type="text" value={searchQuery} />
<h4>{displayName}</h4>
<ul>
<li>{listItems}</li>
</ul>
</div>
)
}
queryUpdate(event) {
const searchQuery = event.target.value; // Get new value from DOM event
this.setState({searchQuery}); // Save to state
this.search(searchQuery); // Search
}
search(searchQuery) {
const url = `http://api.yummly.com/v1/api/recipes?_app_id=5129dd16&_app_key=9772f1db10ba433223ad4e765dc2b537&q=${searchQuery}&maxResult=1`
Request.get(url).then((response) => {
this.setState({
recipe: response.body.matches[0]
});
});
}
}
export default Yum;
Any suggestions?