Trying to create a small app as part of a university assignment using React.
The basic assignment is to create a page which has a question and then has one of 5 answers. I have the answers now stored in a firestore document.
I have created (the start of) a custom Button component.
So the code I have does contact the firestore and I get the data back. The examples I have tried in uni have been for getting 1 bit of data - not like this. What I'm trying to do is to create an answers "array" which I can then iterate over and create my custom buttons. However, I can't quit figure out how to create the array of answers.
Can anyone give me a hint?
import React, {useEffect, useState} from 'react';
import firebase from 'firebase/compat/app';
import 'firebase/compat/firestore';
import 'firebase/compat/storage';
import Button from '../components/Button';
function AnswerComponent() {
const firestore = firebase.firestore();
//const storage = firebase.storage();
const collectionId = "Balances";
const documentId = "Answers"
const [answers, setAnwsers] = useState([]);
useEffect(() => {
const getFirebase = async () => {
const snapshot = await firestore.collection(collectionId).doc(documentId).get();
const questionData = snapshot.data();
// now we add the answers and correct flag to our answers
const answerArr = [];
Object.keys(questionData).forEach(key => {
answerArr.push(questionData[key]);
setAnwsers(answerArr)
});
};
getFirebase();
},[answers, firestore])
console.log(">>", answers)
return (
<div className="col-12">
<h3 className="text-center">Answer</h3>
<div className="p-3 mb-2 bg-light">
<div className="row">
</div>
{/* {btns} */}
</div>
</div>
)
}
export default AnswerComponent;
Need to push new answers onto the array, this solution saves each answer as an object with a key (preserving the key).
const answerArr = [];
Object.keys(questionData).forEach(key => {
answerArr.push({ [key]: questionData[key] });
setAnswers(newAnswerArr);
});
If you don't need the key you could save a step and use Object.values:
const answerArr = [];
Object.values(questionData).forEach(questionValue => {
answerArr.push(questionValue);
});
setAnwsers(answerArr);
Or with either solution you could reduce instead of setting the anwserArr separately:
const answerArr = Object.values(questionData).reduce((acc, questionValue) => {
acc.push(questionValue)
return acc;
}, []);
setAnswers(answerArr)
It looks like you're trying to setAnswer several times in a row (which will change the answer) when you do this:
Object.keys(questionData).forEach(key => {
console.log(key, questionData[key]);
setAnwsers(key, questionData[key])
});
Instead, I would try to create a singe array, then setAnswers to that array
const answerArray = []
Object.keys(questionData).forEach(key => {
console.log(key, questionData[key]);
answerArray.push(questionData[key]);
});
setAnswers(answerArray)
Turns out either piece code works (thanks to both #chris-bradshaw and #radicalturnip. The issue was I forgot useEffect changes on every change. So useEffect was triggered, getting data, that was triggering a change, which ran useEffect, which got more data and triggered another change, etc etc x infinitity.
Related
I am able to print the blog array in the console and it includes the object. I want to use the object components by mapping through the id as the key but I am not able to get inside the map function. I was able to use the same map structure in my another component of my app but this one is not working. I tried map function in different ways but I am not able to get this one fixed. Can I get some review and advise on my code?
import {colRef} from "./firebase";
import { useParams } from "react-router-dom";
import {doc, onSnapshot} from 'firebase/firestore'
import { useEffect, useState } from "react";
const BlogDetails = () => {
const{id}=useParams();
const[blog,setBlog] = useState(null);
///Getting single document.
useEffect(() => {
const docref= doc(colRef,id);
let document =[];
onSnapshot(docref,(doc) => {
document.push({...doc.data(),id:doc.id})
})
///console.log(document)
if(document)
{
setBlog(document);
}
},[id]);
return (
<div className="blog-details">
{blog && (console.log(blog))}
{blog && (blog.map((blog) => (
<div className="blog-info" key={blog.id}>
{console.log('Inside blog-info')}
{console.log(blog)}
<h2>{blog.title}</h2>
<p>Written by {blog.author}</p>
<div>{blog.body}</div>
<button>delete</button>
</div>
)))}
</div>
);
}
export default BlogDetails;
This code is asynchronouse and it can't work like this:
const docref= doc(colRef,id);
let document =[];
onSnapshot(docref,(doc) => {
document.push({...doc.data(),id:doc.id}) // than this
})
if(document)
{
setBlog(document); // this runs first
}
It should look more like that:
const docref= doc(colRef,id);
onSnapshot(docref,(doc) => {
setBlog({...doc.data(),id:doc.id}))
})
This is a next/react project.
folder structure:
components > Navbar.js
pages > index.js (/ route)(includes Navbar)
> submitCollection.js (/submitCollection)(includes Navbar)
I am trying to have the user submit a specific string as an input and i store it inside the account variable.
Navbar.js
const Navbar = ({}) => {
const [account,setAccount] = useState()
const handleClick = () => {
setAccount(randomNumberThatIHaveGenerated)
}
...
return (
<Link href="/">home</Link>
<Link href="/submitCollection">submit collection</Link>
...
<button onClick={handleClick} >press to set account</button>
...
{account?(<p>{account}</p>):(<p>u need to set an accout</p>)}
)
}
when i visit home using the navbar link, the account is again set to undefineed and i need to press the button again in order to set it. How can i make the string remain set. like persist on the navbar
useState is not persistent, it is bound to its component, in order to make it persist, you have to use localStorage
const [account,_setAccount] = useState();
const setAccount = (val) => {
_setAccount(val);
localStorage.setItem('account', val);
}
useEffect(() => {
const storedAccount = localStorage.getItem('account');
if (storedAccount) _setAccount(storedAccount);
}, [])
const handleClick = () => {
setAccount(randomNumberThatIHaveGenerated)
}
useEffect is called when the component renders, check for stored account and displays it.
And notice how we reimplement setAccount, so that everytime it is called, we update the localStorage.
You can also create a custom hook with this logic, so the component would look cleaner. Or even better, use something like use-state-persist
You can solve this problem using localstorage and useEffect
Adding this piece of code to your work will do the trick
const [account,setAccount] = useState(localStorage.getItem('account') ?localStorage.getItem('account') : null)
useEffect(()=>{
localstorage.setItem(account)
},[account])
For example
const [account,setAccount] = useState(localStorage.getItem('account') ?localStorage.getItem('account') : null)
useEffect(()=>{
localStorage.setItem('account',account)
},[account])
const handleClick = () => {
setAccount(randomNumberThatIHaveGenerated)
}
Hope it helped
I'm newbe in React and since few days I'm really stuck. My goal is to make searcher which after typing e.g. "katarzyna" in input text, will search results in two endpoints: users and repositories sort them by id.
I'm using functional components.
I have a few problems:
GitHub search API returns just global info about e.g. users https://api.github.com/search/users?q=katarzyna&per_page=4&page=1, but I need some detailed info like bio or name, which can be found in https://api.github.com/users/sealionkat, so I think that I should firstly connect with this first url, take "url" from that and secondly connect with target url API to take more informations about that person. But I'm not sure how to do this in React, in another component by props with list of urls and then connect with API in map loop?
GitHub search API does not enable sorting by id by url parameter, so I think that I should firstly get data from the API, merge these two results, maybe by associate array/object with id's as a keys and then sort results by these id?
I don't know how to take whole data in one array/object from multi fetch query with useEffect? I'm using GitHub Api in React. On console.log(data) I'm getting two different console.logs with two different objects, one from first url and second from second url. But after sending results to another component by <UserProfile user={items} I get only one object by props const GetData = (props) => { console.log(props.searchResults);
Also I don't get why console.log(fetchUrls); logs in console 5 times...:
ResultsGetData.js component looks like that:
import { useEffect, useState } from "react";
import GetData from "./GetData";
const GITHUB_URL = process.env.REACT_APP_GITHUB_URL;
const GITHUB_TOKEN = process.env.REACT_APP_GITHUB_TOKEN;
const ResultsGetData = (props) => {
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
const fetchUrls = [
GITHUB_URL + "search/users?q=" + props.q + "&per_page=4&page=1", //remove per page
GITHUB_URL + "search/repositories?q=" + props.q + "&per_page=4&page=1",
];
console.log(fetchUrls);
useEffect(() => {
Promise.all(
//renders twice
fetchUrls.map((url) =>
fetch(url, {
headers: {
Authorization: `token ${GITHUB_TOKEN}`,
},
})
.then((response) => response.json())
.then((data) => {
setItems(data);
setIsLoaded(true);
})
)
);
}, []);
console.log(items);
if(isLoaded){
return <GetData searchResults={items} />;
}
};
export default ResultsGetData;
Here is full repository, I'm newbe in React so any comments are really welcome: https://github.com/kasiorzynka/github-finder/tree/UserProfile
If u have any ideas how to solve my problems, please let mi know :)
I am doing a Netflix Clone and I need to change the Banner in every page refresh. I integrated movie details from TMDb. So I want to choose a random number between 0 and 19. I need that random number to display the movie on banner by the number in an array. This array contains movie details. I used Math.random() function and an error came that response is not defined. How Do I solve This. Please Help Me.
Here Is My Code:
import React, { useState } from 'react'
import './Banner.css'
import {useEffect} from 'react'
import {API_KEY,imageUrl} from '../../Constants/Constants'
import axios from '../../Axios'
function Banner() {
const [movie, setMovie] = useState()
const results = response.data.results
const newIndex = Math.floor(Math.rand() * results.length)
useEffect(() => {
axios.get(`trending/all/week?api_key=${API_KEY}&language=en-US`).then((response)=>{
console.log(response.data);
setMovie(response.data.results[newIndex])
})
}, [])
return (
<div className="banner" style={{backgroundImage:`url(${movie ? imageUrl+movie.backdrop_path : ""})`}}>
<div className="content">
<h1 className="title">{movie ? movie.title : ""}</h1>
<div className="banner-buttons">
<button className="button">Play</button>
<button className="button">My List</button>
</div>
<h1 className="description">{movie ? movie.overview : ""}</h1>
</div>
<div className="fade-bottom"></div>
</div>
)
}
export default Banner
response is a block-scoped variable that you're attempting to access.
const [movie, setMovie] = useState()
useEffect(() => {
axios.get(`trending/all/week?api_key=${API_KEY}&language=en-US`).then((response)=>{
const newIndex = Math.floor(Math.rand() * response.data.results.length + 1)
setMovie(response.data.results[newIndex])
})
}, [])
or
const [movie, setMovie] = useState()
const generateRandomNum = (max) => {
Math.floor(Math.rand() * max + 1)
}
useEffect(() => {
axios.get(`trending/all/week?api_key=${API_KEY}&language=en-US`).then((response)=>{
const newIndex = generateRandomNum(response.data.results)
setMovie(response.data.results[newIndex])
})
}, [])
const results = response.data.results
This line, will obviously throw an error because at this point in the execution, response is not defined. You're only getting it later in the axios.get().then() callback. You'd wanna set results there, but using a variable will not work. You'd want this result to persist across renders, so store the results in state, not a constant. Instead of the above line,
const [results, setResults] = useState(null);
and then later in the .then callback,
setResults(response.data.results);
Give an initial placeholder value for your movie, maybe a loading animation, till you get the response from the axios call.
Also,
setMovie(response.data.results[newIndex])
putting the above in your useEffect will result in setting the movie only once,on mount, because the useEffect hook with an empty dependancy array functions as a ComponentDidMount().
If you want to randomly loop through the movies fetched, consider using a setInterval and generate a new random index with Math.random(), (not Math.rand() as used in the question code snippet), and render the result at that index.
Below is the component I am working on:
//PURPOSE : Component to show some useful information about the current page. like LAST REFRESH TIME OF DATA.
//Props that needs to be passed. - {MessageTitle}
//
import React, { useEffect, useState } from "react";
import "../../css/BubbleInfoComponent.scss";
import ApiHelper from "../../api/ApiHelper";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
const BubbleInfoComponent = (props) => {
let [Info, setInfo] = useState({});
function onClicko() {
console.log("Handling Click");
}
useEffect(() => {
getData();
// eslint-disable-next-line
}, [Info]);
function getData() {
ApiHelper.GetLastRefreshTime(props.uid).then((response) => {
// const infoData = response.data.map(({sysName, lastRefreshTime}) =>{
// return (sysName ? <div key={sysName}>`{sysName}:{lastRefreshTime}`</div>): <div>{lastRefreshTime}</div>;
// })
// setInfo(infoData);
// console.log("infoData:- ")
// console.log(infoData);
});
}
return (
<>
<div id="Bubble__Circle" onClick={onClicko}>
<p>
<FontAwesomeIcon icon="info" color="#30343f" />
</p>
</div>
<div id="Bubble__Content">
<div id="Bubble__Content_Msg_Title">{props.MessageTitle}</div>
{/* <div id="Bubble__Content_Msg_Title">{Info}</div> */}
</div>
</>
);
};
export default BubbleInfoComponent;
There will be two kind of JSON response I will be getting:
{
"SystemA": "07-04-2021 08:00",
"SystemB": "07-04-2021 08:00",
"SystemC": "07-04-2021 08:00"
}
{"responses": "07-04-2021 08:00"}
What I want to implement is 1st type of response I want to set the value of Info in "Key": "Value" format and for 2nd type of response only time should be visible.
I hope I made my point clear. I know I did something wrong while destructuring in line21, this might look silly but being a newbie to JavaScript, I am not able to identify where i am going wrong.
Map function can be run on arrays and not objects. So the first thing which has to be done is to convert object into and array. For that you can use the Object.entries which will give you the key and values for each of the elements in the object.
Reference - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
Based on the response type you either access the time directly or run through a map. Here is the code to start with :
ApiHelper.GetLastRefreshTime(props.uid).then((response) => {
const mapSystemNames = (data) => Object.entries(data).map(([key,value]) => <div key={key}>{key}:{value}</div>)
const infoData = response.data.responses? <div>{response.data.responses}</div> : <div>{mapSystemNames(response.data)}</div>;
// setInfo(infoData);
// console.log("infoData:- ")
// console.log(infoData);
});
Probably there may be some edge case scenarios / type checks needed to make it more robust, but this could get you started in the right direction. Please let me know if you have any queries.