prevent render attempt if item not loaded from state yet - reactjs

I am requesting an image from cloudinary and at face value everything seems fine as I can see the image on the frontend as intended. But when looking at chrome dev tools I can see that first there was a 404 error which shows a call to the path where the image is stored but without the image name. For the second call which is successful, there is the path and the image name.
So, It appears that before the image name is not yet loaded from the state at the time the first request is made. I did try the && conditional check but that had the same result ie:
{this.state.bgImg && this.state.bgImg}
Then I tried:
{this.state.bgImg ? this.state.bgImg : "fakeImage.jpg"}
And in dev tools I see it actually tried to get that fakeImage.jpg
How can I prevent this?
class Home extends Component {
state = {
title: "",
bgImg: "",
categories: []
};
async componentDidMount() {
const response = await getHero();
const { data: categories } = await getCategories();
this.setState({
title: response.data.title,
categories,
bgImg: response.data.bgImg
});
}
render() {
return (
<React.Fragment>
<NavBar />
<Hero
title={this.state.title}
bgImg={this.state.bgImg && this.state.bgImg}
/>
</React.Fragment>
);
}
}
export default Home;
const imageUrl = process.env.REACT_APP_CLOUDINARY_URL;
class Hero extends Component {
render() {
const { title, bgImg } = this.props;
return (
<section
className="section-hero d-flex justify-content-center align-items-center mb-5"
style={{
backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), url(${imageUrl}/somepath/${bgImg})`
}}
>
<Container className="text-center text-white">
<h1>{title}</h1>
</Container>
</section>
);
}
}
export default Hero;

For the initial render of your Home component, you pass an empty string to Hero for the bgImg prop. You will get a 404 error because no image was found in this path.
url(${imageUrl}/somepath/${bgImg}) <--- bgImg is an empty string on first render.
To workaround this, you can just do a conditional check so that your Hero component only renders when the bgImg-state in Home is a truthy value, which it will be after the completed fetch in componentDidMount.
So for the first render we will give bgImg a default value of null (that makes sense) because there is no-value. Hero component will not be used yet (so no 404 error). Then after componentDidMount, everything will work as expected.
class Home extends Component {
state = {
title: "",
bgImg: null,
categories: []
};
async componentDidMount() {
const response = await getHero();
const { data: categories } = await getCategories();
this.setState({
title: response.data.title,
categories,
bgImg: response.data.bgImg
});
}
render() {
const { bgImg } = this.state
return (
<React.Fragment>
<NavBar />
{ bgImg && (
<Hero
title={this.state.title}
bgImg={bgImg}
/>
)}
</React.Fragment>
);
}
}
export default Home;

Related

Returning x amount of API movie into unordered list using React, Axios, and MovieDB

I am making a movie search app using React, Axios, and MovieDB API. Currently, it returns all the information I want it to by searching for a movie. However, I ran into an issue where movies that share a title with more popular movies don't show up and there is no way of accessing them. I am trying to add a side section that's titled "show similar" where I can have all the other titles that share the name show up in the unordered list. I do not know how to implement a for or for each function in React js that would be able to get each title outside of the first title ['0'] that it is returning.
You can access a different movie with the same name by changing the number ['0'], ['1'], etc.
Here is a picture for reference
The code is in Movielist and the other two are listed in case you need.
Movielist.js:
import React from 'react';
import axios from 'axios';
import '../CSS/style.css'
export default class Movielist extends React.Component {
state = {
title: "",
popularity: "",
poster: "",
background: "",
results: "",
}
clickHandler = (event) => {
if (event.keyCode === 13) {
const query = event.target.value;
const API_KEY = 'caf02a958f137f43327649b2b8721302';
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
this.setState({ results: res.data.results })
const title = res.data['results'][0]['title'];
this.setState({ title });
const popularity = res.data['results'][0]['popularity']
this.setState({ popularity });
const poster = res.data['results'][0]['poster_path']
this.setState({ poster });
const background = res.data['results'][0]['backdrop_path']
this.setState({ background })
})
}
}
render() {
const backgroundStyle = {
backgroundImage: `linear-gradient(to bottom, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)),
url(https://image.tmdb.org/t/p/w500${this.state.background})`,
backgroundSize: "cover",
height: "100vh"
}
return (
<div id="main-div" style={backgroundStyle}>
<div id="second-div">
<input type="search" id="search" onKeyDown={event => this.clickHandler(event)} />
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<h1 id="title">Title: {this.state.title}</h1>
<h1 id="popularity">Popularity: {this.state.popularity}</h1>
<img id="poster" src={`https://image.tmdb.org/t/p/w300${this.state.poster}`} />
</div>
</div>
)
}
}
App.js
import React from "react"
import Movielist from './components/Movielist'
function App() {
return (
<div>
<Movielist />
</div>
)
}
export default App
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
You can create a new state variable to store the movies that share a similar title.
And set the state value for the similarMovies as the array of results you get from the API call excluding the first index. For example,
state = {
title: "",
popularity: "",
poster: "",
background: "",
results: "",
similarMovies: [] // create an empty array for similarMovies
}
// in the click handler update the state
clickHandler = (event) => {
if (event.keyCode === 13) {
const query = event.target.value;
const API_KEY = 'caf02a958f137f43327649b2b8721302';
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`)
.then(res => {
this.setState({ results: res.data.results });
// here we set the state value
const similarMovies = res.data.results.slice(1);
this.setState({ similarMovies })
const title = res.data['results'][0]['title'];
this.setState({ title });
const popularity = res.data['results'][0]['popularity']
this.setState({ popularity });
const poster = res.data['results'][0]['poster_path']
this.setState({ poster });
const background = res.data['results'][0]['backdrop_path']
this.setState({ background })
})
}
}
Then you can render the movies in an unordered list by mapping over the value of this.state.similarMovies.
return (
<div id="main-div" style={backgroundStyle}>
<div id="second-div">
<input type="search" id="search" onKeyDown={event => this.clickHandler(event)} />
<ul>
{
this.state.similarMovies.length && this.state.similarMovies.map(movie => (
<li>
<h3>{movie.title}</h3>
/*Other details you want to show*/
<li>
))
}
</ul>
<h1 id="title">Title: {this.state.title}</h1>
<h1 id="popularity">Popularity: {this.state.popularity}</h1>
<img id="poster" src={`https://image.tmdb.org/t/p/w300${this.state.poster}`} />
</div>
</div>
)

React Component Doesn't Update After First Button Click

My code generates an input field that allows a user to enter a value to search for. Then when they click the Submit button, it causes displayMap to be true, so that when the MapDisplay component renders, it will trigger an API search via the Map component and return values that are then displayed on the map.
The problem is that this process only works once. When I click the button again, it does do something, I confirmed that it is getting the new value in the input box, but I can't seem to figure out how to get the map to be rendered again.
I've tried setting other variables in the this.setState to try to get it to know that it needs to render the component again, but I guess I'm missing something, because nothing works.
I'm fairly new to React, so any help you can offer would be greatly appreciated.
This is the MainSearchBar.js, where most of the work as described above is happening:
import Map from './newMap.js';
function MapDisplay(props) {
if (props.displayMap) {
return <Map toSearch = {props.searchTerm}></Map>;
} else {
return "";
}
}
class MainSearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
displayMap: false,
value: '',
searchTerm: '',
isOpened: false
};
//this.handleClick = this.handleClick.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleClick = () => {
this.setState({
displayMap: true,
isOpened: !this.state.isOpened,
searchTerm: this.state.value
});
console.log(this.state.value);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
<MapDisplay displayMap={displayMap} searchTerm={this.state.value} />
</div>
)
}
}
export default MainSearchBar;
This is where MainSearchBar is being called from
import Top20Box from '../components/getTop20Comp2.js';
import Header from '../components/Header.js';
import MainIntro from '../components/MainIntro.js';
import MainSearchBar from '../components/MainSearchBar.js';
import MainCTA from '../components/MainCTA.js';
import Footer from '../components/Footer.js';
export default class Home extends Component {
state = {
}
render () {
return (
<React.Fragment>
<Header>
</Header>
<MainIntro />
<MainSearchBar />
<div className="top20-text">
Top 20 trending hashtags
</div>
<Top20Box />
<MainCTA />
<Footer />
</React.Fragment>
)
}
}
And this is the Map component itself, in case you need it:
import React from 'react';
import ReactMapGL, {Marker, Popup} from 'react-map-gl';
import axios from 'axios';
//for the loading animation function
import FadeIn from "react-fade-in";
import Lottie from "react-lottie";
import * as loadingData from "../assets/loading.json";
var locationCoordinates = [];
var locationToSearch = "";
var returnedKeywordSearch = [];
var newArray = [];
const defaultOptions = {
loop: true,
autoplay: true,
animationData: loadingData.default,
rendererSettings: {
preserveAspectRatio: "xMidYMid slice"
}
};
export default class Map extends React.Component {
//sets components for the map, how big the box is and where the map is centered when it opens
state = {
viewport: {
width: "75vw",
height: "50vh",
latitude: 40.4168,
longitude: 3.7038,
zoom: .5
},
tweetSpots: null, //data from the API
selectedSpot: null,
done: undefined, //for loading function
};
async componentDidMount() {
//searches the api for the hashtag that the user entered
await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
returnedKeywordSearch = response.data;
}) //if the api call returns an error, ignore it
.catch(function(err) {
return null;
});
//goes through the list of locations sent from the api above and finds the latitude/longitude for each
var count = 0;
while (count < returnedKeywordSearch.length) {
locationToSearch = returnedKeywordSearch[count].location;
if (locationToSearch !== undefined) {
var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
.catch(function(err) {
return null;
});
if (locationList !== null) {
if (Array.isArray(locationList.data.features) && locationList.data.features.length)
{
locationCoordinates.push(locationList.data.features[0].center);
if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
&& locationList.data.features[0].center !== undefined)
{newArray.push({
id: returnedKeywordSearch[count].id,
createdAt: returnedKeywordSearch[count].createdAt,
text: returnedKeywordSearch[count].text,
name: returnedKeywordSearch[count].name,
location: returnedKeywordSearch[count].location,
coordinates: locationList.data.features[0].center
});
}
}
}
}
count++;
}
this.setState({tweetSpots: newArray});
this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
}
//is triggered when a marker on the map is hovered over
setSelectedSpot = object => {
this.setState({
selectedSpot: object
});
};
//creates markers that display on the map, using location latitude and longitude
loadMarkers = () => {
return this.state.tweetSpots.map((item,index) => {
return (
<Marker
key={index}
latitude={item.coordinates[1]}
longitude={item.coordinates[0]}
>
<img class="mapMarker"
onMouseOver={() => {
this.setSelectedSpot(item);
}}
src="/images/yellow7_dot.png" alt="" />
</Marker>
);
});
};
//closes popup when close is clicked
closePopup = () => {
this.setState({
selectedSpot: null
});
};
//renders map component and loading animation
render() {
return (
<div className="App">
<div className="map">
{!this.state.done ? (
<FadeIn>
<div class="d-flex justify-content-center align-items-center">
<Lottie options={defaultOptions} width={400} />
</div>
</FadeIn>
) : (
<ReactMapGL {...this.state.viewport} mapStyle="mapbox://styles/mapbox/outdoors-v11"
onViewportChange={(viewport => this.setState({viewport}))}
mapboxApiAccessToken="pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg">
{this.loadMarkers()}
{this.state.selectedSpot !== null ? (
<Popup
key={this.state.selectedSpot.id}
tipSize={5}
latitude={this.state.selectedSpot.coordinates[1]}
longitude={this.state.selectedSpot.coordinates[0]}
closeButton={true}
closeOnClick={false}
onClose={this.closePopup}
>
<div className="mapPopup">
<div className="header"> Tweet </div>
<div className="content">
{" "}
<p>
<b>Name:</b> {this.state.selectedSpot.name}
</p>
<p>
<b>Tweet:</b> {this.state.selectedSpot.text}</p>
<p>View Tweet in Twitter
</p>
</div>
</div>
</Popup>
) : null}
</ReactMapGL>
)}
</div>
</div>
);
}
}
Update: 4/28, per the answer I received, I update the render of the MainSearchBar.js to look like this:
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
{this.state.displayMap && <Map toSearch = {this.searchTerm}></Map>}
</div>
)
}
When you click the button again, the state of MainSearchBar.js updates but the functional component MapDisplay does not and thus the Map does not update as well.
There are many ways to resolve this. Looking at the code, it looks like MapDisplay doesn't do much so you can consider replacing it with conditional rendering.
MainSearchBar.js
render() {
const displayMap = this.state.displayMap;
return (
<div class="homepage-search-bar">
<input
type="text" name="search" value={this.state.value} onChange={this.handleChange} className="main-search-bar" placeholder="Search hashtags">
</input>
<button onClick={this.handleClick}>Search</button>
{this.state.displayMap && <Map toSearch = {props.searchTerm}></Map>}
</div>
)
}
Then in your Map component, add a componentDidUpdate lifecycle method to detect updates to the prop which does the same thing as componentDidMount when the props are updated.
async componentDidMount(prevProps) {
if (props.toSearch != prevProps.toSearch) {
await axios.get(`https://laffy.herokuapp.com/search/${this.props.toSearch}`).then(function(response) {
returnedKeywordSearch = response.data;
}) //if the api call returns an error, ignore it
.catch(function(err) {
return null;
});
//goes through the list of locations sent from the api above and finds the latitude/longitude for each
var count = 0;
while (count < returnedKeywordSearch.length) {
locationToSearch = returnedKeywordSearch[count].location;
if (locationToSearch !== undefined) {
var locationList = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${locationToSearch}.json?access_token=pk.eyJ1IjoibGF1bmRyeXNuYWlsIiwiYSI6ImNrODlhem95aDAzNGkzZmw5Z2lhcjIxY2UifQ.Aw4J8uxMSY2h4K9qVJp4lg`)
.catch(function(err) {
return null;
});
if (locationList !== null) {
if (Array.isArray(locationList.data.features) && locationList.data.features.length)
{
locationCoordinates.push(locationList.data.features[0].center);
if (returnedKeywordSearch[count].location!== null && returnedKeywordSearch[count].location!==""
&& locationList.data.features[0].center !== undefined)
{newArray.push({
id: returnedKeywordSearch[count].id,
createdAt: returnedKeywordSearch[count].createdAt,
text: returnedKeywordSearch[count].text,
name: returnedKeywordSearch[count].name,
location: returnedKeywordSearch[count].location,
coordinates: locationList.data.features[0].center
});
}
}
}
}
count++;
}
this.setState({tweetSpots: newArray});
this.setState({ done: true}); //sets done to true so that loading animation goes away and map displays
}
}
#wxker Thanks for all your help! You certainly got me pointed in the right direction.
I changed render in MainSearchBar.js back to what it was originally.
And I added a ComponentDidUpdate to the Map component, as follows below:
async componentDidUpdate(prevProps) {
//searches the api for the hashtag that the user entered
if (this.props.toSearch !== prevProps.toSearch) {
and then the rest was the same as the original componentDidMount.

How to update state before rendering in Reactjs with mobx

This is my mobx store code.
First, 'projectGet()' must be executed to push the data from firestore.
#observable projectState = {
projects: []
};
projectGet = () => {
firebase
.firestore()
.collection("projects")
.get()
.then(snapshot => {
snapshot.forEach(doc => {
this.projectState.projects.push(doc.data());
});
})
.catch(err => {
console.log("Error getting documents", err);
});
};
After push the data into projectState, it should be read at the other .js file.
I ran the function inside of render.
But when I enter the homepage, it doesn't update state at first.
So, when I refresh the homepage, it updates the state.
However, I need to update the state at the first home page access.
I tried to use 'componentWilupdate', 'ComponentDidmount' etc.
It doesn't work at all.
Could you give me some recommendation for this problem?
render() {
const { Project } = this.props;
Project.projectGet();
return (
<div className="col s12 m6">
<ProjectList projects={Project.projectState.projects} />
</div>
);
}
I attached more code below.
import React from "react";
import ProjectSummary from "./ProjectSummary";
import { Link } from "react-router-dom";
const ProjectList = ({ projects }) => {
return (
<div className="project-list section">
{projects &&
projects.map(project => {
return (
<Link to={"/project/" + project.id} key={project.id}>
<ProjectSummary project={project} />
</Link>
);
})}
</div>
);
};
export default ProjectList;
You can use componentDidMount lifecycle method to make API calls before rendering, For example
componentDidMount() {
const { Project } = this.props;
Project.projectGet();
}
then in render
render() {
const { Project } = this.props;
return (
<div className="col s12 m6">
<ProjectList projects={Project.projectState.projects} />
</div>
);
}
Use componendWillMount to make API call before the component renders, if it is not updated then check whether the expected props are available with componentWillReceiveProps lifecycle method.
componentWillReceiveProps({Project}) {
Project.projectGet();
}
Once the props are changed you will get the change in the render

My search input and pagination aren't triggering anything in Reactjs

I'm fairly new to react.
My search input and pagination buttons aren't triggering anything and nothing comes up in the console, what is wrong with my code ?
I tried putting every functions in App.js to get it cleaner.
App.js
import React, { Component } from "react";
import List from './List';
let API = 'https://swapi.co/api/people/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
search: '',
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
this.updateSearch = this.updateSearch.bind(this);
}
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(API);
const json = await response.json();
this.setState({ results: json.results });
};
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
updateSearch(event) {
this.setState({
search: event.target.value.substr(0, 20)
});
}
render() {
return (
<div>
<List data={this.state} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { data } = this.props;
const { results, search, updateSearch, handleClick, currentPage, todosPerPage } = data;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
return item.name.toLowerCase().indexOf(search) !== -1;
});
const renderTodos = currentTodos.map((item, number) => {
return (
<Person item={item} key={number} />
);
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li className="page-link" key={number} id={number} onClick={handleClick} style={{cursor: "pointer"}}>{number}</li>
);
});
return (
<div className="flex-grow-1">
<h1>Personnages de Star Wars</h1>
<form className="mb-4">
<div className="form-group">
<label>Rechercher</label>
<input
className="form-control"
type="text"
placeholder="luke skywalker..."
value={search}
onChange={updateSearch}
/>
</div>
</form>
<div className="row mb-5">{renderTodos}</div>
<nav aria-label="Navigation">
<ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
</nav>
</div>
);
}
}
export default List;
The value of the input doesn't change one bit if I type in it and if I right click on a page number, the console gets me Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Element': '#4' is not a valid selector.
Any idea ?
The issue is that in the List class you attempt take updateSearch and handleClick out of data (which in turn comes from this.props). But updateSearch and handleClick are never placed inside data. If you log either of these methods to the console you'll see they are undefined.
To fix this, you need to pass updateSearch and handleClick from App to List. You can do this either by including the methods inside the data prop, or by passing them directly as their own props (which I would recommend).
For example, you can change the render method of App to look something like this:
render() {
return (
<div>
<List
data={this.state}
updateSearch={ this.updateSearch }
handleClick={ this.handleClick }
/>
</div>
);
}
Then in the render method of List you can do this:
const { data, updateSearch, handleClick } = this.props;
and remove the definitions of the two methods from the destructuring of data below.

How can I update the this.state.songs to songsList

I cant update the state songs which needs to get values from songsList . How can I update the songs to songsList ? Is it anything to do with the component life cycle ? While running the below code , 'songsList is undefined' error throws up . const songList is in the render .
import React, { Component } from 'react';
import logo from './components/Logo/box8.png';
import './App.css';
import SearchBox from './components/SearchBox/SearchBox';
import SongCards from './components/SongCards/SongCards';
import 'tachyons';
import axios from 'axios';
class App extends Component {
state = {
songs : [],
searchField: '',
entries: []
};
componentDidMount() {
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then(response =>
{this.setState({ entries: response.data.feed.entry });
});
}
onSearchChange=(event)=>{
this.setState({songs : songsList})
this.setState({searchField : event.target.value})
const filteredSongs = this.state.songs.filter(song =>{
return song.title.toLowerCase().includes(this.state.searchField.toLowerCase())
});
}
render(){
const songsList = this.state.entries.map(entries => {
return (
<SongCards
key={entries.id.label}
artist={entries["im:artist"].label}
image={entries["im:image"][2].label}
link={entries.id.label}
price={entries["im:price"].label}
date={entries["im:releaseDate"].label}
title={entries.title.label}
/>
);
});
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange= {this.onSearchChange}/>
{songsList}
</div>
);
}
}
export default App;
Appreciate all your responses . I made it finally .
import React, { Component } from 'react';
import logo from './components/Logo/box8.png';
import './App.css';
import SearchBox from './components/SearchBox/SearchBox';
import Albums from './components/Albums/Albums';
import Scroll from './components/Scroll/Scroll';
import 'tachyons';
import emoji from 'emoji-dictionary';
import axios from 'axios';
class App extends Component {
state = {
show:false,
songs : [],
searchField: '',
};
componentDidMount() {
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then(response =>
{this.setState({songs:response.data.feed.entry });
});
}
itunesPageLoader=()=>{
this.setState({show:false})
}
onSearchChange=(event)=>{
this.setState({searchField : event.target.value})
}
render(){
const filteredSongs = this.state.songs.filter(song =>{
return
song.title.label.toLowerCase().includes(this.state.searchField.toLowerCase())
})
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange= {this.onSearchChange}/>
<Scroll >
<Albums songs={filteredSongs}/>
</Scroll>
<footer className="pv4 ph3 ph5-m ph6-l red">
<small className="f6 db tc">© 2018 <b className="ttu">Box8 Inc</b>., All
Rights Reserved</small>
<div className="tc mt3">
{`Made with ${emoji.getUnicode("purple_heart")} by Renjith`}
</div>
</footer>
</div>
);
}
}
export default App;
Try this. You are actually assigning songsList to songs using setState but the songsList doesn’t exist in onSearchChange. To push searched value to an array you need to push event.target.value to songs array
Try with below corrected code
onSearchChange=(event)=>{
this.setState(prevState => ({songs : [...prevState.songs, event.target.value]}));
this.setState({searchField : event.target.value})
const filteredSongs = this.state.songs.filter(song =>{
return song.title.toLowerCase().includes(this.state.searchField.toLowerCase())
});
}
You have mentioned that this.state.entries is an Object.
If this is true, then yo can't perform .map on it as .map is an Array method.
You can however use Object.entries to get an array of [key,value] pairs of this.state.entries.
Object.entries(this.state.entries).map(([key,value]) => ...)
Simple running example:
const object1 = { foo: 'this is foo', baz: "this is baz" };
Object.entries(object1).map(([key,value]) => console.log(`key: ${key}, value: ${value}`));
So i will do something like this:
const IN_PROGRESS = 'IN_PROGRESS';
const SUCCESS = 'SUCCESS';
class App extends Component {
state = {
songs : null,
entries: null,
status: null
};
componentDidMount() {
this.setState({status: IN_PROGRESS});
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then({data} => {
const songs = data.feed.entry;
this.setState({entries: songs});
this.setState({songs});
this.setState({status: SUCCESS});
});
}
onSearchChange = ({target}) => {
const {value} = target;
const songs = this.state.entires.filter(song =>
song.title.toLowerCase().includes(value.toLowerCase())
});
this.setState({songs});
}
render() {
const {status, songs} = this.state;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange={this.onSearchChange}/>
{
status === IN_PROGRESS &&
(/* you can insert here some kind of loader which indicates that data is loading*/)
}
{
status === SUCCESS && songs.map(entry => {
const {
id, ['im:artist']: artist, ['im:image']: image,
['im:price']: price, ['im:releaseDate']: date, title
} = entry;
return (
<SongCard
key={id.label}
artist={artist.label}
image={image[2].label}
link={id.label}
price={price.label}
date={date.label}
title={entry.title.label}
/>
)
}
}
{
//Here you can display error message if status === FAILURE
}
</div>
);
}
}
When component did mount, I set status into IN_PROGRESS (if you want some kind of loader to show), and data are beeing fetched - axios.get is asynchronous so remember that when data is fetching then render method is already triggered. When data is loaded then in state I hold two variables, entries which holds unfiltered list of songs, and songs which holds filteres songs.
When search is triggered then I filter entires by searched phrase and set into state this filtered array.
Component renders songCards mapping by filtered songs

Resources