import { getDocs, collection, query, doc, addDoc } from "firebase/firestore/lite";
import { useState } from "react";
import { db } from "../firebaseConfig";
import { useEffect } from "react";
function EndGame(startGame){
const {startGameHandler} = startGame;
const startGameClick = startGameHandler[0];
const time = startGameHandler[1];
const [leaderboard, setLeaderboard] = useState([]);
const [user, setUser] = useState("");
const [username, setUsername] = useState("")
const [isAnonymous, setIsAnonymous] = useState(false);
const loginAnonymously = () =>{
console.log("login hivas ", user)
setUser(username)
setIsAnonymous(true)
}
const setScore= async(timeprop, userprop)=>{
console.log(time, user)
await addDoc(doc(db, "Leaderboard"), {
name: userprop,
time: timeprop,
})
}
async function getLeaderboard(){
const q = query(collection(db, "Leaderboard"));
const chacSnapShot = await getDocs(q);
const char = chacSnapShot.docs.map(doc => doc.data());
setLeaderboard(char)
}
useEffect(()=>{
setScore(time, user)
getLeaderboard()
}, [isAnonymous])
return(
<div className={`endgame-page`}>
{!isAnonymous && (
<div className="endgame-div">
<input
type="text"
placeholder="Enter a username"
value={username}
onChange={e => setUsername(e.target.value)}
/>
<button onClick={loginAnonymously}>Login Anonymously</button>
</div>
)}
{isAnonymous && (
<div className="endgame-div">
<h1 className="endgame-heading">Leaderboard</h1>
<div className="endgame-leaderboard">
{leaderboard.map((data)=>{
return(
<div key={data.name} className="user-container">
<p className="username">{data.name}</p>
<p className="userdata">{data.time}</p>
</div>
)
})}
</div>
<button className="endgame-button" onClick={startGameClick} >Start Game</button>
</div>
)}
</div>
)
}
export default EndGame
So I have this endgame component and when it renders for some reasons the setScore function gets called and i think thats why i get a error of this:
Uncaught (in promise) FirebaseError: Invalid document reference. Document references must have an even number of segments, but Leaderboard has 1.
On line 27. Am i in the wrong here thinking its beacause of setScore gets called on render? If not what the problem/solution?
In firebase i have a Leaderboard collection inside that i want to create docs from users time and name, (each users should have 1 doc)
Method addDoc should have a collection reference not a document reference. You'd only use a document reference if you want to specify a document name into it and it should use setDoc instead, see sample code below:
const setScore= async(timeprop, userprop)=>{
console.log(time, user)
await setDoc(doc(db, "Leaderboard", "<document-name>"), {
name: userprop,
time: timeprop,
})
}
To correct this, check out the sample code below:
const setScore= async(timeprop, userprop)=>{
console.log(time, user)
await addDoc(collection(db, "Leaderboard"), {
name: userprop,
time: timeprop,
})
}
You may want to checkout this documentation for more information.
Related
import { useSelector, useDispatch } from "react-redux";
import { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import { IoMdArrowRoundBack } from "react-icons/io";
import axios from "axios";
import { fetchMovies } from "../../feautures/movies/moviesSlice";
import Rating from "../../components/UI/Rating/Rating";
import request from "../../requests";
import "./SingleMoviePage.scss";
import SimilarMovies from "../../components/SimilarMovies/SimilarMovies";
const SingleMoviePage = ({ match }) => {
const dispatch = useDispatch();
const [movieDetails, setMovieDetails] = useState({});
const [movieCredits, setMovieCredits] = useState({});
const history = useHistory();
console.log("single rendered")
// number month to string
const date = new Date(movieDetails.release_date);
const dateWithMonthName =
date.getFullYear() +
"-" +
date.toLocaleString("en-EN", { month: "long" }) +
"-" +
date.getDay();
/* params */
const movieId = match.params.id;
const page = match.params.page;
const genre = match.params.genre;
/* movies reducer handle */
const moviesStatus = useSelector((state) => state.movies.status);
/* base urls */
const baseImgUrl = "https://image.tmdb.org/t/p/original";
const movieDetailUrl = `https://api.themoviedb.org/3/movie/${movieId}?api_key=c057c067b76238e7a64d3ba8de37076e&language=en-US`;
const movieCastUrl = `https://api.themoviedb.org/3/movie/${movieId}/credits?api_key=c057c067b76238e7a64d3ba8de37076e&language=en-US`;
// go home page
const goHOme = () => {
history.goBack()
};
// fetch movie cast
useEffect(() => {
const fetchMovieCast = async () => {
let response = await axios.get(movieCastUrl);
response = response.data;
setMovieCredits(response);
};
fetchMovieCast();
}, [movieCastUrl]);
// fetch movie details
useEffect(() => {
const fetchMovieDetails = async () => {
let response = await axios.get(movieDetailUrl);
response = response.data;
setMovieDetails(response);
};
fetchMovieDetails();
}, [movieDetailUrl]);
let content;
if (moviesStatus === "loading") {
} else if (moviesStatus === "succeeded") {
content = (
<div
className="single-movie__container"
style={{
backgroundImage: `url(${
movieDetails.backdrop_path
? baseImgUrl + movieDetails.backdrop_path
: baseImgUrl + movieDetails.poster_path
})`,
}}
>
<div className="single-movie__details">
<IoMdArrowRoundBack
className="single-movie__back"
onClick={goHOme}
size={65}
color={"#e50914"}
/>
<h1 className="single-movie__title">{movieDetails.title}</h1>
<div className="single-movie__rate">
<Rating
rating={movieDetails.vote_average}
className="single-movie__stars"
/>
</div>
<p className="single-movie__overview">{movieDetails.overview}</p>
<div className="single-movie__informations single-movie__informations--genres">
<label className="single-movie__informations-heading">Genres</label>
<div className="single-movie__informations-container">
{movieDetails.genres?.map((genre) => {
return <div className="single-movie__info">{genre.name}</div>;
})}
</div>
</div>
<div className="single-movie__informations single-movie__informations--stars">
<label className="single-movie__informations-heading">
Starring
</label>
<div className="single-movie__informations-container">
{movieCredits.cast?.slice(0, 4).map((star) => {
return <div className="single-movie__info">{star.name}</div>;
})}
</div>
</div>
<div className="single-movie__informations single-movie__informations--released">
<label className="single-movie__informations-heading">
Release Date
</label>
<div className="single-movie__informations-container">
<div className="single-movie__info">{dateWithMonthName}</div>
</div>
</div>
<div className="single-movie__informations single-movie__informations--production">
<label className="single-movie__informations-heading">
Production
</label>
<div className="single-movie__informations-container">
{movieDetails.production_countries?.slice(0, 2).map((country) => {
return <div className="single-movie__info">{country.name}</div>;
})}
</div>
</div>
</div>
<SimilarMovies movieId={movieId} />
</div>
);
}
useEffect(() => {
if (genre === "POPULAR") {
dispatch(fetchMovies(request.fetchPopular(page)));
} else if (genre === "NOW PLAYING") {
dispatch(fetchMovies(request.fetchNowPlaying(page)));
} else if (genre === "UP COMING") {
dispatch(fetchMovies(request.fetchUpComing(page)));
}
}, [dispatch, genre, page]);
return <div className="single-movie">{content}</div>;
};
export default SingleMoviePage;
Hi all.When i clicked Card compenent it navigate me to the SingleMoviePage component.But SingleMoviePage component re-render five times.How can i find this issues's source ? And how can i prevent that ? Finally is there any problem to fetch MovieCast and MovieDetails in same useEffect hook ?
github repo : https://github.com/UmutPalabiyik/hope-movie-app
demo : https://hope-movie.web.app/page/1
The first 2 useEffect hooks fetch data separately which then update your local states, which then triggers re-rendering.
If you don't want to re-render after each data fetch (state update), I'd suggest adding a loading state. Set it to true first and return a loading icon as your content. Then after both movieDetails and movieCredits have been populated, set it to false and return the actual content. This should render twice in total.
Have you considered graphql? GraphQL can combine your api calls into one and it also handles loading state and error state.
Whatever solution you have, re-rendering will happen when you are fetching data. Initial render won't have any data and after fetching data it must re-render.
You should use only one useEffect hook your code is running for all three. React will handle the rest itself.
I'm trying to navigate an array of orders stored in each "User". I am able to query and find ones that have orders but I'm not able to display them. I keep getting an error "Cannot read property 'map' of null". Where am I going wrong?
The image below shows how all the orders are stored in "order"
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { firestore } from "../../../FireBase/FireBase";
const OrdersAdmin = (props) => {
const [order, setOrder] = useState(null);
useEffect(() => {
const fetchOrder = async () => {
const doc = await firestore.collection("Users");
const snapshot = await doc.where("orders", "!=", []).get();
if (snapshot.empty) {
console.log("No matching documents.");
return <h1>No Orders</h1>;
}
var ans = [];
snapshot.forEach((doc) => {
console.log(doc.id, "=>", doc.data().orders);
setOrder(doc.data().orders)
});
};
fetchOrder();
}, [props]);
return (
<div className="adminOrders">
<h1>orders</h1>
{console.log(order)}
{order.map((orderItem) => (
<div className="singleOrder" key={orderItem.id}>
<p>{orderItem}</p>
</div>
))}
</div>
);
};
export default OrdersAdmin;
The issue is that the initial value of order is null. null does not have Array.prototype.map, therefore you get the error. Try updating your render to use conditional rendering to only attempt Array.prototype.map when order is truthy and an Array:
{order && order.length > 0 && order.map((orderItem) => (
<div className="singleOrder" key={orderItem.id}>
<p>{orderItem}</p>
</div>
))}
Otherwise you can use a better default value of an empty array for order which would have Array.prototype.map available to execute:
const [order, setOrder] = useState([]);
Hopefully that helps!
I've retrieved a list of categories using an API. Now I want to fetch images from an URL based on the categories. I tried using each category to fetch images from another API, but I'm not sure how to do it.
import React, { useEffect, useState } from 'react';
import './css/Category.css';
function Category() {
useEffect(() => {
fetchData();
getImage();
}, []);
const [categories, setCategories] = useState([]);
const [image, setImage] = useState('');
const fetchData = async () => {
const data = await fetch('https://opentdb.com/api_category.php')
const categories = await data.json();
console.log(categories.trivia_categories)
setCategories(categories.trivia_categories)
}
const getImage = async (name) => {
console.log(name)
const q = name.split(' ').join('+')
const img = await fetch(`https://pixabay.com/api/?key=apikey&q=${q}&image_type=photo`)
const image = await img.json();
console.log(image)
setImage(image.previewURL)
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category">
{category.name}
<img src={getImage(category.name)} /> //do not know what to do here to fetch image of the respective category
</div>
))}
</div>
</div>
)
}
export default Category;
After changes suggested by Noah, I was able to show only one image.
const getImage = async (name) => {
const query = stringMan(name.name)
console.log(query)
const img = await fetch(`https://pixabay.com/api/?key=17160673-fd37d255ded620179ba954ce0&q=${query}&image_type=photo`)
const image = await img.json();
console.log(image)
setImage({ [name.name]: image.hits[0].largeImageURL })
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category" key={category.id}>
{category.name}
<img key={category.id} src={image[category.name]} />
</div>
))}
</div>
</div>
)
There are a couple of changes that you can make here.
One issue that I see is that you have a single image variable, that's being re-used for every single category. So when you map over a list of categories (for example let's say we have categories: [history, science, and math]). The current code will call getImage three times, with history, science, and math as parameters.
However, there is only one state variable that is being written to. Which means the last execution of setImage is the only one that will be preserved.
So, you might want to change image from being the URL of a category image, to an object that has the shape:
{
history: [url],
science: [url],
math: [url]
}
The other change to make is that you are calling the getImage() function directly in the rendered output <img src={getImage(category.name)} />. Instead, this should simply use the value that was assigned to the image state: <img src={image} />.
To actually fetch the image, you can use the useEffect hook (https://reactjs.org/docs/hooks-effect.html) to react to changes to the categories variable. That might look something like:
useEffect(() => {
categories.forEach((c) => getImage(c));
}, [categories]);
The useEffect hook will invoke the function it is given, whenever the dependencies change. This will allow you to trigger the getImage function in response to changes to the categories.
There're lot of improvement that could be done as stated by #noah-callaway above/below but coming straight to the point you need to simply fix the URI creation logic to use encodeURIComponent like below:
import React, { useEffect, useState } from 'react';
function Category() {
useEffect(() => {
fetchData();
getImage();
}, []);
const [categories, setCategories] = useState([]);
const [image, setImage] = useState('');
const fetchData = async () => {
const data = await fetch('https://opentdb.com/api_category.php')
const categories = await data.json();
console.log(categories.trivia_categories)
setCategories(categories.trivia_categories)
}
const getImage = async (name) => {
return encodeURI(`https://pixabay.com/api/?key=apikey&q=${encodeURIComponent(name)}&image_type=photo`)
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category">
{category.name}
<img src={getImage(category.name)} />
</div>
))}
</div>
</div>
)
}
don't have the api key so can't test but it'll give you something like
https://pixabay.com/api/?key=apikey&q=Entertainment%3A%20Comics&image_type=photo
good luck, hope it works.
I am using this:
https://dev.to/gabe_ragland/debouncing-with-react-hooks-jci
But when I add it to my React Project I am getting this issue:
Line 4:20: Parse errors in imported module './BusNew': JSX attributes must only be assigned a non-empty expression (67:17) import/no-named-as-default
Line 4:20: Parse errors in imported module './BusNew': JSX attributes must only be assigned a non-empty expression (67:17) import/no-named-as-default-member
Here is my code:
import React, { Component, useState, useEffect } from 'react';
import useDebounce from './use-debounce';
export class BusNew extends Component {
return (
<div>
<input
placeholder="Search Marvel Comics"
onChange={e => setSearchTerm(e.target.value)}
/>
</div>
{isSearching && <div>Searching ...</div>}
{results.map(result => (
<div
key={result.id
>
<h4>{result.title}</h4>
))}
</div>
);
}
function Search() {
// State and setters for ...
// Search term
const [searchTerm, setSearchTerm] = useState('');
// API search results
const [results, setResults] = useState([]);
// Searching status (whether there is pending API request)
const [isSearching, setIsSearching] = useState(false);
// Debounce search term so that it only gives us latest value ...
// ... if searchTerm has not been updated within last 500ms
// As a result the API call should only fire once user stops typing
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(
() => {
if (debouncedSearchTerm) {
setIsSearching(true);
searchCharacters(debouncedSearchTerm).then(results => {
setIsSearching(false);
// Filter out results with no thumbnail
const filteredResults = results.data.results.filter(
result => result.thumbnail.path.indexOf('image_not_available') === -1
);
setResults(filteredResults);
});
} else {
setResults([]);
}
},
[debouncedSearchTerm] // Only call effect if debounced search term changes
);
}
function searchCharacters(search) {
const apiKey = 'f9dfb1e8d466d36c27850bedd2047687';
return fetch(`https://gateway.marvel.com/v1/public/comics?apikey=${apiKey}&titleStartsWith=${search}`, {
method: 'GET'
}).then(r => r.json());
}
export default BusNew;
Sandbox example:
https://codesandbox.io/s/usedebounce-7byqd
Thank you for any help!
I try to implement the Search function into my management system using React-Hooks and GraphQL-Apollo Client. While the interface is shown successfully, when I press the 'Search' button it came out an error which named:
Invalid hook call. Hooks can only be called inside of the body of a function component.
I'm pretty sure the useQuery is being called inside a function, so I do not understand what will cause this. The other function such as display all users and add new users are working fine.
I have tried a couple of ways to implement the search function and search around online while still couldn't get it solve. This is my first time encounters React-Hooks too.
Here is my current code in the searchUser component
import React from 'react'
import {useQuery} from 'react-apollo-hooks';
import {searchUserQ} from '../queries/queries';
const SearchUserForm = () => {
let name = '';
let content;
return (
<div id="edit-user">
<div className="field">
<label htmlFor="name">Search UserName</label>
<input type="text" id="name" onChange={ (event) => {
name = event.target.value }}/>
<button onClick={(event) => {
event.preventDefault();
const { data, loading, error } = useQuery(searchUserQ, {
variables: { name: name },
suspend: false
});
if (loading) {
content = <p>Loading User...</p>
}
if (error){
console.log(`Error Occur: ${ error }`);
content = <p>Error Occur!</p>
}
content = data.users.map(user => (
<p key={user.id}>Username: {user.name} ID: {user.id}</p>
));
}}>
Submit
</button>
<p>{ content }</p>
</div>
</div>
)
}
export default SearchUserForm;
Can anyone help with this?
One more question is that my data seems to return undefined everytime I execute the query. Is there something wrong with my query?
Here are the query:
const searchUserQ = gql`
query User($name: String!){
user(name: $name){
id
name
email
}
}
`;
Thanks and appreciate on the help!
According to the Rules of Hooks:
Don’t call Hooks from regular JavaScript functions. Instead, you can:
✅ Call Hooks from React function components.
✅ Call Hooks from custom Hooks (we’ll learn about them on the next page).
If you need to manually call a query manually ins response to an event, use the Apollo client directly. You can use useApolloClient to get an instance of the client inside your component:
const SearchUserForm = () => {
const client = useApolloClient();
...
return (
...
<button onClick={async (event) => {
try {
const { data } = client.query({
query: searchUserQ,
variables: { name: event.target.value },
});
// Do something with data, like set state
catch (e) {
// Handle errors
}
}} />
You can also still use useQuery, like this:
const SearchUserForm = () => {
const [name, setName] = useState('')
const { data, loading, error } = useQuery(searchUserQ, {
variables: { name },
});
...
return (
...
<button onClick={async (event) => {
setName(event.target.value)
...
}} />
You can use the useLazyQuery method and expose your data object to your entire component.
import {useLazyQuery} from '#apollo/react-hooks';
// - etc. -
const SearchUserForm = () => {
// note: keep the query hook on the top level in the component
const [runQuery, { data, loading, error }] = useLazyQuery(searchUserQ);
// you can now just use the data as you would regularly do:
if (data) {
console.log(data);
}
return (
<div id="edit-user">
<div className="field">
<label htmlFor="name">Search UserName</label>
<input
type="text"
id="name"
onChange={(event) => {name = event.target.value }} />
<button onClick={
(event) => {
event.preventDefault();
// query is executed here
runQuery({
variables: { name }, // note: name = property shorthand
suspend: false
})
}}>
// - etc. -
);
}
As opposed to doing the useQuery, the useLazyQuery method will only be executed on the click.
At the point where you are able to pass the 'name' value as a parameter.
If for example you would use the useQuery instead, and have a parameter that is required in your query (i.e. String!), the useQuery method will provide you with an error. Because on component render it will try to run that query directly without the required parameter because at that period of time it's not available yet.
I found problem to my second answer, should just wrap an if-statement before executing it, here is the complete code:
import React, {useState} from 'react'
import {useQuery} from 'react-apollo-hooks';
import {searchUserQ} from '../queries/queries';
const SearchUserForm = () => {
const [name, setName] = useState('');
const { data, error, loading } = useQuery(searchUserQ, {
variables: { name }
});
let content;
let sName;
if (data.user !== undefined && data.user !== null) {
if (loading) { content = 'Loading User...' }
if (error) { content = `Error Occur: ${error}` }
const user = data.user;
content = `Username: ${ user.name } ID: ${ user.id }`
}
return (
<div id="edit-user">
<div className="field">
<label htmlFor="name">Search UserName</label>
<input type="text" id="name" onChange={(event) => {
sName = event.target.value;
}}/>
<button onClick={(event) => {
setName(sName);
}} value={name}>
Search
</button>
</div>
<div>
<p>{ content }</p>
</div>
</div>
)
};
export default SearchUserForm;