I am trying to make a counter that counts how many NFTs are minted.
I have an async function, getTotalTokensMinted, that gets the total number of tokens minted from a smart contract. How do I render the HTML so it displays the return value, numTokens, from getTotalTokensMinted? Should I use useState hook or local storage?
The code I have below returns a
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render
import React, { useEffect, useState } from 'react';
import { ethers } from 'ethers';
import './styles/App.css';
import myEpicNFT from './utils/MyEpicNFT.json';
const TOTAL_MINT_COUNT = 3;
const CONTRACTADDRESS = "0x6fe91f4814f372Eb40B547114CD75B76DF5f53dC";
const App = () => {
//const [NFTsMinted, NFTcounter] = useState(0);
const getTotalTokensMinted = async () => {
const { ethereum } = window;
let numTokens;
try {
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const connectedContract = new ethers.Contract(contractAddress, contractABI, signer);
numTokens = await connectedContract.getTokenNumber();
return numTokens;
}
} catch (error){
console.log(error);
}
return(
<div> {numTokens}</div>
);
}
return (
<div className="App">
<div className="container">
<p className = "sub-text">
NFT MINTED = {getTotalTokensMinted} / { TOTAL_MINT_COUNT }
</p>
</div>
</div>
</div>
);
}
Your code could quickly get confusing as you develop more functionality. As a rule of thumb, it's good to separate the business logic of your app from the UI.
Try putting your getTotalTokensMinted function in an /api folder or /utils. That function doesn't need to be a component; it simply needs to return a value...
const getTotalTokensMinted = async () => {
const { ethereum } = window;
let numTokens;
try {
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const connectedContract = new ethers.Contract(contractAddress, contractABI, signer);
numTokens = await connectedContract.getTokenNumber();
return numTokens;
}
return null
} catch (error){
console.log(error);
}
And then in the component you actually want to display the minted count you can simply declare a variable count (or whatever) which will store the number of tokens.
const App = () => {
const count = await getTotalTokensMinted();
return (
<div className="App">
<div className="container">
<p className = "sub-text">
NFT MINTED = {count} / { TOTAL_MINT_COUNT }
</p>
</div>
</div>
</div>
);
}
You may want to get the function returned value like this
return (
<div className="App">
<div className="container">
<p className = "sub-text">
NFT MINTED = {getTotalTokensMinted()} / { TOTAL_MINT_COUNT }
</p>
</div>
</div>
</div>
);
If you get an array object error you need to change on how you return the value in the function like this :
return (
<div> {numTokens?.map((each)=>{
<div>{each}</div>
})}
</div>
)
You need to wrap your component in the proper syntax:
example:
<Route exact path="/" element={Home}
Will give you this same error...Need to correct to this:
<Route exact path="/" element={}
From react point of view you want some async operation and its output in DOM. According to provided code snippet you are calling getTotalTokensMinted in the DOM with incorrect syntax. As a solution you need to call async function in useEffect hook with no dependency to render as componentDidMount. You need local state to store result and then use that state in the DOM.
Steps
Follow these steps as a solution
Define local state
By using useState you can create local state. You can use global state depends on the requirement
const [NFTsMinted, setNFTsMinted] = useState(0);
Async function
Set state in async function as follow
const getTotalTokensMinted = async () => {
const { ethereum } = window;
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const connectedContract = new ethers.Contract(contractAddress, contractABI, signer);
const numTokens = await connectedContract.getTokenNumber();
setNFTsMinted(numTokens)
}
}
Call async function
Use useEffect hook to call function
useEffect(() => {
getTotalTokensMinted()
}, [])
Display results in DOM
Use state variable to display counts
<div className="App">
<div className="container">
<p className = "sub-text">
NFT MINTED = {NFTsMinted} / { TOTAL_MINT_COUNT }
</p>
</div>
</div>
Related
I have an array of users, and every user has its data.
I'm trying to extract user number 1 (id=1) and then save it in useState. After that, I want to map it and print it as li.
so far I've had no success.
import logo from './logo.svg';
import './App.css';
import './myCss.css';
import { useState ,useEffect } from 'react';
import axios from 'axios'
import Page2 from './Page2'
function Page3() {
const [users,setusers] = useState([])
const [user,setUser] = useState({})
useEffect(() => {
async function fetchData() {
let resp = await axios.get("https://jsonplaceholder.typicode.com/users");
setusers(resp.data);
}
fetchData();
}, []);
// In this function I try to print the information
const getData = () =>
{
let myUser2 = users.filter(x=>x.id==1)
setUser(myUser2)
{
user.map((item,index)=>
{
return <li key = {index}>{item}</li>
})
}
}
return (
<div className = "styles">
<input type = "button" value = "Get Data for comp 1" onClick = {getData}/>
</div>
);
}
export default Page3;
You cannot print data in getData which is an event function. Instead of that, you should add it to JSX.
Besides that, user is a single user and users are a list of users. filter is not fit for your case in finding a single user, I'd suggest that you use find instead. find will return an object, so you don't need to use map for renderings.
//`user` is an empty object, so we need to make sure it has data inside `user` with `Object.keys`
{user && Object.keys(user) > 0 && <li key={user.name}>{user.name}</li>)}
Full possible modification
function Page3() {
const [users,setusers] = useState([])
const [user,setUser] = useState({})
useEffect(() => {
async function fetchData() {
let resp = await axios.get("https://jsonplaceholder.typicode.com/users");
setusers(resp.data);
}
fetchData();
}, []);
const getData = () =>
{
let myUser2 = users.find(x=>x.id==1)
setUser(myUser2)
}
return (
<div className = "styles">
<input type = "button" value = "Get Data for comp 1" onClick = {getData}/>
<ul>
{user && Object.keys(user) > 0 && <li key={user.name}>{user.name}</li>)}
</ul>
</div>
);
}
export default Page3;
First I have a question why would you want to map through a state that contains only one value. I see no point in this, however in case you will store more information inside userstate you may try something like the following:
const getData = () => {
let myUser2 = users.filter(x=>x.id==1)
setUser(myUser2)
}
const printUser = user.map((item, index) => <li key={item + index}>{item}</li>
return (
<div className = "styles">
<input type = "button" value = "Get Data for comp 1" onClick = {getData}/>
<ul>
{printUser && printUser}
</ul>
</div>
);
}
export default Page3;
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.
// Edit --
This may help:
Project Hatchways
Link to issue -
Issue
As the codes stands right now, the results from the tags still aren't rendering results.
I have a component App.js that renders some children. One of them is 2 search bars. The second search bar TagSearch is supposed to render results from tag creation. What I'm trying to do is pass data from Student where the tags live, and pass them up to the App component in order to inject them into my Fuse instance in order for them to be searched. I have tried to create a function update in App.js and then pass it down to Student.js in order for the tags to update in the parent when a user searches the tags. For some reason, I'm getting a TypeError that states update is not a function.
I put in console logs to track where the tags appear. The tags appear perfectly fine in Student.js, but when I console log them in App.js, the tags just appear as an empty array which tells me they aren't being properly passed up the component tree from Student.js to App.js.
// App.js
import axios from "axios";
import Fuse from "fuse.js";
import Student from "./components/Student";
import Search from "./components/Search";
import TagSearch from "./components/TagSearch";
import "./App.css";
function App() {
const [loading, setLoading] = useState(true);
const [students, setStudents] = useState([]);
const [query, updateQuery] = useState("");
const [tags, setTags] = useState([]);
const [tagQuery, setTagQuery] = useState("");
console.log("tags from app: ", tags);
const getStudents = async () => {
setLoading(true);
try {
const url = `private url for assignment`;
const response = await axios.get(url);
setStudents(response.data.students);
setLoading(false);
} catch (err) {
console.log("Error: ", err);
}
};
const fuse = new Fuse(students, {
keys: ["firstName", "lastName"],
includeMatches: true,
minMatchCharLength: 2,
});
const tagFuse = new Fuse(tags, {
keys: ["text", "id"],
includesMatches: true,
minMatchCharLength: 2,
});
function handleChange(e) {
updateQuery(e.target.value);
}
function handleTags(e) {
setTagQuery(e.target.value);
}
const results = fuse.search(query);
const studentResults = query ? results.map((s) => s.item) : students;
const tagResults = tagFuse.search(tagQuery);
const taggedResults = tagQuery ? tagResults.map((s) => s.item) : tags;
const update = (t) => {
t = tags; // changed this to make sure t is tags from this component's state
setTags(t);
};
useEffect(() => {
getStudents();
}, []);
if (loading) return "Loading ...";
return (
<div className="App">
<main>
<Search query={query} handleChange={handleChange} />
<TagSearch query={tagQuery} handleTags={handleTags} />
{studentResults &&
studentResults.map((s, key) => <Student key={key} students={s} update={update} />)}
{taggedResults &&
taggedResults.map((s, key) => (
<Student key={key} students={s} update={update} />
))}
</main>
</div>
);
}
export default App;
// Student.js
import Collapsible from "../components/Collapsible";
import findAverage from "../helpers/findAverage";
import Styles from "../styles/StudentStyles";
const KeyCodes = {
comma: 188,
enter: 13,
};
const delimiters = [KeyCodes.comma, KeyCodes.enter];
const Student = ({ students, update }) => {
const [isOpened, setIsOpened] = useState(false);
const [tags, setTags] = useState([]);
const collapse = () => {
setIsOpened(!isOpened);
};
const handleDelete = (i) => {
const deleted = tags.filter((tag, index) => index !== i);
setTags(deleted);
};
const handleAddition = (tag, i) => {
setTags([...tags, tag]);
};
useEffect(() => {
update(tags);
}, []);
return (
<Styles>
<div className="student-container">
<img src={students.pic} alt={students.firstName} />
<div className="student-details">
<h1>
{students.firstName} {students.lastName}
</h1>
<p>Email: {students.email}</p>
<p>Company: {students.company}</p>
<p>Skill: {students.skill}</p>
<p>Average: {findAverage(students.grades)}</p>
<Collapsible
students={students}
delimiters={delimiters}
handleDelete={handleDelete}
handleAddition={handleAddition}
isOpened={isOpened}
tags={tags}
/>
</div>
</div>
<button onClick={collapse}>+</button>
</Styles>
);
};
export default Student;
Ciao, try to call update function every time you update tags in Student. Something like this:
const handleDelete = (i) => {
const deleted = tags.filter((tag, index) => index !== i);
setTags(deleted);
update(deleted);
};
const handleAddition = (tag, i) => {
let result = tags;
result.push(tag);
setTags(result);
update(result);
};
In this way, every time you change tags in Student, you will update App state.
An alternative could be use useEffect deps list. In Student, modify useEffect like this:
useEffect(() => {
update(tags);
}, [tags]);
This means that, every time tags will update, useEffect will be triggered and update function will be called.
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 use axios to retrieve data from back.
I do get a result from the back. I can even console.log it. Yet, even I assign it to the recipe variable. It doesn't works. I get a empty array.
Anyone would known why ? I really don't understand.
FRONT
import React, { useEffect,useState } from 'react'
import Header from '../../components/Header'
import axios from 'axios'
export default function OneRecipePage(props) {
const [recipe, setrecipe] = useState([])
useEffect(() => {
const id = props.match.params.id
const getRecipe = async () => {
const url = `http://localhost:8000/user/recipe/${id}`
const result = await axios.get(url)
setrecipe(result.data)
console.log('recipe',recipe)
console.log('from back', result.data);
}
getRecipe()
},[])
return (
<div>
<Header/>
<main class="main">
<div class="heading">
<aside class="recipes-info__category_name">{recipe.name}
</aside>
<aside class="recipes-info__date">{recipe.created_at}
</aside>
<h2 class="heading-secondary heading-secondary--big">{recipe.title}</h2>
<p>by author</p>
</div>
<div class="image-box">
<img class="image" src={recipe.photo}/>
</div>
<div class="recipes-details"></div>
</main>
</div>
)
}
BACK
router.get('/recipe/:id', (req,res) => {
const id = req.params.id
connection.query('SELECT * from ETB.recipes WHERE id = ?', id, (err, results) => {
if (err) {
res.status(500).send('Error retrieving the recipe')
}else {
res.status(200).json(results)
}
})
})
In react, set State is an asynchronous aciton. By the time it executes next line, it is no guaranteed that it has set state.
instread of this
`setrecipe(result.data)
console.log('recipe',recipe)`
you can use useEffect() to detect the change in state.
useEffect(()=>console.log('recipe',recipe),[recipe])