Why am I unable to map my data in React project? - javascript

Ill be changing the key shortly. Using the code below I should be able to load a list of movies from the API and each movie should be linked to it's Provider Link website. using
the upMovieDetail. can anyone help point me in the right direction? I have a feeling it has something to do with the component being re-renderd after the click?
here is the codesandbox if you'd rather try to fix it here.. --
https://codesandbox.io/s/movieapp-searchbar-forked-qv1o6
const key ="fde5ddeba3b7dec3fc1f51852ca0fb95";
const getUpMovieDetail = (movieId) => {
//const [movieId, setMovieId] = useState([]);
const url = `https://api.themoviedb.org/3/movie/${movieId}/watch/providers?api_key=${key}`;
return fetch(url);
};
function UpMovieDetail({ movieItem }) {
const [searchLink, setSearchLink] = useState(null);
useEffect(() => {
getUpMovieDetail(movieItem.id)
.then((res) => res.json())
.then((res) => {
setSearchLink(res?.results?.US?.link);
});
}, [movieItem.id]);
return (
<ul className="flexed-search">
{searchLink.map((item) =>
<div className="poster-container" key={item.id}>
<li className="list-item">
<a target="_blank" rel="noopener noreferrer" href={searchLink}
onclick={((event) => {event.preventDefault()})}>
<img className="image-element" tabIndex="0" alt="movie poster"
title={`--Title: ${item.title}-- --Description:
${item.overview}-- --Vote Average: ${item.vote_average}`}
aria-label={item.title}
src={`https://image.tmdb.org/t/p/w500${item.poster_path}`} />
</a>
<h3 className="posterTitle">{item.title}</h3>
</li>
</div>
)}
</ul>
);
};
const SearchBar = () => {
const [search, setSearch] = useState([]);
const [input, setInput] = useState('');
// Input Field
const onUserInput = ({target}) => {
setInput(target.value);
};
// Api Call
const SearchApi = (event) => {
const aUrl = "https://api.themoviedb.org/3/search/movie?api_key=fde5ddeba3b7dec3fc1f51852ca0fb95";
const newUrl = aUrl +'&query=' + input;
event.preventDefault();
fetch(newUrl)
.then((response) => response.json())
.then((data) => {
setSearch(data.results);
})
.catch((error) => {
console.log('Error!! Data interupted!:', error)
})
};
return (
// Heading
<div>
<div className="container">
<h1>Movie Search Extravaganza!</h1>
{/* Input Field and Button Form */}
<form onSubmit={SearchApi}>
<input value={input} onChange={onUserInput} type="text" className="searchbar" aria-label="searchbar" placeholder="search" required/>
<br></br>
<button type="submit" aria-label="searchbutton" className="searchBtn">Movie Express Search</button>
</form>
<h1 className="row-label" tabIndex="0">Movies Related To Your Search</h1>
</div>
<div className="byName-container">
{search.map((item) => (
<UpMovieDetail key={item.id} movieItem={item} />
))}
</div>
</div>
)};
export default SearchBar;```
[1]: http://codesandbox.io/s/movieapp-searchbar-forked-qv1o6
[2]: https://codesandbox.io/s/movieapp-searchbar-forked-qv1o6

From the first render it throws the error because searchLink is null.
Try this:
{
searchLink && searchLink.length && searchLink.map((item) =>
...
}

Related

Wrong document gets updated when using updateDoc from Firebase/Firestore

I use prop drilling to pass down the id value of the document, but every time I click on a document to update it using updateDoc, the same document gets updated(always the latest one added), not the one I clicked on. I don't understand why the unique IDs don't get passed down correctly to the function, or whether that's the problem. I use deleteDoc this way and it's working perfectly. Any help will be appreciated.
This is where I get the id value from
const getPosts = useCallback(async (id) => {
const data = await getDocs(postCollectionRef);
setPosts(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
});
useEffect(() => {
getPosts();
}, [deletePost]);
return (
<div className={classes.home}>
<ul className={classes.list}>
{posts.map((post) => (
<BlogCard
key={post.id}
id={post.id}
title={post.title}
image={post.image}
post={post.post}
date={post.date}
showModal={showModal}
setShowModal={setShowModal}
deletePost={() => {
deletePost(post.id);
}}
showUpdateModal={showUpdateModal}
setShowUpdateModal={setShowUpdateModal}
/>
))}
</ul>
</div>
);
This is where I pass through the id value to the update modal component for each document:
function BlogCard(props) {
const [postIsOpen, setPostIsOpen] = useState(false);
const modalHandler = () => {
props.setShowModal((prevState) => {
return (prevState = !prevState);
});
};
const updateModalHandler = () => {
props.setShowUpdateModal((prevState) => {
return (prevState = !prevState);
});
};
const handleView = () => {
setPostIsOpen((prevState) => {
return (prevState = !prevState);
});
};
return (
<>
{props.showUpdateModal && (
<UpdateModal
showUpdateModal={props.showUpdateModal}
setShowUpdateModal={props.setShowUpdateModal}
id={props.id}
title={props.title}
image={props.image}
post={props.post}
/>
)}
{props.showModal && (
<DeleteModal
showModal={props.showModal}
setShowModal={props.setShowModal}
deletePost={props.deletePost}
/>
)}
<div className={classes.blogCard} id={props.id}>
<div className={classes.head}>
<p className={classes.title}> {props.title}</p>
<div className={classes.buttons}>
<button className={classes.editButton} onClick={updateModalHandler}>
Edit
</button>
<button className={classes.removeButton} onClick={modalHandler}>
Delete
</button>
</div>
</div>
<p className={classes.date}>{props.date}</p>
<img src={props.image} alt="image" />
{!postIsOpen ? (
<p className={classes.viewHandler} onClick={handleView}>
Show More
</p>
) : (
<p className={classes.viewHandler} onClick={handleView}>
Show Less
</p>
)}
{postIsOpen && <p className={classes.article}>{props.post}</p>}
</div>
</>
);
}
export default BlogCard;
Here I create the function to update and add the onclick listener
function UpdateModal(props) {
const [title, setTitle] = useState(props.title);
const [image, setImage] = useState(props.image);
const [post, setPost] = useState(props.post);
const updateModalHandler = (prevState) => {
props.setShowUpdateModal((prevState = !prevState));
};
const updatePost = async (id) => {
const postDocRef = doc(db, "posts", id);
props.setShowUpdateModal(false);
try {
await updateDoc(postDocRef, {
title: title,
image: image,
post: post,
});
} catch (err) {
alert(err);
}
};
return (
<div onClick={updateModalHandler} className={classes.backdrop}>
<form onClick={(e) => e.stopPropagation()} className={classes.form}>
<label htmlFor="title">Title</label>
<input
id="title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<label htmlFor="image">Image(URL)</label>
<input
id="image"
type="text"
value={image}
onChange={(e) => setImage(e.target.value)}
/>
<label htmlFor="post">Post</label>
<textarea
id="post"
cols="30"
rows="30"
value={post}
onChange={(e) => setPost(e.target.value)}
/>
<div className={classes.buttons}>
<button className={classes.cancel} onClick={updateModalHandler}>Cancel</button>
<button className={classes.update} onClick={() => updatePost(props.id)}>Update</button>
</div>
</form>
</div>
);
}
export default UpdateModal;
This is the way my data is structured
firebase

Array state not pushing elements correctly from input field

I'm trying to collect elements from an input field in React. I have a state with an empty array, but when I try to push an element into it, the resulting array is always one element late.
What I mean is that if I enter a number into the array and I console.log it nothing happens, but if I insert another, then I'm able to see the previous one.
This is what I tried, the submitWeight function is triggered by a submit button.
Code:
const [inputWeight, setInputWeight] = useState("");
const [weight, setWeight] = useState([]);
const submitWeight = () => {
setWeight([...weight, inputWeight]);
};
And here's the JSX portion:
<div className="input-container">
<input type="text" onChange={(e) => setInputWeight(e.target.value)} />
<button onClick={submitWeight}>SUBMIT</button>
</div>
Full component:
const MainContainer = () => {
const [date, setDate] = useState(new Date());
const [recipeOfTheDay, setRecipeOfTheDay] = useState("");
const [recipeImg, setRecipeImg] = useState("");
const [quoteOfTheDay, setQuoteOfTheDay] = useState("");
const [modalClass, setModalClass] = useState("closed");
const [inputWeight, setInputWeight] = useState("");
const [weight, setWeight] = useState([]);
//FUNZIONI
const openClass = () => {
setModalClass("open");
};
const closedClass = () => {
setModalClass("closed");
};
const submitWeight = () => {
setWeight([...weight, inputWeight]);
console.log(weight);
};
//API
useEffect(() => {
axios
.get(
"https://zenquotes.io/api/random/9b5ee37d2eebfa303c900da058c17eaa914c5709"
)
.then((res) => setQuoteOfTheDay(res.data[0].q))
.catch((err) => console.log(err));
axios
.get(
"https://api.edamam.com/api/recipes/v2?type=public&q=beef&app_id=68835608&app_key=%20a13f4a407eb56c9970ab3732c77bc8cb%09&diet=high-protein&random=true"
)
.then((res) => setRecipeOfTheDay(res.data.hits[0].recipe.label))
.catch((err) => console.log(err));
axios
.get(
"https://api.edamam.com/api/recipes/v2?type=public&q=beef&app_id=68835608&app_key=%20a13f4a407eb56c9970ab3732c77bc8cb%09&diet=high-protein&random=true"
)
.then((res) => setRecipeImg(res.data.hits[0].recipe.image))
.catch((err) => console.log(err));
}, []);
return (
<div className="main-container">
<div className="div-1">
<img src={logo} alt="logo" className="logo" />
</div>
<div className="div-2">
<h1>
{date.getDate()} {date.getMonth()} {date.getFullYear()}
</h1>
</div>
<div className="div-3">
<h1>Add Weight</h1>
<img
src={plussign}
alt="plus sign"
className="plus-sign"
onClick={openClass}
/>
</div>
<div className="div-4">
<h1>ok</h1>
</div>
<div className="div-5">
<div className="quote-container">
<div className="quote-subcontainer">
<h1>Quote Of The Day:</h1>
<p>{quoteOfTheDay}</p>
</div>
</div>
<div
className="recipe-container"
style={{ backgroundImage: `url(${recipeImg})` }}
>
<div className="recipe-subcontainer">
<h1>Recipe Of The Day:</h1>
<p>{recipeOfTheDay}</p>
<img src={showmore} alt="show more" className="show-more" />
</div>
</div>
</div>
<div className="div-6">
<div className="weight-history-container">
<p>Enter your weight to start tracking.</p>
</div>
</div>
<div className={`page-mask ${modalClass}`}></div>
<div className={`modal-add-weight ${modalClass}`}>
<p>What's your weight today?</p>
<div className="input-container">
<input type="text" onChange={(e) => setInputWeight(e.target.value)} />
<h1> kg</h1>
</div>
<div className="modal-button-container">
<button onClick={submitWeight}>SUBMIT</button>
<button onClick={closedClass}>CLOSE</button>
</div>
</div>
</div>
);
};
Thank you!
The setState or in your case setWeight function is async. So, when you call console.log directly under the setWeight, the function didn't execute yet, thus showing you the old value.
If you want to console.log the new one, or do something once the state changes, you can use the useEffect hook.
useEffect(() => {
console.log(weight)
}, [weight])
If you want to read more about why setting state is async, you can find it in the docs here: https://reactjs.org/docs/state-and-lifecycle.html

Update a React Element with the Data of Another Component's API Response

I am trying to make a simple react app that pulls info from a MySQL database ("username", "balance", "purchases").
So far, I've used node and react to pull from the database with an HTTP query and to display each element on the website.
I then created the API query for searching the database for all entries that start with what I've typed into the search bar.
The issue I'm running into is how do I change the state of the elements that display the username, etc with the new filtered information from the API query? The search bar and data elements are two separate components so I can't use the use effect hook natively.
I cant use the filter method because the database is huge and I've sent my query limit to 100.
Here's my code so far:
PlayerData.js
import axios from 'axios';
import React,{useState, useEffect} from 'react';
const Player = () => {
const [playerData,setPlayerData]=useState([])
useEffect(()=>{
axios.get("http://localhost:3001/api/get").then((res)=>{
console.log(res.data)
setPlayerData(res.data)
})
.catch(err=>{
console.log(err);
})
},[])
return (
<>
{playerData.map((data,id)=>{
return <div className="Player" key={id}>
<span className="Username"> { data.name } </span>
<span className="Crystals"> { data.balance } </span>
<span className="DateModi"> {Object.keys(JSON.parse(data.items)).length} </span>
</div>
})}
</>
)
};
export default Player;
SearchBar.js
import { useState } from "react";
import axios from 'axios'
const Search = () => {
const [searchTerm, setSearchTerm] = useState("")
axios.get(`http://localhost:3001/api/getSearchName/${searchTerm}`).then((res)=>{
console.log(res.data)
})
return (
<div className="Search">
<input className = "InputField" type="text" placeholder="Enter Username:" onChange={e => {setSearchTerm(e.target.value)}}/>
<span className="SearchButton" onClick={console.log(searchTerm)}>
Search
</span>
</div>
)
};
export default Search;
If I understood the question correctly, you need to set the state of PlayerData to a shared component(App), and pass it to the Player.js component. Then when searching it will be overwritten and update the information in the Player.js
function App() {
const [playerData, setPlayerData] = useState([]);
useEffect(() => {
fetchData();
}, []);
const fetchData = () =>
axios
.get("http://localhost:3001/api/get")
.then((res) => {
setPlayerData(res.data);
})
.catch((err) => {
console.log(err);
});
const handleSearch = (text) => {
const clearText = text.trim();
if (!clearText.length) {
fetchData();
return;
}
axios
.get(`http://localhost:3001/api/getSearchName/${clearText}`)
.then((res) => {
setPlayerData(res.data);
});
};
return (
<div>
<div>
<Search handleSearch={handleSearch} />
</div>
<div>
<Player playerData={playerData} />
</div>
</div>
);
}
Search.js
const Search = ({ handleSearch }) => {
const [searchTerm, setSearchTerm] = useState("");
return (
<div className="Search">
<input
className="InputField"
type="text"
placeholder="Enter Username:"
onChange={(e) => {
setSearchTerm(e.target.value);
}}
/>
<span className="SearchButton" onClick={() => handleSearch(searchTerm)}>
Search
</span>
</div>
);
};
Player.js
const Player = ({ playerData }) => {
return (
<>
{playerData?.length ? (
playerData.map((data, id) => {
return (
<div className="Player" key={id}>
<span className="Username"> {data.name} </span>
<span className="Crystals"> {data.balance} </span>
<span className="DateModi">
{" "}
{Object.keys(JSON.parse(data.items)).length}{" "}
</span>
</div>
);
})
) : (
<div>Loading...</div>
)}
</>
);
};

react update state from children broke component at the same level

I am new to react. I'm trying to update the parent state from the child but i have an error on another component at the the same level of the child one.
that's my code.
RedirectPage.js (parent)
const RedirectPage = (props) => {
const [status, setStatus] = useState("Loading");
const [weather, setWeather] = useState(null);
const [location, setLocation] = useState(null)
const [showLoader, setShowLoader] = useState(true)
const [userId, setUserId] = useState(false)
const [isPlaylistCreated, setIsPlaylistCreated] = useState(false)
const headers = getParamValues(props.location.hash)
const getWeather = () =>{
//fetch data..
//...
//...
.then(response => {
var res = response.json();
return res;
})
.then(result => {
setWeather(result)
setShowLoader(false)
setStatus(null)
setLocation(result.name)
});
})
}
const changeStateFromChild = (value) => {
setIsPlaylistCreated(value)
}
useEffect(() => {
getWeather()
},[]);
return (
<div className="containerRedirectPage">
{showLoader ? (
<div className="wrapperLogo">
<img src={loader}className="" alt="logo" />
</div>)
: (
<div className="wrapperColonne">
<div className="firstRow">
<WeatherCard weatherConditions={weather}/>
</div>
{isPlaylistCreated ? (
<div className="secondRow">
<PlaylistCard />
</div>
) : (
<PlaylistButton userId={userId} headers={headers} weatherInfo={weather} playlistCreated={changeStateFromChild} />
)}
</div>
)}
</div>
)
};
export default RedirectPage;
PlaylistButton.js:
export default function PlaylistButton({userId, headers, weatherInfo, playlistCreated}) {
const buttonClicked = async () => {
// ...some code...
playlistCreated(true)
}
return (
<div className="button-container-1">
<span className="mas">CREA PLAYLIST</span>
<button onClick={buttonClicked} id='work' type="button" name="Hover">CREA PLAYLIST</button>
</div>
)
}
and that's the other component i'm getting the error when i click on button.
WeatherCard.js:
const WeatherCard = ({weatherConditions}) => {
const [weather, setWeather] = useState(null);
const [icon, setIcon] = useState(null);
const getTheIcon = () => {
// code to get the right icon
}
setIcon(x)
}
useEffect(() => {
getTheIcon()
},[]);
return (
<div className="weatherCard">
<div className="headerCard">
<h2>{weatherConditions.name}</h2>
<h3>{Math.floor(weatherConditions.main.temp)}°C</h3>
</div>
<div className="bodyCard">
<h5>{weatherConditions.weather[0].description}</h5>
<img className="weatherIcon" src={icon} alt="aa" />
</div>
</div>
)
};
export default WeatherCard;
the first time i load the redirect page WeatherCard component is right. When i click the button i get this error:
error
Can someone explain me why ?
What is the effect of the setting playlistCreated(true) ?
Does it affects the weatherCondition object ?
If weatherCondition could be undefined at some point you need to check it before using its properties (name, main.temp, and weather)
Update:
The error clearly state that it cannot read name from weather because it's undefined. You have to check it before using the weather object properties.
if (!weatherConditions) {
return <div>Loading...</div> // or something appropriate.
}
return (
<div className="weatherCard">
<div className="headerCard">
<h2>{weatherConditions.name}</h2>
{weatherConditions.main && <h3>{Math.floor(weatherConditions.main.temp)}°C</h3>}
</div>
<div className="bodyCard">
{weatherConditions.weather &&
{weatherConditions.weather.length > 0 &&
<h5>{weatherConditions.weather[0].description}</h5>}
....
)

How can I collapse an accordion from a child component in react

I am creating a page to update product details on an e-commerce site I am building using NextJS, and I have the image upload section nested inside an accordion on the individual item page. Once images have been uploaded, I would like to clear the upload form and close the accordion. It is closing the accordion I am having trouble with.
ImageUploadAccordion.js:
import React, {useRef} from 'react';
import {Accordion} from 'react-bootstrap'
import ImageUpload from './ImageUpload'
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.click();
}
return (
<Accordion ref={accordionRef} defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}
ImageUpload.js:
import React, {useState} from 'react';
import { useRouter } from 'next/router'
export default function ImageUpload({ item, toggle }) {
const router = useRouter()
const [images, setImages] = useState([])
const [imageURLS, setImageURLS] = useState([])
const [tags, setTags] = useState([])
const [theInputKey, setTheInputKey] = useState('')
const uploadImageToClient = (event) => {
if (event.target.files && event.target.files[0]) {
setImages((imageList) => [...imageList, {"index": images.length, "data": event.target.files[0]}]);
setImageURLS((urlList) => [
...urlList,
URL.createObjectURL(event.target.files[0])
]);
}
let randomString = Math.random().toString(36);
setTheInputKey(randomString)
};
const uploadTagToClient = (e) => {
if (event.target.value) {
const name = e.target.getAttribute("name")
// const i = event.target.value;
// document.getElementById("image-upload")
setTags((tagList) => [...tagList, {"name": name, "tag": e.target.value}]);
}
};
const removeImage = (name) => {
// debug
alert(`Trying to remove image index ${name}`)
let newImages = []
let newTags = []
setImages(images.filter(image => image.data.name !== name));
setTags(tags.filter(tag => tag.name !== name));
}
const uploadToServer = async (e) => {
const body = new FormData()
images.map((file, index) => {
body.append(`file${index}`, file.data);
});
// Use the filenames as keys then we can retrieve server side once we have the images
tags.map((tag, index) => {
body.append(tag.name, tag.tag)
})
const response = await fetch("/api/file", {
method: "POST",
"Content-Type": "multipart/form-data",
body
})
var message = await response.json();
alert(message['message'])
setImages([])
setTags([])
toggle()
};
const openImageUploadDialogue = () =>{
document.getElementById("image-upload").click()
}
return (
<div className="container">
<input style={{display:'none'}} accept="image/*" id="image-upload" type="file" key={theInputKey || '' } className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
<button className="btn btn-outline-success-inverse" onClick={openImageUploadDialogue} >
Add Image
</button>
<hr className = "text-pink"/>
<div className="row">
<div className="col d-flex flex-wrap">
{images.map((file, index) => {
return (
<div className="div p-1" key={file.data.name}>
<p className="text-pink">{file.data.name}</p>
<p>Tag</p>
<input type="text" name={file.data.name} id={`${file.data.name}`} onChange={uploadTagToClient} />
<img src={imageURLS[index]} height="200" width="150" />
<div className="btn btn-outline-success-inverse" onClick={ () =>removeImage(file.data.name)}>Remove Image</div>
</div>
);
})}
</div>
<button
className="btn btn-outline-success-inverse"
type="submit"
onClick={uploadToServer}
>
Upload Images
</button>
</div>
</div>
);
}
I tried by creating a reference to the accordion using useRef, and a function which uses this reference to activate the click event, which I passed to the ImageUpload component, according to another answer to a similar question, but it doesn't seem to work and I'm unsure as to why.
Any help always appreciated :-)
I believe you have the wrong target as the ref, update it to target the button that is automatically generated to wrap the header content.
<h2 class="accordion-header"><button type="button" aria-expanded="true" class="accordion-button"><span class="btn btn-outline-success">Upload Images</span></button></h2>
Rudimentary example:
export default function ImageUploadAccordion({ item }) {
const accordionRef = useRef(null);
const toggleAccordion = () => {
accordionRef.current.querySelector('button').click();
}
return (
<Accordion defaultActiveKey="0">
<Accordion.Item eventKey="1">
<Accordion.Header ref={accordionRef}>
<span className="btn btn-outline-success">Upload Images</span>
</Accordion.Header>
<Accordion.Body>
<ImageUpload
toggle={toggleAccordion}
item={item}
/>
</Accordion.Body>
</Accordion.Item>
</Accordion>
)
}

Categories

Resources