I am trying to do a Pagination in React but I am getting an error that I don't really understand.
I am following these steps https://codepen.io/PiotrBerebecki/pen/pEYPbY
class Reviews extends Component {
state = {
currentPage: 1,
reviewsPerPage: 2,
reviews: []
}
componentDidMount() {
this.getReviews();
}
getReviews() {
fetch(`https://apiwe.herokuapp.com/reviews`)
.then(response => response.json())
.then(result => this.setState({ reviews: result }))
.then(result => console.log(this.state.reviews))
.catch(err => console.log(err));
}
handlePageChange = number => {
this.setState({ currentPage: number });
};
render() {
const { currentPage, reviewsPerPage, reviews} = this.state
console.log(this.state)
const indexLastReview = currentPage * reviewsPerPage;
const indexFirstReview = indexLastReview - reviewsPerPage;
// HERE IS THE ERROR
const currentReviews = reviews.slice(indexFirstReview, indexLastReview);
const renderReviews = currentReviews.map((review, index) => {
return (
<li key={review.id}>
<figure>
<h3>
{review.review_name}
</h3>
</figure>
<p>
{review.review_text}
</p>
</li>
)
})
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(reviews.length / reviewsPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li
key={number}
id={number}
onClick={this.handlePageChange.bind(this)}
>
{number}
</li>
);
});
return (
<div>
<ul>
{renderReviews}
</ul>
<ul id="page-numbers">
{renderPageNumbers}
</ul>
</div>
)
}
}
export default Reviews;
The first error I am getting is understandable:
TypeError: reviews.slice is not a function
And that's because my reviews is not an array, I need to get my data so I tried:
let allReviews = reviews.reviews
console.log(allReviews)
const currentReviews = allReviews.slice......
And now, allReviews.slice is giving me undefined, and also does my console.log(allReviews).
But I noticed, that before changing my .slice, so:
let allReviews = reviews.reviews
console.log(allReviews)
const currentReviews = reviews.slice......
I get the same error as the first one, as expected, but I get data in my allReviews. I am not sure how to tackle this so I would appreciate it if someone can give me a hand on how to approach this.
Since your fetch is async, render code is running before you get the results. This is normal. In the render method you just need to check whether the fetch returned results. You can render an empty div if reviews.reviews is null or undefined.
Related
Been trying to print out a simple list from db for 2 days now, here's the code right now:
function CategoriesTable() {
const [isLoading, setLoading] = useState(true);
let item_list = [];
let print_list;
useEffect(() =>{
Axios.get('http://localhost:3000/categories').then((response) => {
const category_list = response.data.result;
if(category_list) {
for(let i = 0; i < category_list.length; i++){
item_list.push(category_list[i].category_name)
}
}
print_list = function() {
console.log(item_list.map((item) => <li>item</li>))
return item_list.map((item) => <li>item</li>)
}
setLoading(false);
})
}, [])
return (
<div>
{ !isLoading && print_list }
</div>
)
}
I think the function should be executed after the loading state gets changed to false, right? For some reason the function is not executing
By the way, I can print out the list in console without a problem, rendering the list is the problem.
I would suggest to do something like this:
function CategoriesTable() {
const [fetchedData, setFetchedData] = useState({
result: [],
isLoading: true,
});
useEffect(() =>{
Axios.get('http://localhost:3000/categories').then(response => {
const category_list = response.data.result;
setFetchedData({
isLoading: false,
result: category_list.map(category => <li>{ category.category_name }</li>),
})
})
}, [])
return (
<div>
{ !fetchedData.isLoading && fetchedData.result }
</div>
)
}
Basically rewrite from the ground up since the original code is quite messy I'm afraid.
Feel free to ask in the comments if you have any questions.
import { useState, useEffect } from "react";
import Axios from "axios";
/**
* Why mix cases here? Are you gonna use camel or snake case? Choose one and only one.
*/
function CategoriesTable() {
const [categoryNames, setCategoryNames] = useState([]);
useEffect(() => {
// not a good idea to use full URL on localhost
Axios.get('/categories').then((response) => {
const categoryList = response.data.result;
if (categoryList) {
const categoryNames = categoryList.map(({ category_name }) => category_name);
console.log(categoryNames); // in case you want ot keep the console output
setCategoryNames(categoryNames);
}
});
}, []);
return (
<div>
{/* You should either wrap <li> with either <ol> or <ul> */}
<ol>
{categoryNames.map(categoryName => (
// key is required and should be unique in map statement. In here I assume there are no duplicated categoryName
<li key={categoryName}>{categoryNames}</li>
))}
</ol>
</div>
);
}
I'm trying to make react not load until after an axios get requests finishes. I'm pretty rough on react all around, so sorry in advance.
I'm getting an array of objects
const { dogBreedsTest } = useApplicationData()
And I need it to be the default value of one of my states
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest);
However, I'm getting an error that my value is coming up as null on the first iteration of my app starting. How can I ensure that my value has completed my request before my app tries to use it?
Here is how I am getting the data for useApplicationData()
const [dogBreedsTest, setDogBreeds] = useState(null);
const getDogBreeds = async () => {
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
setDogBreeds
}
And I am importing into my app and using:
import useApplicationData from "./hooks/useApplicationData";
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest[0]);
const [breedList1, updateBreedList1] = useState(dogBreedsTest[0])
function handleOnDragEnd(result) {
if (!result.destination) return;
const items = Array.from(dogBreeds);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
for (const [index, item] of items.entries()) {
item['rank'] = index + 1
}
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0])
}
return (
<div className="flex-container">
<div className="App-header">
<h1>Dog Breeds 1</h1>
<DragDropContext onDragEnd={handleOnDragEnd}>
<Droppable droppableId="characters">
{(provided) => (
<ul className="dogBreeds" {...provided.droppableProps} ref={provided.innerRef}>
{breedList1?.map(({id, name, rank}, index) => {
return (
<Draggable key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable>
);
})}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
</div>
)
error: TypeError: Cannot read property 'map' of null
(I am mapping the data later in the program)
const getDogBreeds = async () => {
try {
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
}
useEffect(() => {
getDogBreeds() // -> you are not awaiting this
}, []);
Do this instead
useEffect(() => {
axios.get('https://dog.ceo/api/breeds/list/all')
.then(res => {
const newDogList = generateDogsArray(res.data['message']);
const generatedDogs = selectedDogs(newDogList);
setDogBreeds(generatedDogs);
})
.catch(err => console.log(err));
}, []);
I know this looks awful, but I don't think you should use async/await inside useEffect
Use this in your application
useEffect will update whenever dogBreedsTest is changed. In order to make it work, start with null values and update them to the correct initial values once your async operation is finished.
const { dogBreedsTest } = useApplicationData();
const [dogBreeds, updateDogBreeds] = useState(null);
const [breedList1, updateBreedList1] = useState(null);
useEffect(() => {
updateDogBreeds(dogBreedsTest[0]);
updateBreedList1(dogBreedsTest[0]);
}, [dogBreedsTest]);
The problem is, that react first render and then run useEffect(), so if you don't want to render nothing before the axios, you need to tell to react, that the first render is null.
Where is your map function, to see the code? to show you it?.
I suppose that your data first is null. So you can use something like.
if(!data) return null
2nd Option:
In your map try this:
{breedList1 === null
? null
: breedList1.map(({id, name, rank}, index) => (
<Draggable
key={id} draggableId={id} index={index}>
{(provided) => (
<li ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<p>
#{rank}: { name }
</p>
</li>
)}
</Draggable> ))}
You have null, because your axios is async and react try to render before any effect. So if you say to react that the list is null, react will render and load the data from the api in the second time.
Option 1 use the optional chaining operator
dogBreedsTest?.map()
Option 2 check in the return if dogBreedsTest is an array
retrun (<>
{Array.isArray(dogBreedsTest) && dogBreedsTest.map()}
</>)
Option 3 return early
if (!Array.isArray(dogBreedsTest)) return null
retrun (<>
{dogBreedsTest.map()}
</>)
Option 4 set initial state
const [dogBreedsTest, setDogBreeds] = useState([]);
You could also add a loading state and add a loading spinner or something like that:
const [dogBreedsTest, setDogBreeds] = useState(null);
const [loading, setLoading] = useState(true)
const getDogBreeds = async () => {
setLoading(true)
try{
const { data } = await axios.get('https://dog.ceo/api/breeds/list/all')
if(data) {
const newDogList = generateDogsArray(data['message'])
const generatedDogs = selectedDogs(newDogList)
setDogBreeds(generatedDogs)
}
} catch(err) {
console.log(err);
}
setLoading(false)
}
useEffect(() => {
getDogBreeds()
}, []);
return {
dogBreedsTest,
loading,
setDogBreeds
}
Edit
Try to use a useEffect hook to update the states when dogBreedsTest got set.
const { dogBreedsTest } = useApplicationData()
const [dogBreeds, updateDogBreeds] = useState(dogBreedsTest?.[0] ?? []);
const [breedList1, updateBreedList1] = useState(dogBreedsTest?.[0] ?? [])
useEffect(() => {
updateDogBreeds(dogBreedsTest?.[0] ?? [])
updateBreedList1(dogBreedsTest?.[0] ?? [])
}, [dogBreedsTest])
I am trying to display random gifs at onClick from GIFs API, but I am getting an error:
'Cannot read property 'images' of undefined'
when trying to get the URL for images.
Additionally, I have created a handleClick function to get random GIFs on every click.
Main.js
const Main = () => {
const [data, setData] = useState([])
useEffect(() => {
fetch('https://api.giphy.com/v1/gifs/trending?&limit=9&api_key=aIINKf0Pxb8MDhC2QPzTLbgvUXN6Uz7l')
.then(response => response.json())
.then(responseData => setData(responseData.data))
.catch(error => {
console.log('Error fetching and parsing data', error)
})
},[])
return(
<div className="main hide">
<GifList data={data} />
</div>
)
}
export default Main
GifList.js
import Gif from './Gif'
const GifList = (props) => {
let results = props.data
let gif
const pickRandomGif = () =>{
let randomGifs = []
for(let i=0; i<9; i++){
let randomNumber = Math.floor(Math.random() * results.length)
randomGifs.push(randomNumber)
}
gif = randomGifs.map(random =>
<Gif url={!results ? 'Loading...' : `${results[random].images.fixed_height.url}`}
key={random.id} />
)
}
const handleClick = (e) =>{
e.preventDefault()
pickRandomGif()
}
return (
<div>
<ul className="gif-list">
{gif}
</ul>
<button className="btn" onClick={handleClick}>Click here</button>
</div>
)
}
export default GifList
Gif.js
const Gif = (props) => {
return (
<li className="gif-wrap">
<img src={props.url} alt=""/>
</li>
)
}
export default Gif;
When the data is loading, data (and thus results) is an empty array. In your GifList, when data is empty randomNumber always equals 0. Then, when you map the randomGifs array, you try to access results[0], which is undefined since results is empty. This causes the error, since undefined is not an object with property images.
You should add a check for whether results is empty (or whether results[random] is undefined) to solve the issue.
Learning react
Trying to loop through an object from an API call that returns a json object and display it but struggling to implement it
This is the component that should render it
export default class ProfilePage extends Component {
constructor() {
super();
this.state = { data: '' };
}
mapObject(object, callback) {
return Object.keys(object).map(function (key) {
return callback(key, object[key]);
})
}
async componentDidMount() {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const json = await response.json();
// console.log(json)
this.setState({ data: json });
}
render() {
const data = this.state.data
console.log(data)
return (
<div className="row">
{Object.values(data).map(data => {
<div key={key}>
{data[key]}
</div>
})
}
Woerkkk please
</div>
);
}
}
All I'm getting is a blank screen.
in the console i get the error 'key' is not defined no-undef
You are missing a return statement in your map for your render method.
Edit: Key is not returned from Object.values
Either reconfigure with a return statement like so:
{Object.keys(data).map(key => {
return (<div key={key}>
{data[key]}
</div>);
})
Or alternatively you can implicitly return from arrow function using brackets
{Object.keys(data).map(key => (
<div key={key}>
{data[key]}
</div>)
))
Using Object.values(myObj) you can get all object values as a array. So, with this array, you can iterate over the array and show your items, like this:
{Object.values(myObj).map(value => <p>{value}</p>)}
Don't forget use key prop when iterating.
You can use useState and useEffect to fetch the object data
const App = () => {
const [objData, setObjData] = useState({});
const [objItems, setObjItems] = useState([]);
const fetchObj = async () => {
const response = await fetch(`https://indapi.kumba.io/webdev/assignment`);
const data = await response.json();
setObjData(data);
setObjItems(data.items);
}
useEffect(() => {
fetchObj()
},[]);
return(
<div>
<h1> Order Id :{objData.order_id}</h1>
// or any other objData keys
<h1>Items : </h1>
<ul>
{
objItems.map((i, idx) => {
return(
<li key={idx}>Name : {i.name} , Category: {i.category}, Price: {i.price}, Currency: {i.currency}</li>
)
})
}
</ul>
</div>
)
}
export default App;
I'm doing this fullstack course to learn about web dev: https://fullstackopen.com/en/part2/getting_data_from_server
And I have a problem with section 2.13*.
I am able to display a list of the countries after filtering with the button. Pressing the button returns the correct values from the countries arrays as seen with the console.log(country), but it doesn't to the screen.
My guess is that I can't return a div item within another item, but I am pretty sure that works in normal cases, so the fact that I'm returning the item to a different return statement might be the issue?
How can I fix this? I know my code is messy and a refactor might make things simpler, but it is currently beyond me right now since I find it easier to refactor working code.
In the DisplayCountries component, I've tried apply a map to countries that fit the filter input and prints it into a div item. Now when I add a button beside it, it displays correctly, but pressing it does not yield what I expect.
Is the correct approach here to use a useState with the button, so that each button click will rerender the screen? How would I go about doing this if so?
After pressing the button, the detailed information of the country should display such as in 2.12* from the linked website.
import { useState, useEffect } from 'react'
import axios from 'axios'
//feed array of countries
const printLanguages = (languages) => {
// console.log('map', languages.map(language => language.name))
return languages.map(language => <li key={language.name}>{language.name}</li>)
}
const displayCountryView = (country) => {
console.log(country)
return (
<div>
<h1>{country.name}</h1>
<p>capital {country.capital}</p>
<p>population {country.population}</p>
<h2>languages</h2>
<ul>
{printLanguages(country.languages)}
</ul>
<img src={country.flag} height="100" width="100"></img>
</div>
)
}
const DisplayCountries = ({ countries, searchValue }) => {
const displayFilter = filteredCountries(countries, searchValue)
// console.log('current search', searchValue)
if (displayFilter.length >= 10) {
return <p>Too many matches, specify another filter</p>
} else if (isFiltered(searchValue)) {
if (displayFilter.length > 1 && displayFilter.length < 10) {
console.log('new level')
return displayFilter.map(country => <div key={country.name}>{country.name}{showButton(country)}</div>)
} else if (displayFilter.length === 1) {
// console.log('suh')
// return displayFilter.map(country => <p key={country.name}>{country.name}</p>)
const country = displayFilter
return displayCountryView(country[0])
// console.log(country)
// console.log('country.name', country[0])
// console.log(country[0].languages)
// console.log(printLanguages(country[0].languages))
// return (
// <div>
// <h1>{country[0].name}</h1>
// <p>capital {country[0].capital}</p>
// <p>population {country[0].population}</p>
// <h2>languages</h2>
// <ul>
// {printLanguages(country[0].languages)}
// </ul>
// <img src={country[0].flag} height="100" width="100"></img>
// </div>
// )
}
} else {
return <p>empty</p>
}
}
const showButton = (country) => {
return <button type="button" onClick={() => displayCountryView(country)}>show</button>
}
const filteredCountries = (countries, searchValue) => {
const showCountries = (!isFiltered(searchValue))
? [{ name: "hi" }]
: countries.filter(country => country.name.toLowerCase().includes(searchValue.toLowerCase()))
// const countryMap = countries.map(country => country.name.toLowerCase())
// console.log(countryMap)
// return countryMap
return showCountries
}
function isFiltered(value) {
if (value === '') {
return false
} else {
return true
}
}
const Filter = ({ search, onChange }) => {
return (
<form >
<div>
find countries <input value={search} onChange={onChange} />
</div>
</form>
)
}
const App = () => {
const [countries, setCountries] = useState([])
const [search, setNewSearch] = useState('')
const [showCountry, setShowCountry] = useState('false')
useEffect(() => {
// console.log('effect')
axios
.get('https://restcountries.eu/rest/v2/all')
.then(response => {
// console.log('promise fulfilled')
setCountries(response.data)
})
}, [])
// const countryNames = countries.map(country => country.name)
// console.log('name', countryNames)
const handleSearchChange = (event) => {
setNewSearch(event.target.value)
}
// const fil = countries.filter(country => country.name==='Afg')
// console.log(countries[0])
// console.log('filtered:',fil)
// console.log(countries[0])
// console.log('render', countries.length, 'persons')
return (
<div>
<Filter search={search} onChange={handleSearchChange} />
<form>
<div>
<DisplayCountries countries={countries} searchValue={search} />
</div>
</form>
</div>
)
}
export default App;