Here I am trying to get productList from MySQL database and for each product object I am assigning new property - imageURL via getImages(). When I log productList to console, there is property imageURL with correct url. Problem is, when I try to map it, it shows nothing. Why?
const storageRef = firebase.storage().ref("/assets")
const [productList, setProductList] = useState([])
useEffect(() => {
Axios.get("http://localhost:3001/product/get").then((response) => {
setProductList(response.data)
})
}, [])
useEffect(() => {
getImages(productList)
}, [productList])
const getImages = (array) => {
array.forEach((item) => {
storageRef.child(`${item.bannerImage}`).getDownloadURL().then((url) => {
item.imageURL = url
})
})
}
My map function:
{productList.map((val) => {
return (
<div key={val.id} className="product">
<div className="item">
<h1>Product title: {val.title}</h1>
<h2>Price: {val.price}</h2>
<h2>Quantity: {val.quantity}</h2>
<h2>IMAGE: {val.imageURL}</h2>
</div>
</div>
)
})}
Problems:
You are not setting productList back in getImages function. You are just iterating over array.
getDownloadURL is a async function, you should not use it inside loop. The best way to do this is through a recursive function. But you can also do this as below:
Solution
Your getImage function
const getImage = async (bannerImage) => {
const url = await storageRef.child(bannerImage).getDownloadURL();
return url;
}
then your map function
{productList.map((val) => {
return (
<div key={val.id} className="product">
<div className="item">
<h1>Product title: {val.title}</h1>
<h2>Price: {val.price}</h2>
<h2>Quantity: {val.quantity}</h2>
<h2>IMAGE: {getImage(val.bannerImage)}</h2>
</div>
</div>
)
})}
I would suggest you create another small component for your image rendering and handle async for getDownloadURL behaviour inside that component
function ProductImage({bannerImage}) {
const [imageUrl, setImageUrl] = useState('')
useEffect(() => {
async function getImage(bannerImage) {
const url = await bannerImage.getDownloadURL()
setImageUrl(url)
}
getImage(bannerImage)
}, [bannerImage])
return imageUrl ? <h2>IMAGE: {imageUrl}</h2> : '...Loading'
}
And use this component in your main component
{productList.map((val) => {
return (
<div key={val.id} className="product">
<div className="item">
<h1>Product title: {val.title}</h1>
<h2>Price: {val.price}</h2>
<h2>Quantity: {val.quantity}</h2>
<ProductImage bannerImage={val.bannerImage} />
</div>
</div>
)
})}
Related
I have the parent Posts.js component which map every object in posts array. In this function I try to filter all notes have same post_id as id of the current mapped post object. All stored in filteredNotes variable. Then I pass it to each child. Now the issue. When I want to add new note in specific post, the view doesn't update (new note was not added to the list) although the database and redux store has been updated successfully.
But when I try to remove that filter function, everything works just fine so I guess the main problem is there. Any idea how to fix this? Thanks
Posts.js
const posts = useSelector((state) => state.post.posts);
const notes = useSelector((state) => state.notes.notes);
useEffect(() => {
dispatch(getPosts());
dispatch(getNotes());
}, []);
const addNoteHandle = (val) => {
dispatch(addNote({new_note: val}));
}
return (
<div className="post__page">
<div className="post__list">
{posts.map((data) => {
let filteredNotes = notes.filter((i) => i.post_id === data.id);
return <Post data={data} notes={filteredNotes} />;
})}
</div>
<PostForm addNewNote={addNoteHandle} />
</div>
);
Post.js
export const Post = ({ data, notes }) => {
return (
<div className="post__item">
<div className="post__title">{data.title}</div>
<div className="post__note">
{notes.map(note => <div>{note.text}</div>)}
</div>
</div>
);
};
NoteForm.js
const NoteForm = ({ addNewNote }) => {
const [text, setText] = useState("");
return (
<div>
<Input value={text} onChange={(e) => setText(e.target.value)} />
<Button type="primary" onClick={() => addNewNote(text)} >
<SendOutlined />
</Button>
</div>
);
};
Action
export const addNote = ({ new_note }) => async (dispatch) => {
try {
const res = await axios.post("http://localhost:9000/api/note", new_note);
dispatch({ type: ADD_NOTE, payload: res.data });
} catch (err) {
dispatch({ type: NOTE_FAIL });
}
};
Reducer
case ADD_NOTE:
return {
...state,
notes: [...state.notes, payload]
};
use useSelector to get the component value from redux store. for some reason hook setText will not work to update the page component. I had a similar problem and could not find any solution. This code may help:
let text ='';
text = useSelector((state) =>
state.yourReducer.text);
Now show your text wherever you want
this will fix the issue until you find real solution
today i have a problem with my searchbar.
const [posts, setPosts] = useState(null)
const [searchTerm, setSearchTerm] = useState("")
useEffect(() => {
const loadPosts = async () => {
try {
const post = await getAllPosts()
setPosts(post)
} catch (e) {
alert("Couldn't load posts")
}
}
loadPosts()
}, [])
return (
<div>
<input type={"text"} placeholder="Search..." onChange={event => {
setSearchTerm(event.target.value)
}}/>
</div>
)
}
This is my Searchbar Component. In the Index file, did i gave a props with.
const [posts, setPosts] = useState([])
const [searchTerm, setSearchTerm] = useState("")
useEffect(() => {
const loadPosts = async () => {
try {
const post = await getAllPosts()
setPosts(post)
} catch (e) {
alert("Couldn't load posts")
}
}
loadPosts()
}, [])
return (
<div className={styles.posts}>
<h1>Market-place Valando</h1>
<SearchList title={posts.filter(post => {
if (post.title.toLowerCase().includes(searchTerm.trim().toLowerCase()) && searchTerm.trim() !== "") {
return post.title
}
}).map(titles => {
{
{titles.title}
}
}
)}/>
{
posts.map(post => {
return (
<div key={post.id} className={styles.key}>
<h1>{post.title}</h1>
<Image width={1000} height={1000} src={post.image}/>
<p>Price: {post.price}.-</p>
<p>Description: {post.description}</p>
<Link href={`/posts/${post.id}`} passHref>
<a>Read more</a>
</Link>
</div>
)
})
}
</div>
)
}
I have a db.json file that i connected with an API File. In this Api File i made all the fetch stuff. This shouldnt be the problem. I think the problem is, that the filter doesnt work properly, with the titels.
You are correct, JavaScript filter does not return specific property values, but it returns the top entries of the array, a.k.a posts. So return post.title or return true will yield the same result. However, the problem in your code appears to be that you are not returning anything from the map function. All you need to do is to change it to the following:
.map(post => post.title)
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'm trying to implement search functionality on the Pokemon API, I have tried different methods but I cannot make it work for some reason.
My idea was to make a function that handles the changes on the event and then pass that to a hook(useState) and maybe make a get request and rerender?
I have this method for getting all Pokemons from the API. Should I make a new designed to filter the request?
export async function getAllPokemon(url) {
return new Promise((resolve) => {
fetch(url).then(res => res.json())
.then(data => {
resolve(data)
})
});
}
function App() {
....
const [filter, setFilter] = useState("");
function handleChange(event) {
setOption(event.target.value)
console.log(option)
}
const initialURL = `https://pokeapi.co/api/v2/pokemon?limit=50`
const handleSearchChange = (e) => {
setFilter(e.target.value);
};
useEffect(() => {
async function fetchData() {
let response = await getAllPokemon(initialURL)
await loadPokemon(response.results);
setLoading(false);
}
fetchData();
}, [option])
const loadPokemon = async (data) => {
let _pokemonData = await Promise.all(data.map(async pokemon => {
let pokemonRecord = await getPokemon(pokemon)
return pokemonRecord
}))
setPokemonData(_pokemonData);
}
return (
<>
<div >
<div>
<input
type="text"
id="header-search"
placeholder="Search Pokemon"
name="s"
onChange={handleSearchChange}
/>
<button >Search</button>
</div>
</div>
<div>
{loading ? <h1 style={{ textAlign: 'center' }}>Loading...</h1> : (
<>
<div className="grid-container">
{pokemonData.map((pokemon) => {
return <Card pokemon={pokemon} />
})}
</div>
</>
)}
</div>
</>
);
}
export default App;
You need to filter the array of Pokemon's that you are getting in your onChange. So something like
onChange = e = > {
setPokemons(pokemons.filter(pokemon => pokemon.indexOf(e.target.value) > -1))
}
So, this will toggle your local state variable that you are rendering on every key that is typed, which in turn will filter your array.
i'm having an error while trying to map an array (code below). The error i'm receiving is : Uncaught (in promise) TypeError: posts.map is not a function.
I have compared it to one of my old project, and it worked perfectly on the older one. Thanks in advance
const MainContent = () => {
const openModal = () => {
const modal = document.querySelector(".modal");
modal.style.display = "block";
};
const [posts, setPosts] = useState([]);
const getPosts = async () => {
const response = await fetch('http://localhost:5000/api/post/');
const posts = await response.json();
setPosts(posts);
console.log(posts)
}
useEffect(() => {
getPosts();
}, []);
return (
<section className="main-content-section">
<button className="button post-btn" onClick={openModal}>
Créer un nouveau post
</button>
<ModalPost style={{ display: "none" }} />
<div className="post-container">
{posts.map((post) => {
const { id, title, content, author } = post;
return (
<div className="post-content" key={id}>
<div className="post-content__user-info">
<div className="post-content__photo-container">
</div>
<div className="post-content__name">
<h3>{author}</h3>
</div>
</div>
<div className="post-content__content">
<h3>{title}</h3>
<p>{content}</p>
</div>
<div className="post-content__like-box">
<BiHeart className="like-heart like" size={26} />
<RiDislikeLine className="dislike-heart dislike" size={26} />
</div>
</div>
);
})}
</div>
</section>
);
};
Just add if statement before mapping post array. The reason is that posts array does not get data immediately causing map function to fail. if condition will wait until posts array actually contains data
{
(posts && posts.length > 0) ?
(posts.map((post) => {})
: (<p> loading... </p>)
}
const getPosts = async () => {
const response = await fetch('http://localhost:5000/api/post/');
const posts = await response.json();
setPosts(posts);
console.log(posts)
}
Here, the result of response.json() is going to be an object, and not an array. Probably, you have to look inside the object and find the posts array.