so I'm working on a pokedex project that is linked to the PokeAPI. I have the main page that loads up the list ans the code for PokemonList.js is as follows:
import React, { Component } from "react";
import PokemonCard from "./PokemonCard";
import axios from "axios";
import InfiniteScroll from "react-infinite-scroller";
export default class PokemonList extends Component {
state = {
url: "https://pokeapi.co/api/v2/pokemon?limit=20&offset=0.",
pokemon: null,
itemsCountPerPage: 20,
activePage: 1,
count: 365,
previous: null
};
loadPokemon = () => {
axios
.get(this.state.url)
.then(res => {
this.setState(prevState => {
return {
pokemon: [...prevState.pokemon, ...res.data.results],
url: res.data.next
};
});
})
.catch(function(error) {
// handle error
console.log(error);
});
};
async componentDidMount() {
const res = await axios.get(this.state.url);
this.setState({ pokemon: res.data["results"] });
}
render() {
console.log(this.state.pokemon);
return (
<React.Fragment>
{this.state.pokemon ? (
<div className="row">
<InfiniteScroll
pageStart={1}
loadMore={this.loadPokemon}
hasMore={true}
loader={
<div className="loader" key={0}>
Loading ...
</div>
}
>
{this.state.pokemon.map((pokemon, i) => (
<PokemonCard
key={pokemon.name + i}
name={pokemon.name}
url={pokemon.url}
/>
))}
</InfiniteScroll>
</div>
) : (
<h1>Loading Pokemon</h1>
)}
</React.Fragment>
/*<React.Fragment>
{this.state.pokemon ? (
<div className="row">
{this.state.pokemon.map(pokemon => (
<PokemonCard
key={pokemon.name}
name={pokemon.name}
url={pokemon.url}
/>
))}
</div>
) : (
<h1>Loading Pokemon</h1>
)}
</React.Fragment>*/
);
}
}
For some reason, the first 20 PokemonCards are loaded in from the API, but once I reach the 20th one, the infinite scroller loads up the first 20 again before eventually loading the next 20-40, then 40-60. It is only the first set amount that is duplicated.
This is a screenshot of the console being returned
As you can see, Bulbasaur is repeated after the 20th data in the list.
You should not called the loadPokemon function inside your componentDidMount(). Remove it and it should work as expected!
import React, { Component } from "react";
import PokemonCard from "./PokemonCard";
import axios from "axios";
import InfiniteScroll from "react-infinite-scroller";
export default class PokemonList extends Component {
state = {
url: "https://pokeapi.co/api/v2/pokemon?limit=20&offset=0.",
pokemon: [],
itemsCountPerPage: 20,
activePage: 1,
count: 365,
previous: null
};
loadPokemon = () => {
axios
.get(this.state.url)
.then(res => {
this.setState(prevState => {
return {
pokemon: [...prevState.pokemon, ...res.data.results],
url: res.data.next
};
});
})
.catch(function(error) {
// handle error
console.log(error);
});
};
render() {
console.log(this.state.pokemon);
return (
<React.Fragment>
{this.state.pokemon ? (
<div className="row">
<InfiniteScroll
pageStart={1}
loadMore={this.loadPokemon}
hasMore={true}
loader={
<div className="loader" key={0}>
Loading ...
</div>
}
>
{this.state.pokemon.map((pokemon, i) => (
<PokemonCard
key={pokemon.name + i}
name={pokemon.name}
url={pokemon.url}
/>
))}
</InfiniteScroll>
</div>
) : (
<h1>Loading Pokemon</h1>
)}
</React.Fragment>
);
}
}
I ended up changing my state from
pokemon: null,
to
pokemon: [],
And I added
const pokemon = prevState.pokemon;
to my loadPokemon Function
In your componentDidMount function, you need to set your this.state.url, just like you do each time loadPokemon is called. You are calling the base url twice for the first page currently, once in componentDidMount and again in loadPokemon.
Related
I am trying to write a wrapper component around an API call to render "loading" if the api hasnt updated. Im very new to react, but I can t seem to figure out why the state isnt being passed to the ApiResp component:
Here is the console.log of the state changes..... why is the final apiResp in console.log undefined?
App.js
class App extends React.Component {
async componentDidMount() {
let xx = await axios.get(apiUrl)
console.log(`componentDidMount`, xx)
this.setState({ loading: false, body: xx });
}
render() {
console.log(`rendering ComponentLoading`, this.state)
const DisplayComponent = ComponentLoading(ApiResp)
return (
<div className="App">
<header className="App-header">
<img src={face} /*className="App-logo"*/ alt="face-img" />
</header>
<br></br>
<div>
<DisplayComponent isLoading={AppState.loading} body={AppState.body} />
</div>
</div>
);
}
}
export default App;
ComponentLoading:
import React from 'react';
function ComponentLoading(Component) {
return function WihLoadingComponent({ isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return (
<p style={{ textAlign: 'center', fontSize: '30px' }}>
Loading
</p>
);
};
}
export default ComponentLoading;
apiResp.js
import React from 'react';
const ApiResp = (data) => {
console.log(`apiResp:`, data)
if (!data || data.statusCode !== 200) {
return <p>Err: {JSON.stringify(data)}</p>;
}
else
return (
<div>
<h3>obj:</h3>
{JSON.stringify(data)}
</div>
);
};
export default ApiResp;
ComponentLoading is a Higher Order Component. const DisplayComponent = ComponentLoading(ApiResp) is decorating the ApiResp component:
const ApiResp = (data) => {
console.log(`apiResp:`, data)
if (!data || data.statusCode !== 200) {
return <p>Err: {JSON.stringify(data)}</p>;
}
else
return (
<div>
<h3>obj:</h3>
{JSON.stringify(data)}
</div>
);
};
and returning a component you've called DisplayComponent.
As a component ApiResp is consuming a props object called data and only accesses a statusCode prop.
DisplayComponent is passed two props:
<DisplayComponent isLoading={AppState.loading} body={AppState.body} />
AppState isn't defined in the parent component but it seems this.state has the values you want passed to DisplayComponent.
Solution
Access and pass the correct object to props.
<DisplayComponent
isLoading={this.state.loading}
body={this.state.body}
/>
I suggest also moving the const DisplayComponent = ComponentLoading(ApiResp) declaration out of the render method, and also outside the App component.
const DisplayComponent = ComponentLoading(ApiResp);
class App extends React.Component {
state = {
loading: true,
body: null,
};
async componentDidMount() {
let xx = await axios.get(apiUrl)
console.log(`componentDidMount`, xx)
this.setState({ loading: false, body: xx });
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={face} /*className="App-logo"*/ alt="face-img" />
</header>
<br></br>
<div>
<DisplayComponent
isLoading={this.state.loading}
body={this.state.body}
/>
</div>
</div>
);
}
}
So I have stated learning react and tried to make a project that renders data from an api. I have 2 components, a Search bar and a component that renders the weather.
What I'm trying to do is to get the value from the search bar and concatenate into the api string. I have tried doing this by settings a prop but I am unable accessing it in the weather component.
My questions is: How can I access the search value in a different component
/components/Search.js
class Search extends Component {
state = {
title: '',
};
onChange = (e) => {
this.setState({ title: e.target.value });
};
onSubmit = (e) => {
// e.preventDefault();
this.props.searchValue(this.state.title);
this.setState({ title: '' });
};
render() {
return (
<Mui.Container>
<CssBaseline />
<form
onSubmit={this.onSubmit}
autoComplete='off'
>
<Mui.Input
placeholder='enter place'
value={this.state.title}
onChange={this.onChange}
/>
</form>
</Mui.Container>
);
}
}
Search.propTypes = {
searchValue: PropTypes.func,
};
/components/Weather.js
class Weather extends Component {
state = {
videos: [],
};
componentDidMount = () => {
axios
.get(
'<weather api here>'
)
.then((res) => {
const videosArr = res.data.videos.map((item) => {
return item;
});
this.setState({ videos: videosArr });
});
};
render() {
return (
{this.state.videos.map((video, index) => {
return (
<React.Fragment key={video.id}>
<Mui.Grid item>
<Mui.Paper>
<div>
<img src='./190x107.png' alt='placeholder' />
<div>
<a href={video.url}>{video.title}</a>
</div>
</div>
</Mui.Paper>
</Mui.Grid>
</React.Fragment>
);
})}
);
}
}
I assume there will be a parent component for <Weather /> and <Search />?
If yes then the parent component can have state, and you pass your setState function into the search component, and then you pass the current state into the weather component.
<Weather searchValue="current state from parent" />
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
videos: []
};
}
componentDidMount = () => {
axios
.get(`URL?${this.props.searchValue}`)
.then((res) => {
const videosArr = res.data.videos.map((item) => {
return item;
});
this.setState({ videos: videosArr });
});
};
render() {
return (
{this.state.videos.map((video, index) => {
return (
<React.Fragment key={video.id}>
<Mui.Grid item>
<Mui.Paper>
<div>
<img src='./190x107.png' alt='placeholder' />
<div>
<a href={video.url}>{video.title}</a>
</div>
</div>
</Mui.Paper>
</Mui.Grid>
</React.Fragment>
);
})}
);
}
}
I'm using axios to return the details of a Pokemon:
class PokeDetails extends React.Component{
constructor(props){
super(props);
this.state = { pokemon: null }
}
componentDidMount(){
axios.get('https://pokeapi.co/api/v2/pokemon/1')
.then(res => {
this.setState({
pokemon: res.data
});
}).catch((err) => { console.log('Axios Poke Details error: ', err) });
}
render(){
const {pokemon} = this.state;
const pokeCard = pokemon ? (
<div className="poke-details">
<img src={pokemon.sprites.front_default} alt={`${pokemon.name} front`}/>
<h3 className="card-title">{pokemon.name}</h3>
</div>
) : (
<div className="center">Loading Pokemon...</div>)
return(
<div className="container">
{pokeCard}
</div>
)
}
}
export default PokeDetails
I want to display the pokemon types, which is an array that has a length of 1 or 2 depending on the pokemon. So I thought:
render(){
const {pokemon} = this.state
const listTypes = pokemon.types.map((type) => { <li>{type.name}</li> });
}
...and render listTypes in a ul in const pokeCard. When I do this I get an error saying pokemon is null. Shouldn't this not happen because of the ternary operator rendering the pokeCard?
Using pokemon before its detail get fetched from service, try like given:
const listTypes = pokemon && pokemon.types.map((type) => { <li>{type.name}</li> });
Problem is that componentDidMount is called after the initial render. So on the initial render, pokemon is null. That is why accessing pokemon.types fails.
You need to access pokemon.types only when pokemon is not null. Just move the code where you .map() over the pokemon.types inside the ternary operator.
const pokeCard = pokemon ? (
<div className="poke-details">
<img src={pokemon.sprites.front_default} alt={`${pokemon.name} front`} />
<h3 className="card-title">{pokemon.name}</h3>
<ul>
{pokemon.types.map(({ type }) => (
<li key={type.name}>{type.name}</li>
))}
</ul>
</div>
) : (
<div className="center">Loading Pokemon...</div>
);
Demo:
class PokeDetails extends React.Component {
constructor(props) {
super(props);
this.state = { pokemon: null };
}
componentDidMount() {
axios
.get("https://pokeapi.co/api/v2/pokemon/1")
.then((res) => {
this.setState({
pokemon: res.data
});
})
.catch((err) => {
console.log("Axios Poke Details error: ", err);
});
}
render() {
const { pokemon } = this.state;
const pokeCard = pokemon ? (
<div className="poke-details">
<img
src={pokemon.sprites.front_default}
alt={`${pokemon.name} front`}
/>
<h3 className="card-title">{pokemon.name}</h3>
<ul>
{pokemon.types.map(({ type }) => (
<li key={type.name}>{type.name}</li>
))}
</ul>
</div>
) : (
<div className="center">Loading Pokemon...</div>
);
return <div className="container">{pokeCard}</div>;
}
}
ReactDOM.render(<PokeDetails />,document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.20.0/axios.min.js"></script>
<div id="root"></div>
Rome data is better kept in cache, in case it gets shared between componenets in different levels.
I reproduced your code:
import React from 'react';
import useSWR from 'swr';
const fetcher = async (...args) => {
const res = await fetch(...args);
return res.json();
};
function PokeDetails() {
const { data, error } = useSWR('https://pokeapi.co/api/v2/pokemon/1', fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return (
<div>
{data ? (
data.map((pokemon) => {
return (
<div className='poke-details'>
<img
src={pokemon.sprites.front_default}
alt={`${pokemon.name} front`}
/>
<h3 className='card-title'>{pokemon.name}</h3>
</div>
);
})
) : (
<div>Some loading content</div>
)}
</div>
);
}
export default PokeDetails;
you can check this article to get a clear idea.
Upon loading the page, data is retrieved from my API and stored in an array. It then displays a button for each object in the array and titles is appropriately.
What needs to happen next is when I click a button, that button disappears, and the associated item removed from the array. That part is not working. The button is registering the click, and the function is running. However the button does not disappear, and when I log the array again, the array appears to be unchanged. I cannot figure out why. Can anyone help me spot the issue?
Here is my code, the part in question is the "handleAddPolicy" function:
import React, { Component, Fragment } from 'react';
import PolicyButton from './PolicyButton/PolicyButton';
class Handbook extends Component {
constructor(props){
super(props);
this.state = {
clients: [],
policies: [],
client: 'Choose Client',
logo: '',
color1: '#000',
color2: '#fff'
};
}
componentDidMount() {
fetch('/api/themes/all')
.then(res => res.json())
.then((result) => {
this.setState({ clients: result });
console.log(result);
})
.then(
fetch(`/api/policy/all`)
.then(res => res.json())
.then((result) => {
this.setState({ policies: result });
console.log(result);
})
);
}
handleAddPolicy = policyId => {
console.log(`button clicked`);
const policies = this.state.policies.filter(policy => policy.id !== policyId);
this.setState({ policies: policies});
console.log(this.state.policies);
}
render(){
return(
<Fragment>
{/* <ClientPicker /> */}
<div className='buttons'>
{this.state.policies.map(policy => (
<PolicyButton key={policy.id} policy={policy.policy} onAddPolicy={this.handleAddPolicy} />
))}
</div>
</Fragment>
);
}
}
export default Handbook;
And here is code for my button that should disappear after being clicked if it helps:
import React, { Component } from 'react';
import styled from 'styled-components';
class PolicyButton extends Component {
state = {
id: this.props.id,
policy: this.props.policy
}
render(){
return(
<button onClick={() => this.props.onAddPolicy(this.props.id)}>{this.props.policy}</button>
)
}
}
export default PolicyButton;
You missed the id prop when rendering PolicyButton
<Fragment>
{/* <ClientPicker /> */}
<div className='buttons'>
{this.state.policies.map(policy => (
<PolicyButton
key={policy.id}
/* This is what you missed */
id={policy.id}
policy={policy.policy}
onAddPolicy={this.handleAddPolicy}
/>
))}
</div>
</Fragment>
I have been trying to learn React over the past couple of weeks and started working on a site which displays art works.
I would like for the user to be able to click on one of the images displayed and for a new component to be loaded with information about the work.
I have the implementation below of the gallery view, but when I click on an image the URL changes, but the WorkPage component never loads.
Would anyone be able to spot what I am doing wrong? The links and images are generated in the renderItems() function.
import React, { Component } from "react";
import Masonry from 'react-masonry-css';
import WorkPage from "./WorkPage"
import axios from "axios";
import { Link, Route, Switch, useRouteMatch, useParams } from "react-router-dom";
import { BrowserRouter as Router } from "react-router-dom";
class Works extends Component {
constructor(props) {
super(props);
this.state = {
viewPaintings: true,
workList: []
};
axios
.get("http://localhost:8000/api/works/")
.then(res => this.setState({ workList: res.data }))
.catch(err => console.log(err))
};
displayPaintings = status => {
if (status) {
return this.setState({ viewPaintings: true })
}
return this.setState({ viewPaintings: false })
};
renderTabList = () => {
return (
<div>
<ul className="tab-list-buttons">
<li onClick={() => this.displayPaintings(true)}
className={this.state.viewPaintings ? "active" : "disabled"}
key="button1"
>
Paintings
</li>
<li onClick={() => this.displayPaintings(false)}
className={this.state.viewPaintings ? "disabled" : "active"}
key="button2"
>
Works on Paper
</li>
</ul>
</div>
);
};
renderItems = () => {
const { viewPaintings } = this.state;
const newItems = viewPaintings
? this.state.workList.filter(item => item.type === 1)
: this.state.workList.filter(item => item.type === 0);
const breakpointColumnsObj = {
default: 4,
1100: 3,
700: 2,
500: 1
};
const items = newItems.map(item => (
<div key = {item.slug}>
<Link to={`${item.slug}`}>
<img src={item.image} alt={item.name} width="300"/>
</Link>
<Switch>
<Route path=":item.slug" component={WorkPage} />
</Switch>
</div>
));
return (
<Masonry
breakpointCols={breakpointColumnsObj}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{items}
</Masonry>
);
}
render() {
return (
<Router>
<div>
{ this.renderTabList() }
{ this.renderItems() }
</div>
</Router>
)
};
}
export default Works;