ReactJS – how to check whether value is undefined - reactjs

I am creating a find nearest restaurant app using Google Places API. I am calling for a background image in my ResultItem component in a prop <ResultsItem name={places.name} image={Thumbnail} rating={places.rating} rating_total={places.user_ratings_total} />
This image is defined as Thumbnail in const above this part of the code. My code runs smoothly but as soon as places.photos[0] returns as undefined (meaning that it is Google Place that doesn't have any image uploaded) i get an error saying:
Unhandled Rejection (TypeError): Cannot read property '0' of undefined
I think what I have to do is check whether places.photos[0] is undefined or not but I do not seem to get it right...
My goal is to display another placeholder image when this value turns out undefined. If it is defined though the component should take the image from google places api.
Can someone help me?
FULL COMPONENT:
import React, { Component } from 'react';
// Imports
import axios from 'axios';
import Script from 'react-load-script';
import Placeholder from './Placeholder.jsx';
import FadeIn from 'react-fade-in';
import {
Spinner,
Paragraph,
SideSheet,
Tooltip,
IconButton,
SearchInput
} from 'evergreen-ui';
import ResultsItem from '../../components/ResultsItem/ResultsItem.jsx';
import Geocode from 'react-geocode';
// Styles
import './Search.scss';
class Autocomplete extends Component {
// Define Constructor
constructor(props) {
super(props);
// Declare State
this.state = {
type: 'restaurant',
radius: 10,
lat: '59.0738',
lng: '41.3226',
city: '',
query: '',
open: false,
places: [],
place_detail: [],
sidebar: false,
loading: true
};
this.currentLocationOnClick = this.currentLocationOnClick.bind(this);
this.handlePlaceSelect = this.handlePlaceSelect.bind(this);
}
currentLocationOnClick = async () => {
let { lat, lng, places } = this.state;
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&type=restaurant&radius=${5 *
1000}&key=MY_API_KEY`;
navigator.geolocation.getCurrentPosition(
async position => {
this.setState({ lat: position.coords.latitude });
this.setState({ lng: position.coords.longitude });
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${
position.coords.latitude
},${position.coords.longitude}&type=restaurant&radius=${5 *
1000}&key=MY_API_KEY`;
const response = await axios.get(URL);
console.log(response.data);
places = response.data.results;
this.setState({ places });
},
error => {
console.log('Error getting location');
}
);
};
async componentDidMount() {
const url = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=
${this.state.lat},${this.state.lng}type=restaurant&radius=${2 *
1000}&key=MY_API_KEYE`;
const response = await fetch(url);
const data = await response.json();
this.setState({ places: data.results });
console.log(data.results);
}
handleScriptLoad = () => {
// Declare Options For Autocomplete
const options = {
types: ['address']
}; // To disable any eslint 'google not defined' errors
// Initialize Google Autocomplete
/*global google*/ this.autocomplete = new google.maps.places.Autocomplete(
document.getElementById('autocomplete'),
options
);
// Avoid paying for data that you don't need by restricting the set of
// place fields that are returned to just the address components and formatted
// address.
this.autocomplete.setFields(['address_components', 'formatted_address']);
// Fire Event when a suggested name is selected
this.autocomplete.addListener('place_changed', this.handlePlaceSelect);
};
handlePlaceSelect = async () => {
let { query, lat, lng } = this.state;
this.setState({ loading: true });
// Extract City From Address Object
const addressObject = this.autocomplete.getPlace();
const address = addressObject.address_components;
Geocode.setApiKey('MY_API_KEY');
// Check if address is valid
let city;
if (address) {
city = address[0].long_name;
query = addressObject.formatted_address;
}
try {
const response = await Geocode.fromAddress(query);
({ lat, lng } = response.results[0].geometry.location);
} catch (error) {
console.error(error);
}
let places;
try {
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&type=restaurant&radius=${5 *
1000}&key=MY_API_KEY`;
const response = await axios.get(URL);
console.log(response.data);
places = response.data.results;
} catch (error) {
console.log(error.message);
}
this.setState({ query, places, city, lat, lng });
setTimeout(() => this.setState({ loading: false }), 400);
};
render() {
const { loading } = this.state;
return (
<div>
<div className="flex align-center">
<div className="search">
<SearchInput
id="autocomplete"
placeholder="Search by address"
width="100%"
height={56}
/>
<Script
url="https://maps.googleapis.com/maps/api/js?key=MY_API_KEY&libraries=places,geometry&callback=initAutocomplete"
onLoad={this.handleScriptLoad}
/>
</div>
<div className="current-location">
<Tooltip content="Use current location">
<IconButton
icon="locate"
iconSize={16}
height={32}
onClick={this.currentLocationOnClick}
>
{this.state.lat} & {this.state.lng}
</IconButton>
</Tooltip>
</div>
</div>
<div className="results">
{this.state.places.map(places => {
const Thumbnail = `https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=${places.photos[0].photo_reference}&key=MY_API_KEY
`;
return (
<div
className="results-flex"
onClick={async () => {
let loading;
let sidebar;
let place_detail;
try {
const URL = `https://maps.googleapis.com/maps/api/place/details/json?place_id=${places.place_id}&fields=name,rating,formatted_phone_number&key=
MY_API_KEY`;
const response = await axios.get(URL);
console.log(response.data);
place_detail = response.data.result;
} catch (error) {
console.log(error.message);
}
this.setState(
{ place_detail, sidebar: true }
// () => this.props.sideBarOpen()
);
}}
>
{this.state.loading ? (
<>
<div className="flex justify-center">
<Placeholder />
</div>
</>
) : (
<FadeIn>
<ResultsItem
name={places.name}
image={Thumbnail}
rating={places.rating}
rating_total={places.user_ratings_total}
/>
</FadeIn>
)}
</div>
);
})}
<SideSheet
isShown={this.state.sidebar}
onCloseComplete={() => this.setState({ sidebar: false })}
>
<Paragraph margin={40}>{this.state.place_detail.name}</Paragraph>
</SideSheet>
</div>
</div>
);
}
}
export default Autocomplete;

I think the best solution is to assign it to a variable :
const photoReference = places && places.photos[0] ? places.photos[0].photo_reference : placeholder;

try using if else for it
for eg:
if(!!places.photos[0]) {
const Thumbnail =
`https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=${places.photos[0].photo_reference}&key=MY_API_KEY
}
else { const Thumbnail = `another image url` }

Related

React: Uncaught TypeError: Cannot read properties of undefined (reading 'jpg')

I'm new to react, I'm trying to pick a random anime from jikan, which will return an object containing a key images(an object). images containg a key jpg. It keeps saying that "Cannot read properties of undefined (reading 'jpg')". Please help! Thank you so much
This is the main component:
import React, { useState, useEffect } from "react";
import "./animedb.css";
import TopAnime from "./topanime";
import AnimeSearch from "./animesearch";
import AnimeRandom from "./animerandom";
import AnimeDisplay from "./animedisplay";
import NavBar from "./navbar";
const AnimeDB = () => {
const [animeList, setAnimeList] = useState([]);
const [isRandomAnime, setIsRandomAnime] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [topAnime, setTopAnime] = useState([]);
const [searchAnime, setSearchAnime] = useState("");
const [randomAnime, setRandomAnime] = useState({});
const url = "https://api.jikan.moe/v3";
const ShowAnime = async () => {
let display_url = url + "/season/2022/spring";
const response = await fetch(display_url);
const result = await response.json();
setAnimeList(result.anime.slice(0, 10));
setIsLoading(false);
};
useEffect(() => {
ShowAnime();
FindTopAnime();
}, []);
const FindTopAnime = async () => {
let top_url = url + "/top/anime/1/bypopularity";
try {
const res = await fetch(top_url);
const result = await res.json();
setTopAnime(result.top.slice(0, 5));
} catch (error) {
console.log(error);
}
};
const FindSearchAnime = async (query) => {
setIsLoading(true);
try {
const res = await fetch(
`https://api.jikan.moe/v3/search/anime?q=${query}&order_by=title&sort=asc`
);
const result = await res.json();
setAnimeList(result.results.slice(0, 10));
setIsLoading(false);
} catch (error) {
console.log(error);
}
};
const handleSearch = (e) => {
e.preventDefault();
FindSearchAnime(searchAnime);
};
const handleRandom = async () => {
setIsLoading(true);
setIsRandomAnime(true);
let res = await fetch("https://api.jikan.moe/v4/random/anime");
let result = await res.json();
setRandomAnime(result.data);
setIsLoading(false);
console.log(result.data);
};
return (
<div className="wrapperAnime">
{/* <NavBar /> */}
<h1 id="appName">
u<strong id="strong">ME</strong>i
</h1>
<div className="container">
<TopAnime topAnime={topAnime} />
<div className="display">
<button onClick={handleRandom}>Random Anime</button>
<AnimeSearch
onSearch={handleSearch}
setSearchAnime={setSearchAnime}
value={searchAnime}
/>
<AnimeDisplay
isLoading={isLoading}
animeList={animeList}
isRandomAnime={isRandomAnime}
randomAnime={randomAnime}
setIsRandomAnime={setIsRandomAnime}
/>
</div>
</div>
</div>
);
};
export default AnimeDB;
component: AnimeDisplay:
function AnimeDisplay(props) {
if (props.isRandomAnime) {
let { mal_id, title, images } = props.randomAnime;
return (
<div className="collections">
<div key={mal_id} className="card">
<h1>{title}</h1>
<img src={images.jpg.image_url} alt="test" />
</div>
</div>
);
} else {
return (
<div className="collections">
{props.animeList.map((ele) => {
const { mal_id, image_url, title, episodes } = ele;
if (props.isLoading) {
return <h1>Loading...</h1>;
}
return (
<div key={mal_id} className="card">
<img src={image_url} alt="" />
<h1>{title}</h1>
</div>
);
})}
</div>
);
}
}
export default AnimeDisplay;
#Update: I've tried your ways, but it didn't work either. This is the returned data:
{mal_id: 43450, url: 'https://myanimelist.net/anime/43450/Shao_Nian_Effendi', images: {…}, trailer: {…}, title: 'Shao Nian Effendi', …}aired: {from: '2012-01-01T00:00:00+00:00', to: null, prop: {…}, string: '2012 to ?'}airing: falsebackground: nullbroadcast: {day: null, time: null, timezone: null, string: 'Unknown'}demographics: [{…}]duration: "12 min per ep"episodes: 104explicit_genres: []favorites: 0genres: []images: jpg: image_url: "https://cdn.myanimelist.net/images/anime/1248/118197.jpg"large_image_url: "https://cdn.myanimelist.net/images/anime/1248/118197l.jpg"small_image_url: "https://cdn.myanimelist.net/images/anime/1248/118197t.jpg"[[Prototype]]: Objectwebp: {image_url: 'https://cdn.myanimelist.net/images/anime/1248/118197.webp', small_image_url: 'https://cdn.myanimelist.net/images/anime/1248/118197t.webp', large_image_url: 'https://cdn.myanimelist.net/images/anime/1248/118197l.webp'}[[Prototype]]: Objectlicensors: []mal_id: 43450members: 31popularity: 19210producers: []rank: 14029rating: "PG - Children"score: nullscored_by: nullseason: nullsource: "Other"status: "Finished Airing"studios: []synopsis: nullthemes: []title: "Shao Nian Effendi"title_english: nulltitle_japanese: "少年阿凡提"title_synonyms: (3) ['Shao Nian A Fan Ti', 'Shao Nian Afanti', 'Young Effendi']trailer: {youtube_id: null, url: null, embed_url: null, images: {…}}type: "TV"url: "https://myanimelist.net/anime/43450/Shao_Nian_Effendi"year: null[[Prototype]]: Object
I think it's because your initial state doesn't have any "jpg" key and when it reads your code for the first time (before useEffect) it throws an error. you could either (the wrong way!) update your initial state like so: useState([{images:{jpg:""}}]) or the right way which is conditional rendering!
As Shahryar_Jahanshahloo said, you must provide a default value for your initial state, so change the randomAnime this way:
// note that the object is much more greater than this, but I only included
// the properties that you are using
const [randomAnime, setRandomAnime] = useState({
mal_id: "",
title: "",
images: { jpg: { image_url: "" } }
});
Another thing I notice that is wrong is your loading state in your AnimeDisplay, you should do something like this:
function AnimeDisplay(props) {
const { mal_id, title, images } = props.randomAnime;
// move the loading conditional to outside of the mapping
// so when the component is fetching the data it only
// renders the loading element
if (props.isLoading) {
return <h1>Loading...</h1>;
}
// after the fetch you can render your components normally
if (props.isRandomAnime) {
return (
<>
<div className="collections">
<div key={mal_id} className="card">
<h1>{title}</h1>
<img src={images?.jpg.image_url} alt="test" />
</div>
</div>
</>
);
} else {
return (
<div className="collections">
{props.animeList.map((ele) => {
const { mal_id, image_url, title, episodes } = ele;
return (
<div key={mal_id} className="card">
<img src={image_url} alt="" />
<h1>{title}</h1>
</div>
);
})}
</div>
);
}
}

draft.js - retrieve formatted text from db

My draft.js <TextEditor /> populates body with the text e.g: '{"blocks":[{"key":"3mont","text":"lorem ipsum","type":"unstyled","depth":0,"inlineStyleRanges":[],"entityRanges":[],"data":{}}],"entityMap":{}}' and persists it to the db after using convertToRaw().
In Post.js, I want to retrieve and display the formatted text from the db.
I've read that in order to do this, I must use convertToRaw() and then convertFromRaw() when retrieving it from the db but I'm having the same problems as this (I'm receiving the cors error and Unexpected token u in JSON at position 0) whenever I use convertFromRaw() and try to retrieve the formatted text from the db.
I've set up my server to support cors so why am I receiving the cors error? Is it because I am trying to parse an invalid response into JSON?
How can I get the formatted text from the db in Post.js?
Any help would be really appreciated!
GitHub
CreatePost.js
class CreatePost extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
body: EditorState.createEmpty(),
};
}
changeHandler = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
submitHandler = (e) => {
e.preventDefault();
const {
user: { _id },
} = isAuthenticated();
axios({
url: `${API}/post/new-post/${_id}`,
method: "POST",
data: {
...this.state,
body: JSON.stringify(convertToRaw(this.state.body.getCurrentContent())),
},
})
.then((response) => {
// this.setState({ createdPost: this.state.title });
return response
})
.catch((error) => {
if (!this.state.title || !this.state.body) {
this.setState({
error: "This post must contain a title and a body.",
});
}
console.log(error);
});
};
// Attempt to map through blocks
//getText = () => {
// const {body} = this.state;
//const arr = body.blocks.map(({ text }) => text).join(' ')
// console.log(arr)
//}
render() {
const { title, body } = this.state;
return (
<>
<Navbar />
<Tabs>
<TabList>
<Tab>Draft</Tab>
<Tab>Preview</Tab>
</TabList>
<TabPanel>
<div>
<form onSubmit={this.submitHandler}>
<div>
// title input
</div>
<div>
<TextEditor
onChange={(value) => this.setState({ body: value })}
editorState={body}
/>
</div>
<button type="submit">
Publish
</button>
</form>
</div>
</TabPanel>
<TabPanel>
<div>
<h1>{title}</h1>
// display body text value here too
{this.getText()}
</div>
</TabPanel>
</Tabs>
</>
);
}
}
Post.js (display body text)
const [post, setPost] = useState({});
const [error, setError] = useState(false);
const id = props.match.params.id;
const loadSinglePost = (slug, id) => {
read(slug, id).then((data) => {
if (error) {
console.log(data.error);
setError(data.error);
} else {
setPost(data)
console.log(data);
}
});
};
useEffect(() => {
const slug = props.match.params.slug;
loadSinglePost(slug, id);
}, [props]);
return (
<>
<div>
<h3>{post.title}</h3>
...
// display text value below
<p>{post.body}</p>
</div>
</div>
</>
);
};
TextEditor.js
class TextEditor extends React.Component {
constructor(props) {
super(props);
this.plugins = [addLinkPlugin];
}
toggleBlockType = (blockType) => {
this.props.onChange(RichUtils.toggleBlockType(this.props.editorState, blockType));
};
handleKeyCommand = (command) => {
const newState = RichUtils.handleKeyCommand(
this.props.editorState,
command
);
if (newState) {
this.props.onChange(newState);
return "handled";
}
return "not-handled";
};
onUnderlineClick = () => {
this.props.onChange(
RichUtils.toggleInlineStyle(this.props.editorState, "UNDERLINE")
);
};
onBoldClick = (event) => {
this.props.onChange(RichUtils.toggleInlineStyle(this.props.editorState, "BOLD"));
};
onItalicClick = () => {
this.props.onChange(
RichUtils.toggleInlineStyle(this.props.editorState, "ITALIC")
);
};
onAddLink = () => {
const editorState = this.props.editorState;
const selection = editorState.getSelection();
const link = window.prompt("Paste the link -");
if (!link) {
this.props.onChange(RichUtils.toggleLink(editorState, selection, null));
return "handled";
}
const content = editorState.getCurrentContent();
const contentWithEntity = content.createEntity("LINK", "MUTABLE", {
url: link,
});
const newEditorState = EditorState.push(
editorState,
contentWithEntity,
"create-entity"
);
const entityKey = contentWithEntity.getLastCreatedEntityKey();
this.props.onChange(RichUtils.toggleLink(newEditorState, selection, entityKey));
};
toggleBlockType = (blockType) => {
this.props.onChange(RichUtils.toggleBlockType(this.props.editorState, blockType));
};
render() {
return (
<div>
// formatting buttons
<div>
<Editor
blockStyleFn={getBlockStyle}
editorState={this.props.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.props.onChange}
plugins={this.plugins}
placeholder="Post Content"
/>
</div>
</div>
);
}
}
Apparently draft-js does not have html output function because it's supposed to have no assumption on the output so people can tune their output however they want (see this). This means we'll have to implement it ourselves and if you're looking for just an html or markdown output to preserve in the database, then this mono repo can be of help. I've implemented an example of how to do it in this sandbox. Note that I used dangerouslySetInnerHTML for demonstration which is not optimal. You may want to use sanitization and rich text components to display back the posts. As a matter of fact I'd suggest ditching html and going for markdown instead if possible.

ReactJS – if value is undefined, display placeholder image

0
I am creating a find nearest restaurant app using Google Places API. I am calling for a background image in my ResultItem component in a prop <ResultsItem name={places.name} image={Thumbnail} rating={places.rating} rating_total={places.user_ratings_total} />
This image is defined as Thumbnail in const above this part of the code. My code runs smoothly but as soon as places.photos[0] returns as undefined (meaning that it is Google Place that doesn't have any image uploaded) i get an error saying:
Unhandled Rejection (TypeError): Cannot read property '0' of undefined
I think what I have to do is check whether places.photos[0] is undefined or not but I do not seem to get it right...
My goal is to display another placeholder image when this value turns out undefined. If it is defined though the component should take the image from google places api.
This is what I have tried to do with the const Thumbnail to achieve it, but didnt work:
const Thumbnail =
places && places.photos[0]
? `https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=${places.photos[0].photo_reference}&key=MYAPIKEY`
: 'https://www.xy.com/images/placeholder.jpg';
Can someone help me?
FULL COMPONENT:
import React, { Component } from 'react';
// Imports
import axios from 'axios';
import Script from 'react-load-script';
import Placeholder from './Placeholder.jsx';
import FadeIn from 'react-fade-in';
import {
Spinner,
Paragraph,
SideSheet,
Tooltip,
IconButton,
SearchInput
} from 'evergreen-ui';
import ResultsItem from '../../components/ResultsItem/ResultsItem.jsx';
import Geocode from 'react-geocode';
// Styles
import './Search.scss';
class Autocomplete extends Component {
// Define Constructor
constructor(props) {
super(props);
// Declare State
this.state = {
type: 'restaurant',
radius: 10,
lat: '59.0738',
lng: '41.3226',
city: '',
query: '',
open: false,
places: [],
place_detail: [],
sidebar: false,
loading: true
};
this.currentLocationOnClick = this.currentLocationOnClick.bind(this);
this.handlePlaceSelect = this.handlePlaceSelect.bind(this);
}
currentLocationOnClick = async () => {
let { lat, lng, places } = this.state;
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&type=restaurant&radius=${5 *
1000}&key=MY_API_KEY`;
navigator.geolocation.getCurrentPosition(
async position => {
this.setState({ lat: position.coords.latitude });
this.setState({ lng: position.coords.longitude });
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${
position.coords.latitude
},${position.coords.longitude}&type=restaurant&radius=${5 *
1000}&key=MY_API_KEY`;
const response = await axios.get(URL);
console.log(response.data);
places = response.data.results;
this.setState({ places });
},
error => {
console.log('Error getting location');
}
);
};
async componentDidMount() {
const url = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=
${this.state.lat},${this.state.lng}type=restaurant&radius=${2 *
1000}&key=MY_API_KEYE`;
const response = await fetch(url);
const data = await response.json();
this.setState({ places: data.results });
console.log(data.results);
}
handleScriptLoad = () => {
// Declare Options For Autocomplete
const options = {
types: ['address']
}; // To disable any eslint 'google not defined' errors
// Initialize Google Autocomplete
/*global google*/ this.autocomplete = new google.maps.places.Autocomplete(
document.getElementById('autocomplete'),
options
);
// Avoid paying for data that you don't need by restricting the set of
// place fields that are returned to just the address components and formatted
// address.
this.autocomplete.setFields(['address_components', 'formatted_address']);
// Fire Event when a suggested name is selected
this.autocomplete.addListener('place_changed', this.handlePlaceSelect);
};
handlePlaceSelect = async () => {
let { query, lat, lng } = this.state;
this.setState({ loading: true });
// Extract City From Address Object
const addressObject = this.autocomplete.getPlace();
const address = addressObject.address_components;
Geocode.setApiKey('MY_API_KEY');
// Check if address is valid
let city;
if (address) {
city = address[0].long_name;
query = addressObject.formatted_address;
}
try {
const response = await Geocode.fromAddress(query);
({ lat, lng } = response.results[0].geometry.location);
} catch (error) {
console.error(error);
}
let places;
try {
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&type=restaurant&radius=${5 *
1000}&key=MY_API_KEY`;
const response = await axios.get(URL);
console.log(response.data);
places = response.data.results;
} catch (error) {
console.log(error.message);
}
this.setState({ query, places, city, lat, lng });
setTimeout(() => this.setState({ loading: false }), 400);
};
render() {
const { loading } = this.state;
return (
<div>
<div className="flex align-center">
<div className="search">
<SearchInput
id="autocomplete"
placeholder="Search by address"
width="100%"
height={56}
/>
<Script
url="https://maps.googleapis.com/maps/api/js?key=MY_API_KEY&libraries=places,geometry&callback=initAutocomplete"
onLoad={this.handleScriptLoad}
/>
</div>
<div className="current-location">
<Tooltip content="Use current location">
<IconButton
icon="locate"
iconSize={16}
height={32}
onClick={this.currentLocationOnClick}
>
{this.state.lat} & {this.state.lng}
</IconButton>
</Tooltip>
</div>
</div>
<div className="results">
{this.state.places.map(places => {
const Thumbnail = `https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=${places.photos[0].photo_reference}&key=MY_API_KEY
`;
return (
<div
className="results-flex"
onClick={async () => {
let loading;
let sidebar;
let place_detail;
try {
const URL = `https://maps.googleapis.com/maps/api/place/details/json?place_id=${places.place_id}&fields=name,rating,formatted_phone_number&key=
MY_API_KEY`;
const response = await axios.get(URL);
console.log(response.data);
place_detail = response.data.result;
} catch (error) {
console.log(error.message);
}
this.setState(
{ place_detail, sidebar: true }
// () => this.props.sideBarOpen()
);
}}
>
{this.state.loading ? (
<>
<div className="flex justify-center">
<Placeholder />
</div>
</>
) : (
<FadeIn>
<ResultsItem
name={places.name}
image={Thumbnail}
rating={places.rating}
rating_total={places.user_ratings_total}
/>
</FadeIn>
)}
</div>
);
})}
<SideSheet
isShown={this.state.sidebar}
onCloseComplete={() => this.setState({ sidebar: false })}
>
<Paragraph margin={40}>{this.state.place_detail.name}</Paragraph>
</SideSheet>
</div>
</div>
);
}
}
export default Autocomplete;
The problem might be arising beacuse when a Google Place doesn't have any image uploaded then there is not any element pointing to "places.photos[0]" which results in error: Cannot read property '0' of undefined.
So in const Thumbnail u must also check whether places.photos array exist or not.
I hope the below code solves your issue.
const Thumbnail =
places && places.photos && places.photos[0]
? `https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=${places.photos[0].photo_reference}&key=MYAPIKEY`
: 'https://www.xy.com/images/placeholder.jpg';

How to add page number to the URL

Could someone please tell me how can I add page number to my url. The component is as follows:
/** NPM Packages */
import React, { Component } from "react";
import { connect } from "react-redux";
import { Spinner, Pagination } from "react-bootstrap";
//import styles from "./App.module.css";
/** Custom Packages */
import List from "../List";
//import fetchCategories from "../../../actions/configuration/category/fetchCategories";
import deleteCategory from "../../../actions/configuration/category/deleteCategory";
import API from "../../../../app/pages/utils/api";
class Category extends Component {
constructor(props) {
super(props);
this.state = {
mesg: "",
mesgType: "",
isLoading: true,
total: null,
per_page: null,
current_page: 1,
pdata: []
};
this.fetchCategoriesAPI = this.fetchCategoriesAPI.bind(this);
}
fetchCategoriesAPI = async pno => {
await API.get("categories?offset=" + (pno.index+1))
.then(res => this.setState({ pdata: res.data }))
.then(() => this.props.passToRedux(this.state.pdata))
.catch(err => console.log(err));
};
componentDidMount = async () => {
const { state } = this.props.location;
if (state && state.mesg) {
this.setState({
mesg: this.props.location.state.mesg,
mesgType: this.props.location.state.mesgType
});
const stateCopy = { ...state };
delete stateCopy.mesg;
this.props.history.replace({ state: stateCopy });
}
this.closeMesg();
await this.fetchCategoriesAPI(1);
this.setState({ isLoading: false });
};
onDelete = async id => {
this.props.removeCategory(id);
await deleteCategory(id).then(data =>
this.setState({ mesg: data.msg, mesgType: "success" })
);
this.closeMesg();
};
closeMesg = () =>
setTimeout(
function() {
this.setState({ mesg: "", mesgType: "" });
}.bind(this),
10000
);
/** Rendering the Template */
render() {
let activePage = this.state.pdata.currPage;
let items = [];
let totalPages = Math.ceil(this.state.pdata.totalCount / 10);
for (let number = 1; number <= totalPages; number++) {
items.push(
<Pagination.Item key={number} active={number == activePage}>
{number}
</Pagination.Item>
);
}
const paginationBasic = (
<div>
<Pagination>
{items.map((item,index)=>{
return <p key={index} onClick={() => this.fetchCategoriesAPI({index})}>{item}</p>
})}
</Pagination>
<br />
</div>
);
const { mesg, mesgType, isLoading } = this.state;
return (
<>
{mesg ? (
<div
className={"alert alert-" + mesgType + " text-white mb-3"}
role="alert"
>
{mesg}
</div>
) : (
""
)}
{isLoading ? (
<div className="container-fluid">
<h4
className="panel-body"
style={{ "text-align": "center", margin: "auto" }}
>
Loading
<Spinner animation="border" role="status" />
</h4>
</div>
) : (
<div>
<List
listData={this.props.categories}
listName="category"
_handleDelete={this.onDelete.bind(this)}
/>
{paginationBasic}
</div>
)}
</>
);
}
}
const matchStatestoProps = state => {
return { categories: state.categories };
};
const dispatchStatestoProps = dispatch => {
return {
passToRedux: pload =>
dispatch({ type: "FETCH_CATEGORIES", payload: pload }),
removeCategory: id => dispatch({ type: "DELETE_CATEGORY", payload: id })
};
};
export default connect(matchStatestoProps, dispatchStatestoProps)(Category);
the route is as follows:
<Route exact path="/categories/:page?" component={Category} />
So basically I want the page number to be displayed in the URL. Also if I change the page number, the data should load the corresponding page. Please help me
Could someone please help me out?
In a class component:
Your router will pass match in as a prop. When your component mounts, get this.props.match.params.page and load the data accordingly:
class MyComponent extends React.Component {
componentDidMount () {
// get the 'page' param out of the router props.
// default to 0 if not specified.
const { page = 0 } = this.props.match.params;
// it comes in as a string, parse to int
const p = parseInt(page, 10);
// do whatever you need to do (load data, etc.)
}
}
In a function component:
In a function component, you can get the page param via react-router's useParams hook:
import { useParams } from 'react-router-dom';
function MyComponent () {
const { page } = useParams(); // get the 'page' router param
const p = parseInt(page, 10); // comes in as a string, convert to int
// do whatever you need to do with it
}
If you need prev/next navigation you can deduce those page numbers from the current page.
I made this quick example that demonstrates how to access and use the route's url parameters via react router's useParams hook and how to do it via the match prop with a class component.
You can get page number from props like this:
const matchStatestoProps = (state, ownProps) => {
return { id: ownProps.match.params.id; categories: state.categories };
};
In your routes:
<Route path="/page/:id" component={Page} />

Custom react hook triggers api call multiple times

I cannot figure out how to handle my function components calling my api repeatedly. I have two components which retrieve data, one of them calls the api twice. Once before the second component once after.
I am using a custom react hook and axios get method to retrieve the data. My two components are are nested. The first component when loads and fetches data. Inside this component is a child component which when renders it fetches data right before passing the first set of data as props to another child component. When it completes loading it reloads the first child component which again calls the api for data. I understand the function components reload on state change. I would be happy for it to not call the api a second time. Is there a way to check if it already has data and bypass the api call?
Custom hook to retrieve data
import React, { useState, useEffect, useReducer } from "react";
import axios from "axios";
const dataFetchReducer = (state, action) => {
switch (action.type) {
case "FETCH_INIT":
return { ...state, isLoading: true, hasErrored: false };
case "FETCH_SUCCESS":
return {
...state,
isLoading: false,
hasErrored: false,
errorMessage: "",
data: action.payload
};
case "FETCH_FAILURE":
return {
...state,
isLoading: false,
hasErrored: true,
errorMessage: "Data Retrieve Failure"
};
case "REPLACE_DATA":
// The record passed (state.data) must have the attribute "id"
const newData = state.data.map(rec => {
return rec.id === action.replacerecord.id ? action.replacerecord : rec;
});
return {
...state,
isLoading: false,
hasErrored: false,
errorMessage: "",
data: newData
};
default:
throw new Error();
}
};
const useAxiosFetch = (initialUrl, initialData) => {
const [url] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
hasErrored: false,
errorMessage: "",
data: initialData
});
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
dispatch({ type: "FETCH_INIT" });
try {
let result = await axios.get(url);
if (!didCancel) {
dispatch({ type: "FETCH_SUCCESS", payload: result.data });
}
} catch (err) {
if (!didCancel) {
dispatch({ type: "FETCH_FAILURE" });
}
}
};
fetchData();
return () => {
didCancel = true;
};
}, [url]);
const updateDataRecord = record => {
dispatch({
type: "REPLACE_DATA",
replacerecord: record
});
};
return { ...state, updateDataRecord };
};
export default useAxiosFetch;
Main component which renders the "CompaniesDropdown" twice inside
CompaniesDropdown is one of three dropdowns within the ListFilterContainer component but the only one which calls the api more than once. The other two dropdowns load by selection of the CompaniesDropdown.
import React, { useMemo, useEffect, useContext } from "react";
import InvoiceList from "../src/Components/Lists/InvoiceList";
import useAxiosFetch from "../src/useAxiosFetch";
import { ConfigContext } from "./_app";
import ListFilterContainer from "../src/Components/Filters/InvoiceFilters";
// import "../css/ListView.css";
const Invoices = props => {
const context = useContext(ConfigContext);
useEffect(() => {
document.title = "Captive Billing :: Invoices";
});
const {
data,
isLoading,
hasErrored,
errorMessage,
updateDataRecord
} = useAxiosFetch("https://localhost:44394/Invoice/GetInvoices/false", []);
const newInvoicesList = useMemo(
() => data
// .filter(
// ({ sat, sun }) => (speakingSaturday && sat) || (speakingSunday && sun)
// )
// .sort(function(a, b) {
// if (a.firstName < b.firstName) {
// return -1;
// }
// if (a.firstName > b.firstName) {
// return 1;
// }
// return 0;
// }),
// [speakingSaturday, speakingSunday, data]
);
const invoices = isLoading ? [] : newInvoicesList;
if (hasErrored)
return (
<div>
{errorMessage} "Make sure you have launched "npm run json-server"
</div>
);
if (isLoading) return <div>Loading...</div>;
const dataProps = {
data: invoices,
titlefield: "invoiceNumber",
titleHeader: "Invoice Number:",
childPathRoot: "invoiceDetail",
childIdField: "invoiceId",
childDataCollection: "invoiceData"
};
var divStyle = {
height: context.windowHeight - 100 + "px"
};
return (
<main>
<ListFilterContainer />
<section style={divStyle} id="invoices" className="card-container">
<InvoiceList data={dataProps} />
</section>
</main>
);
};
Invoices.getInitialProps = async ({ req }) => {
const isServer = !!req;
return { isServer };
};
export default Invoices;
Actual result is described above. My main concern is to not have the api calls more than once.
Here is some additional code to help. It is the filter control mentioned above. It, as you will notice really just contains dropdowns and a text box. The first dropdown is the one that calls the api twice. The second two are not visible until that one is selected.
import React, { useState, useMemo } from "react";
import CompaniesDropdown from "../Dropdowns/CompaniesDropdown";
import LocationsDropdown from "../Dropdowns/LocationsDropdown";
import AccountsDropdown from "../Dropdowns/AccountsDropdown";
import Search from "./SearchFilter/SearchFilter";
const InvoiceFilters = props => {
const [company, setCompany] = useState("");
const [location, setLocation] = useState(undefined);
const [account, setAccount] = useState(undefined);
const handleClientChange = clientValue => {
setCompany(clientValue);
};
const handleLocationsChange = locationValue => {
setLocation(locationValue);
};
const handleAccountsChange = AccountValue => {
setAccount(AccountValue);
};
return (
<section className="filter-container mb-3">
<div className="form-row">
<div className="col-auto">
<CompaniesDropdown change={e => handleClientChange(e)} />
</div>
<div className="col-auto">
<LocationsDropdown
selectedCompany={company}
change={e => handleLocationsChange(e)}
/>
</div>
<div className="col-auto">
<AccountsDropdown
selectedCompany={company}
change={e => handleAccountsChange(e)}
/>
</div>
<div className="col-auto">
<Search />
</div>
</div>
</section>
);
};
InvoiceFilters.getInitialProps = async ({ req }) => {
const isServer = !!req;
return { isServer };
};
export default InvoiceFilters;
Also the datalist
import React from "react";
import Link from "next/link";
import InvoiceListRecord from "./InvoiceListRecord";
const InvoiceList = props => {
let dataCollection = props.data.data;
return dataCollection.length == 0 ? "" : dataCollection.map((item, index) => {
return (
<section key={"item-" + index} className="card text-left mb-3">
<header className="card-header">
<span className="pr-1">{props.data.titleHeader}</span>
<Link
href={
"/" +
props.data.childPathRoot +
"?invoiceId=" +
item[props.data.childIdField]
}
as={
"/" +
props.data.childPathRoot +
"/" +
item[props.data.childIdField]
}
>
<a>{item[props.data.titlefield]}</a>
</Link>{" "}
</header>
<div className="card-body">
<div className="row">
<InvoiceListRecord
data={item}
childDataCollection={props.data.childDataCollection}
/>
</div>
</div>
</section>
);
});
};
InvoiceList.getInitialProps = async ({ req }) => {
console.log("Get Intitial Props works: Invoices Page!");
const isServer = !!req;
return { isServer };
};
export default InvoiceList;
and the list items component.
import React from "react";
const InvoiceListRecord = props => {
var invoiceData = JSON.parse(props.data[props.childDataCollection]);
return invoiceData.map((invKey, index) => {
return (
<div className="col-3 mb-1" key={"item-data-" + index}>
<strong>{invKey.MappedFieldName}</strong>
<br />
{invKey.Value}
</div>
);
});
};
export default InvoiceListRecord;
The API is not called more than once if the url is the same. It just gets the value from data variable. The api call is not made again, unless the url changes.
I created an example from your code, changing all the unknown components to div. I added a console.log in the useEffect of the useAxiosFetch hook. And to re-render the component, I added a button to increment the count.
You'll see that the console.log from the hook is printed only once, even though the component re-renders on every button click. The value just comes from the data variable from the hook and the api call is not made again and again.

Resources