I am currently working on a app similar to MyAnimeList and I'm using React. I want to click the image of the anime and have it redirect to MyAnimeList for more information. The API i am using is called Jikan APi(https://jikan.moe/). I have seen many ways to do it however most have not worked for my app? I am new to React so im still getting the hang of it.
I am currently using react-bootstrap to for the card.
import React, { useState, useEffect } from 'react';
import { Card, CardColumns, Button } from 'react-bootstrap';
import Nav from '../components/Nav';
import Auth from '../utils/auth';
import { Link } from 'react-router-dom';
import { searchJikanApi } from '../utils/API';
import { saveAnimeIds, getSavedAnimeIds } from '../utils/localStorage';
import video from '../imgs/video.mp4';
import { SAVE_ANIME } from '../utils/mutations';
import { useMutation } from '#apollo/react-hooks';
import Carousel from 'react-elastic-carousel';
import Item from '../components/Carousel/item';
import PopularAnime from '../components/PopularAnime';
import Footer from '../components/Footer';
function Home() {
const [searchedAnimes, setSearchedAnimes] = useState([]);
// create state for holding our search field data
const [searchInput, setSearchInput] = useState('');
// create state to hold saved animeId values
const [savedAnimeIds, setSavedAnimeIds] = useState(getSavedAnimeIds());
const [saveAnime] = useMutation(SAVE_ANIME);
useEffect(() => {
return () => saveAnimeIds(savedAnimeIds);
});
const breakPoints = [
{ width: 1, itemsToShow: 1 },
{ width: 550, itemsToShow: 2, itemsToScroll: 2 },
{ width: 768, itemsToShow: 3 },
{ width: 1200, itemsToShow: 4 },
];
const handleFormSubmit = async (event) => {
event.preventDefault();
if (!searchInput) {
return false;
}
try {
const response = await searchJikanApi(searchInput);
if (!response.ok) {
throw new Error('something went wrong!');
}
const { results } = await response.json();
const animeData = results.map((anime) => ({
animeId: anime.mal_id,
rating: anime.rated || ['No rating to display'],
title: anime.title,
score: anime.score,
description: anime.synopsis,
image: anime.image_url || '',
link: anime.url,
}));
setSearchedAnimes(animeData);
setSearchInput('');
} catch (err) {
console.error(err);
}
};
// function to handle saving an anime to our database
const handleSaveAnime = async (animeId) => {
// find the book in `searchedAnime` state by the matching id
const animeToSave = searchedAnimes.find(
(anime) => anime.animeId === animeId
);
// get token
const token = Auth.loggedIn() ? Auth.getToken() : null;
if (!token) {
return false;
}
try {
await saveAnime({
variables: { ...animeToSave },
});
// if book successfully saves to user's account, save book id to state
setSavedAnimeIds([...savedAnimeIds, animeToSave.animeId]);
} catch (err) {
console.error(err);
}
};
return (
<div className='container'>
<section className='header'>
<Nav></Nav>
<video className='bg-video' autoPlay muted loop>
<source src={video} type='video/mp4' />
Your browser is not supported!
</video>
<div className='heading-primary'>
<form className='search-bar' onSubmit={handleFormSubmit}>
<input
className='heading-search-bar'
name='searchInput'
type='text'
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
/>
<button className='heading-search-btn' type='submit'>
Search
</button>
</form>
</div>
</section>
<div>
<h2>
{searchedAnimes.length
? `Viewing ${searchedAnimes.length} results:`
: 'Search Anime!'}
</h2>
<Link to=''>
<CardColumns className='search-container carousel'>
<Carousel breakPoints={breakPoints}>
{searchedAnimes.map((anime) => {
return (
<Item>
<Card key={anime.animeId} className='anime-card'>
{anime.image ? (
<Card.Img
src={anime.image}
alt={`The cover for ${anime.title}`}
variant='top'
/>
) : null}
<Card.Body>
<Card.Title>{anime.title}</Card.Title>
<p className='small'>Rating: {anime.rating}</p>
<p className='small'>Score: {anime.score}/10</p>
<Card.Text>{anime.description}</Card.Text>
{Auth.loggedIn() && (
<Button
disabled={savedAnimeIds?.some(
(savedAnimeId) => savedAnimeId === anime.animeId
)}
className='btn-block btn-info'
onClick={() => handleSaveAnime(anime.animeId)}
>
{savedAnimeIds?.some(
(savedAnimeId) => savedAnimeId === anime.animeId
)
? 'This anime has already been saved!'
: 'Save this anime!'}
</Button>
)}
</Card.Body>
</Card>
</Item>
);
})}
</Carousel>
</CardColumns>
</Link>
<PopularAnime />
</div>
<Footer />
</div>
);
}
export default Home;
Thank You For Any Help!
try to add onPress to your image to go to our link
<TouchableHighlight
onPress={() => Linking.openURL('https://website.com')}>
<Image
source={{uri: image source}}
/>
</TouchableHighlight>
Related
Currently I am working on a function that exports components to PDFs
`
import { PDFExport } from "#progress/kendo-react-pdf";
import { useAppDispatch, useAppSelector } from "hooks";
import { get } from "lodash";
import { ManagerGeotechnicalDetail } from "pages/GeotechnicalDetail";
import PointDetail from "pages/PointDetail";
import DataChilAnalyticsDetails from "pages/Project/DataChildAnalyticsDetails";
import { FC, useEffect, useRef } from "react";
import { getListPoints } from "store/actions/datamap";
import { ETypeParent } from "../../state/reducer";
interface Props {
dataOverview: any[];
title: string;
idButton: string;
}
const DownloadPDF: FC<Props> = ({ dataOverview, title, idButton }) => {
const dispatch = useAppDispatch();
const [dataPointsSelected, projectState] = useAppSelector((state) => [
state.datamap.data,
state.project.project,
]);
const pdfExportComponent = useRef<PDFExport>(null);
useEffect(() => {
if (projectState.projectnumber) {
dispatch(
getListPoints(
get(projectState, "bounds"),
projectState,
dataPointsSelected.dataSummaryPoints
)
);
}
//eslint-disable-next-line
}, [projectState]);
const renderUIPDF = (data) => {
switch (data.typeChild) {
case ETypeParent.RAW_DATA:
return (
<PointDetail
match={{
params: {
idpoint: data.pointid,
id: projectState.projectid,
isHideForExport: true,
},
}}
/>
);
case ETypeParent.ASSIGNING_GEOLOGICAL_UNITS:
return (
<ManagerGeotechnicalDetail
match={{
params: {
idpointGeotechnical: data.pointid,
id: projectState.projectid,
isHideForExport: true,
},
}}
/>
);
case ETypeParent.DATA_ANALYTICSANALYTICS:
return (
<DataChilAnalyticsDetails
match={{
params: {
idDataChildAnalytics: data.childanalysisid,
id: projectState.projectid,
isHideForExport: true,
},
}}
/>
);
default:
return;
}
};
return (
<div>
<div className="example-config">
<button
id={idButton}
className="btn btn-success mt-2 me-4 k-button k-button-md k-rounded-md k-button-solid k-button-solid-base"
onClick={() => {
if (pdfExportComponent.current) {
pdfExportComponent.current.save();
}
}}
disabled={title.length === 0}
>
Export PDF
</button>
</div>
<div className="hide-ui">
<PDFExport
paperSize="A3"
forcePageBreak=".page-break"
landscape={true}
ref={pdfExportComponent}
fileName={title}
title={title}
scale={0.6}
margin={{
top: "0.75cm",
left: "3.6cm",
right: "3.6cm",
bottom: "0.75cm",
}}
>
{dataOverview &&
dataOverview.map((data, index) => (
<div
key={index}
style={{
width: "1620px",
}}
>
{renderUIPDF(data)}
{index !== dataOverview.length - 1 && (
<div className="page-break" />
)}
</div>
))}
</PDFExport>
</div>
</div>
);
};
export default DownloadPDF;
`
The problem I am facing now is when the dataOverview list has many objects, it will map the data to the components in renderUIPDF then I will call multiple APIs at the same time in those components.
At that time, there will be lag or website freeze :(
So is there any way I can improve the performance of my website without lag, freeze when calling too many APIs at once, the number can be up to more than 100 APIs at once?
I integrated a webcam capture file in my application while working in ReactJS. I'd like to know if there's any way that the captured image gets saved in a particular code directory when I click the button. The code looks something like this :
import React, { useState } from 'react';
import Webcam from "react-webcam";
const videoConstraints = {
width: 220,
height: 200,
facingMode: "user"
};
const WebcamCapture = () => {
const [image,setImage]=useState('');
const webcamRef = React.useRef(null);
const capture = React.useCallback(
() => {
const imageSrc = webcamRef.current.getScreenshot();
setImage(imageSrc)
});
return (
<div className="webcam-container">
<div className="webcam-img">
{image == '' ? <Webcam
audio={false}
height={200}
ref={webcamRef}
screenshotFormat="image/jpeg"
width={220}
videoConstraints={videoConstraints}
/> : <img src={image} />}
</div>
<div>
{image != '' ?
<button onClick={(e) => {
e.preventDefault();
setImage('')
}}
className="webcam-btn">
Retake Image</button> :
<button onClick={(e) => {
e.preventDefault();
capture();
}}
className="webcam-btn">Capture</button>
}
</div>
</div>
);
};
export default WebcamCapture;
I have a problem with my react app. I have a blog page where I can create blog posts and display them to the screen. In this part everything works fine. User logs in and can write a post. Each post contains a Read more... link and if the user clicks on that link the app redirects to the actual blog post. There the user can read the whole blog and add some comments. Everything works perfectly except when the user refreshes the page, everything disappears without any error in the console. I use firebase as my back-end and everything is saved there just like it has to be. Each time I click on the particular post I get redirected to that post and everything is ok, but when I refresh the page everything disappears, the post, the comments, even the input field and the submit comment button.
Here is a picture before refresh:
Before
here is a picture after refresh:
After
Also I will include the code for the actual blog and comment section.
The BlogAndCommentPage contains the actual blog post and holds the input field for the comments and the comments that belong to this post.
import React from 'react'
import { projectFirestore } from '../../firebase/config';
import BackToBlogs from './BackToBlogs'
import AddComment from '../commentComponents/AddComment'
class BlogAndCommentPage extends React.Component {
state = { param: '', blog: [] }
componentDidMount = () => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString)
const id = urlParams.get('id')
this.setState({ param: id })
const fetchDataFromFireBase = async () => {
projectFirestore.collection('Blogs').doc(id).get()
.then(doc => {
if(doc.exists) {
let document = [];
document.push(doc.data());
this.setState({ blog: document })
}
})
}
fetchDataFromFireBase()
}
renderContent() {
// Display the blog
const blogs = this.state.blog?.map(value => {
return (
<div key={value.post.blogID}>
<h1>{value.post.title}</h1>
<h6>{`${value.post.name} - ${value.post.date}`}</h6>
<p>{value.post.body}</p>
</div>
)
})
return blogs;
}
render() {
const displayedBlog = this.state.param
return (
<div>
{
displayedBlog ? (
<div>
{this.renderContent()}
<BackToBlogs />
<hr></hr>
<h5 className="mb-2">Add a comment</h5>
<AddComment param={this.state.param} />
</div>
) : ''
}
</div>
)
}
}
export default BlogAndCommentPage
The AddComment component holds the submit button for the comments and the list of the components
import React, { useState, useEffect } from 'react'
import SubmitComment from './SubmitComment'
import CommentHolder from './CommentHolder';
import { useSelector, useDispatch } from 'react-redux';
const AddComment = ({ param }) => {
const [comment, setComment] = useState('');
useEffect(() => {
if (sessionStorage.getItem('user') === null) {
alert('You are not logged in. Click OK to log in.')
window.location = 'http://localhost:3000/'
}
}, [])
const dispatch = useDispatch();
const state = useSelector((state) => state.state);
if (state) {
setTimeout(() => {
setComment('')
dispatch({ type: "SET_FALSE" })
}, 50)
}
return (
<div>
<div>
<div className="row">
<div className="col-sm">
<div className="form-group">
<textarea rows="4" cols="50" placeholder="Comment" className="form-control mb-3" value={comment} onChange={(e) => setComment(e.target.value)} />
</div>
</div>
</div>
</div>
<div className="mb-3">
<SubmitComment comment={comment} param={param} />
</div>
<CommentHolder param={param} />
</div>
)
}
export default AddComment
The CommentHolder renders each comment that belong to that post
import React from 'react';
import { projectFirestore } from '../../firebase/config';
import DeleteComment from './DeleteComment'
class CommentHolder extends React.Component {
state = { docs: [] }
_isMounted = false;
componentDidMount = () => {
const fetchDataFromFireBase = async () => {
const getData = await projectFirestore.collection("Comments")
getData.onSnapshot((querySnapshot) => {
var documents = [];
querySnapshot.forEach((doc) => {
documents.push({ ...doc.data(), id: doc.id });
});
if (this._isMounted) {
this.setState({ docs: documents })
}
});
}
fetchDataFromFireBase()
this._isMounted = true;
}
componentWillUnmount = () => {
this._isMounted = false;
}
renderContent() {
// Delete comments
const deleteComment = async (id) => {
projectFirestore.collection('Comments').doc(String(id)).delete().then(() => {
console.log(`Blog with id: ${id} has been successfully deleted!`)
})
}
// Build comments
let user;
if (sessionStorage.getItem('user') === null) {
user = [];
} else {
user = JSON.parse(sessionStorage.getItem('user'));
const commentArray = this.state.docs?.filter(value => value.blogID === this.props.param)
.sort((a, b) => (a.time > b.time) ? -1 : (b.time > a.time) ? 1 : 0)
.map(comment => {
return (
<div key={comment.id} className="card mb-3" >
<div className="card-body">
<div className="row">
<div className="col-sm">
<h6>{`${comment.name} - ${comment.time}`}</h6>
<p>{comment.comment}</p>
</div>
<div className="col-sm text-right">
{user[0].id === comment.userID ? <DeleteComment commentid={comment.id} onDeleteComment={deleteComment} /> : ''}
</div>
</div>
</div>
</div>
)
});
const updateComments = () => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString)
const id = urlParams.get('id')
const updateComment = projectFirestore.collection('Blogs').doc(id);
return updateComment.update({
'post.comments': commentArray.length
})
}
updateComments()
return commentArray;
}
}
render() {
return (
<div>
{this.renderContent()}
</div>
)
}
}
export default CommentHolder
The DeleteComment deletes the comment
import React from 'react'
const DeleteComment = ({ commentid, onDeleteComment }) => {
return (
<div>
<button onClick={() => onDeleteComment(commentid)} className='btn btn-outline-danger'>X</button>
</div>
)
}
export default DeleteComment
The SubmitComment stores the comment in the Firebase
import React from 'react'
import { projectFirestore } from '../../firebase/config';
import { v4 as uuidv4 } from 'uuid';
import { useDispatch } from 'react-redux';
const SubmitComment = ({ comment, param }) => {
const dispatch = useDispatch();
const onCommentSubmit = () => {
let user;
if (sessionStorage.getItem('user') === null) {
user = [];
} else {
user = JSON.parse(sessionStorage.getItem('user'));
projectFirestore.collection('Comments').doc().set({
id: uuidv4(),
comment,
name: `${user[0].firstName} ${user[0].lastName}`,
userID: user[0].id,
blogID: param,
time: new Date().toLocaleString()
})
dispatch({ type: "SET_TRUE" });
}
}
return (
<div>
<button onClick={() => onCommentSubmit()} className='btn btn-primary'>Add comment</button>
</div>
)
}
export default SubmitComment
In case there is a rout problem here is the code for the routing between the blogs section and the blog + comments section
return (
<Router >
<Route path='/content-page' exact render={(props) => (
<>
<BlogAndCommentPage />
</>
)} />
<Route path='/blogpage' exact render={(props) => (
<>
<div>
<div className="row">
<div className="col-8">
<h1 className='mb-3'>Blog</h1>
</div>
<div className="col-4 mb-3">
<LogoutButton onLogOut={logout} />
<h6 className='float-right mt-4 mr-2'>{displayUser}</h6>
</div>
</div>
{empty ? (<div style={{ color: "red", backgroundColor: "#F39189", borderColor: "red", borderStyle: "solid", borderRadius: "5px", textAlign: 'center' }} className="mb-2">Title and body cannot be blank</div>
) : ("")}
<InputArea getBlogContent={getBlogContent} />
<CreateBlog post={post} onCheckEmptyInputs={checkEmptyTitleAndBody} />
<hr />
<BlogHolder />
</div>
</>
)} />
</Router>
)
If anybody has any clue on why is this happening, please let me know.
Thank you.
As your website is CSR (client side rendering) it doesn't understand the URL in the first execution, you might need to configure a hash router, take a look at:
https://reactrouter.com/web/api/HashRouter
Also, there is a good answer about it here
This seems like it should be easy but why isn't a button callback function with a setState call not triggering a refresh of the data item? Actually it's just the computeSMA button that isn't changing the sma when the button is selected. The other two callbacks to set inputs work. The fetchData updates the charts so i can't figure this out!! Must be too tired ...
import React, { useState, useEffect } from "react"
import { useRecoilState } from "recoil";
import { closingDataAtom, metaDataAtom, tickerAtom, timeSeriesAtom , smaAtom} from '../../utils/atoms'
import { Container } from '#material-ui/core'
import '../../utils/Home.css'
import { VictoryChart, VictoryBar, VictoryTheme, VictoryVoronoiContainer, VictoryLine, VictoryBrushContainer, VictoryZoomContainer } from 'victory';
import { Chart, Axis, Tooltip, Line, Point } from "bizcharts";
import {XYPlot, LineSeries} from 'react-vis';
const APIKEY = 'demo'
const Home = () => {
const [apikey, setApiKey] = useState(APIKEY)
const [ticker, setTicker] = useRecoilState(tickerAtom);
const [metadata, setMetaData] = useRecoilState(metaDataAtom)
const [closingdata, setClosingData] = useRecoilState(closingDataAtom)
const [dates, setDates] = useRecoilState(timeSeriesAtom)
const [sma, setSMA] = useRecoilState(smaAtom)
const TIME_RESOLUTION = 'Daily'
var requestUrl
if (TIME_RESOLUTION === 'Daily') {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey
} else {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_WEEKLY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey;
}
const fetchData = async () => {
fetch(requestUrl)
.then(response => response.json())
.then(data => {
var closing_price = []
var metadata = []
var dat = []
Object.keys(data['Time Series (Daily)']).forEach((dateKey) => {
closing_price.push(data['Time Series (Daily)'][dateKey]['5. adjusted close'])
dat.push({ 'date': new Date(dateKey) })
})
Object.keys(data['Meta Data']).forEach((metaKey) => {
metadata.push(data['Meta Data'][metaKey])
})
setDates(dat.reverse())
setClosingData(closing_price.reverse())
setMetaData(metadata)
})
.catch(e => {
});
};
const handleSMACompute = (e) => {
var sm = ['2', '3', '4']
setSMA(sm) <====== REACT IS NOT "REACTING"
}
const handleTickerInput = (e) => {
setTicker(e.target.value)
}
const handleAPIInput = (e) => {
setApiKey(e.target.value)
}
return (
<>
<Container className="container" maxWidth="sm">
<div>
<label>Ticker:</label> {ticker}
<input type="text" name="ticker" onChange={handleTickerInput} />
</div>
<div>
<label>APIKEY:</label> {apikey}
<input type="text" name="apikey" onChange={handleAPIInput} />
</div>
<button onClick={fetchData}>
Click it
</button>
<Container className="container" maxWidth="sm">
<ul>{metadata}</ul>
</Container>
<button OnClick={handleSMACompute}> Generate SMA </button>
<Container className="container" maxWidth="sm">
<ul>The value is {sma}</ul>
</Container><div>
</div>
<VictoryChart
theme={VictoryTheme.material}
domainPadding={10}
>
<VictoryBar
style={{ data: { fill: "#c43a31" } }}
data={closingdata}
/>
</VictoryChart>
<div>
<VictoryChart
theme={VictoryTheme.material}
>
<VictoryLine
style={{
data: { stroke: "#c43a31" },
parent: { border: "1px solid #ccc" }
}}
animate={{
duration: 20,
onLoad: { duration: 20 }
}}
containerComponent={<VictoryZoomContainer zoomDomain={{x: [5, 35], y: [0, 100]}}/>}
categories={{
y: dates
}}
data={closingdata}
/>
</VictoryChart>
</div>
</Container>
</>
);
}```
It seems to have been the button setup. I changed to this and it works....??ggrrrr
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{metadata}</li>
</Container>
<button onClick={computeSMA}>
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{sma}</li>
</Container>
In your first code, you used OnClick as the event name. Should be onClick. It is a react syntax and it is case sensitive.
I am fetching data using the Foursquare API within a React app using the Google Map API and react-google-map.
When I create a list of venues and try to check if the error handling works by deleting some characters from the API link, the app crashes because the venues state is empty.
I should be adding a conditional statement to the function that maps through the data to check if it's null, but I don't know how to do it.
Here is my repository:
https://github.com/Katesold/Watford-Map
If someone could advise me on how to do it, I would really appreciate it.
What I have been trying looks like this:
filterCafes = (cafes, query) => {
cafes !== undefined ? cafes.filter(cafe =>
cafe.name.toLowerCase().includes(query.toLowerCase())): "error";
}
Here's the error I got:
app crashes
Welcome to StackOverflow.
Your problems can be alleviated by ensuring that cafes is set to null by default. Then in Sidebar1.js, I have added a truthy check to your filter that you used in the render method (cafePlaces && cafePlaces.filter...). This means that the filter will only be executed if cafePlaces was successfully fetched in your parent component, where you fetch the data.
You will also need to update Places.js, as you use cafes in there too. I have also added an early return to the filterCafes method as it will error out when you try filtering as not cafes were loaded into the parent state if the API called failed.
(P.S. Please note I broke your API call on purpose in the example code provided below).
App.js
import React, { Component } from "react";
import Map from "./Map";
import SideBar from "./SideBar1.js";
import "./App.css";
import Header from "./Header.js";
import Footer from "./Footer.js";
class App extends Component {
state = {
cafes: null,
clickedCafe: {},
filteredCafe: []
};
// get the data for the cafes in Watford and catch any errors from Foursquare and Map
componentDidMount() {
fetch(
"https://api.foursquare.com/v2/venues/search?ll=51.656489,-.39032&intent=browse&radius=10000&client_id=XQSXUGIR140AWUVFJJ120S31IPIXQYIO2QJ2ZN2U0ZPLLG4P&client_secret=A0N5P5VI4NG5UQK2GV2M0WU1FYY3KZ0EUYV0YMYZSX5IHHSU&v=26"
)
.then(response => response.json())
.then(data => {
this.setState({
cafes: data.response.venues,
filteredCafe: data.response.venues
});
})
.catch(error => {
alert(
"An error occurred while trying to fetch data from Foursquare: " +
error
);
});
window.gm_authFailure = () => {
alert("An error occurred while trying to load Google Map");
};
}
// Update filtered list of venues
updateList = filteredCafe => {
this.setState({ filteredCafe });
};
// Show the infowindow when a place is clicked
handleInfoWindow = clickedCafe => {
this.setState({ clickedPlace: clickedCafe });
this.setState({ menuHidden: false });
};
render() {
return (
<div className="app" role="application" aria-label="map">
<Header />
<Map
cafes={this.state.filteredCafe}
clickedPlace={this.state.clickedPlace}
handleInfoWindow={this.handleInfoWindow}
/>
<SideBar
cafes={this.state.cafes}
handleInfoWindow={this.handleInfoWindow}
updateList={this.updateList}
menuHidden={this.state.menuHidden}
/>
<Footer />
</div>
);
}
}
export default App;
Sidebar1.js
import React, { Component } from "react";
class SideBar extends Component {
state = {
query: ""
};
//filter the cafes depending on the search
refreshQuery = query => {
this.setState({ query });
this.props.updateList(this.filterCafes(this.props.cafes, query));
};
filterCafes = (cafes, query) => {
if (!cafes) {
return;
}
cafes.filter(cafe => cafe.name.toLowerCase().includes(query.toLowerCase()));
};
//cafes displayed in SideBar
render() {
const cafePlaces = this.props.cafes;
const typedQuery = this.state.query;
const listCafes =
cafePlaces &&
this.filterCafes(cafePlaces, typedQuery).map((cafe, idx) => {
return (
<li
key={cafe.id}
className="cafe"
tabIndex={0}
aria-label={cafe.name}
onClick={() => {
this.props.handleInfoWindow(idx);
}}
onKeyPress={() => {
this.props.handleInfoWindow(idx);
}}
>
{cafe.name}
</li>
);
});
return (
<aside>
<div className="sideBar">
<div className="locations-list">
<input
type="text"
placeholder="Search for a place"
aria-label="Type to look for a cafe"
value={this.state.query}
onChange={e => this.refreshQuery(e.target.value)}
/>
<ul aria-labelledby="Cafe list">{listCafes}</ul>
</div>
</div>
</aside>
);
}
}
export default SideBar;
Places.js
import React, { Component } from "react";
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
InfoWindow
} from "react-google-maps";
class Map extends Component {
render() {
const places = this.props.cafes;
const animatePlace = this.props.clickedPlace;
/* create Google Map App and markers, infowindow from FourSquare API from https://github.com/tomchentw/react-google-maps/blob/master/src/docs/configuration.md
and https://tomchentw.github.io/react-google-maps/#infowindow and https://github.com/tomchentw/react-google-maps/issues/753 */
const style = {
height: "100%"
};
const styleMap = {
height: "600px",
width: "100%"
};
//define map with markers and infowindow then return it below to display within container div
const WMap = withScriptjs(
withGoogleMap(props => (
<GoogleMap
defaultZoom={14}
defaultCenter={{ lat: 51.656489, lng: -0.39032 }}
>
{places &&
places.map((place, i) => (
<Marker
key={i}
position={{ lat: place.location.lat, lng: place.location.lng }}
id={place.id}
name={place.name}
onClick={() => {
this.props.handleInfoWindow(i);
}}
animation={
animatePlace === i ? window.google.maps.Animation.DROP : null
}
>
{animatePlace === i && (
<InfoWindow onCloseClick={props.onToggleOpen}>
<div
className="infoWindow"
tabIndex={0}
aria-label="Infowindow"
>
<h2>{place.name}</h2>
<hr />
<p>
<strong>Address: </strong>
{place.location.formattedAddress[0]}
</p>
<p>{place.location.formattedAddress[1]}</p>
<p>{place.location.formattedAddress[2]}</p>
<p>{place.location.formattedAddress[3]}</p>
<p>{place.location.formattedAddress[4]}</p>
</div>
</InfoWindow>
)}
</Marker>
))}
</GoogleMap>
))
);
return (
<div className="map">
<div className="wmap" role="application">
<WMap
googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyCFk8F7SikfJihxgfeWargVEIsb31hwlwA&v=3.exp"
loadingElement={<div style={style} />}
containerElement={<div style={styleMap} />}
mapElement={<div style={style} />}
/>
</div>
</div>
);
}
}
export default Map;