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>
);
}
Related
What I want is to paginate my data but the problem is when I'm searching for specific data if I'm on page 3 the result shows on page 1 always and I can't see anything because I was on page no 3. I want to go to page 1 automatically when I'm searching for something. Also when I press the next button if there is no data at all it still increases the page number.
Here is my code:
import { React, useState, useEffect } from "react";
import UpdateDialogue from "./UpdateDialogue";
function List(props) {
const API_URL = "http://dummy.restapiexample.com/api/v1/employees";
const [EmployeeData, setEmployeeData] = useState([]);
const [pageNumber, setPageNumber] = useState(1);
const [postNumber] = useState(8);
const currentPageNumber = pageNumber * postNumber - postNumber;
const handlePrev = () => {
if (pageNumber === 1) return;
setPageNumber(pageNumber - 1);
};
const handleNext = () => {
setPageNumber(pageNumber + 1);
};
useEffect(() => {
fetch(API_URL)
.then((response) => response.json())
.then((response) => {
setEmployeeData(response.data);
})
.catch((err) => {
console.error(err);
});
}, []);
const filteredData = EmployeeData.filter((el) => {
if (props.input === "") {
return el;
} else {
return el.employee_name.toLowerCase().includes(props.input)
}
});
const paginatedData = filteredData.splice(currentPageNumber, postNumber);
return (
<>
<ul>
{paginatedData.map((user) => (
<UpdateDialogue user={user} key={user.id} />
))}
</ul>
<div>Page {pageNumber} </div>
<div>
<button style={{marginRight:10}} onClick={handlePrev}>prev</button>
<button onClick={handleNext}>next</button>
</div>
</>
);
}
export default List;
Maybe with a useEffect on your input:
useEffect(() => {
if (props.input) {
setPageNumber(1);
}
}, [props.input]);
That way, whenever your input changes, your page number is set to 1.
I want to add items to an array with the useState hook instead of doing array.push. This is the original code:
let tags = []
data.blog.posts.map(post => {
post.frontmatter.tags.forEach(tag => {
if (!tags.includes(tag)){
tags.push(tag)
}
})
})
This is one of several things I've tried with React:
const [tags, setTags] = useState([])
data.blog.posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!tags.includes(tag)){
setTags(tags => [...tags, tag])
}
})
})
The "tags" state variable does not receive anything in the above example.
I have looked at a variety of similar threads but the problems and solutions there are difficult to translate to this situation.
You can try setting the tags state in initial render or on any event as per your requirement .
const [tags, setTags] = useState([]);
useEffect(()=>{
const arr=[];
data.blog.posts.map(post => {
post.frontmatter.tags.map(tag => {
if (!arr.includes(tag)){
arr.push(tag)
}
})
});
setTags([...arr]);
},[]);
Ok, I did understand what you wanted to do.
Here is the code and I did add some commest and there is also a working code sandbox
so it will show the "tags" you have on your state and when you click on the button it will filter and add those tags that are missing
import React, { useState } from "react";
//mock data.blog.posts
const data = {
blog: {
posts: [
{
frontmatter: {
tags: ["tag1", "tag2", "tag3"]
}
}
]
}
};
const App = () => {
const [tags, setTags] = useState(["tag1"]);
const filterTags = () => {
const myTags = ["tag1"];
let result;
data.blog.posts.map((post) => {
// check what tags are not included in tag stateon line 18
result = post.frontmatter.tags.filter((item) => !tags.includes(item));
});
// here it will show that 'tag2' and 'tag3' does not exist
console.log("result", result);
// here we are setting the state
setTags((oldState) => [...oldState, ...result]);
};
return (
<div className="App">
<h1>My tags</h1>
{tags.map((tag) => (
<h4>{tag}</h4>
))}
<button onClick={() => filterTags()}>add tags</button>
<hr />
<h1>My tags from posts</h1>
{data.blog.posts.map((posts) => {
return posts.frontmatter.tags.map((tag) => <div>{tag}</div>);
})}
</div>
);
};
export default App;
and here is the codeSandBox
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 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.
import React, { useState, useEffect } from "react";
import axios from "axios";
const App = () => {
let [countries, setCountries] = useState([]);
const [newCountry, newStuff] = useState("");
const hook = () => {
//console.log("effect");
axios.get("https://restcountries.eu/rest/v2/all").then((response) => {
console.log("promise fulfilled");
setCountries(response.data);
//console.log(response.data);
});
};
const filter = (event) => {
newStuff(event.target.value);
if (event.target.value === undefined) {
return
} else {
let value = event.target.value;
console.log(value);
countries = countries.filter((country) => country.name.startsWith(value));
setCountries(countries);
console.log(countries);
}
};
useEffect(hook, []);
return (
<div>
<p>find countries</p>
<input value={newCountry} onChange={filter} />
<ul>
{countries.map((country) => (
<li key={country.name.length}>{country.name}</li>
))}
</ul>
</div>
);
};
export default App;
So I have a search bar so that when you enter a few characters it will update the state and show the countries that start with the respective first characters. However, nothing is being shown when I enter input into my search bar. Also, my filter function, when I console.log my countries array which is supposed to have the countries that start with the characters I entered, it's always an empty array.
You need some changes in order to make this work:
Use two states for countries, one for the list you
get in the initial render and another for the current filter
countries.
const [countriesStore, setCountriesStore] = useState([]); // this only change in the first render
const [countries, setCountries] = useState([]); // use this to print the list
I recomed to use any tool to manage the state and create a model for
the countries ther you can make the side effect there and create an
action that update the countries store. I'm using Easy Peasy in
my current project and it goes very well.
Take care of the filter method because startsWith
method is not case-insensitive. You need a regular expression or
turn the current country value to lower case. I recommend to use
includes method to match seconds names like island in the search.
const filterCountries = countriesStore.filter(country => {
return country.name.toLowerCase().includes(value);
});
Remove the if condition in the filter in order to include the
delete action in the search and get the full list again if
everything is removed.
Just in the case, empty the search string state in the first
render
useEffect(() => {
hook();
setSearchString("");
}, []);
Replace the length in the list key. You can use the name and trim to remove space.
<li key={country.name.trim()}>{country.name}</li>
The final code look like this:
export default function App() {
const [countriesStore, setCountriesStore] = useState([]);
const [countries, setCountries] = useState([]);
const [searchString, setSearchString] = useState("");
const hook = () => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
console.log("promise fulfilled");
setCountriesStore(response.data);
setCountries(response.data);
});
};
const filter = event => {
setSearchString(event.target.value);
let value = event.target.value;
const filterCountries = countriesStore.filter(country => {
return country.name.toLowerCase().includes(value);
});
setCountries(filterCountries);
};
useEffect(() => {
hook();
setSearchString("");
}, []);
return (
<div>
<p>find countries</p>
<input value={searchString} onChange={filter} />
<ul>
{countries.map(country => (
<li key={country.name.trim()}>{country.name}</li>
))}
</ul>
</div>
);
}
You need to wrap your hook into async useCallback:
const hook = useCallback(async () => {
const {data} = await axios.get("https://restcountries.eu/rest/v2/all");
setCountries(data);
}, []);
you are not able to mutate state countries. Use immutable way to update your state:
const filter = (event) => {
newStuff(event.target.value);
if (event.target.value === undefined) {
return
} else {
let value = event.target.value;
setCountries(countries.filter((country) => country.name.startsWith(value)));
}
};
And useState is asynchronous function. You will not see result immediately. Just try to console.log outside of any function.