ReactJS doesn't display content from array - javascript

I fetch data from Firebase and then push that data to array with components. I am trying to display that by using the following code but React displays nothing, also there are no errors in console and during compiling.
import React, { useState ,useEffect } from "react";
import './App.css';
import db from "./firebase";
import { doc, getDocs, getDoc, collection, query, orderBy, limit, where } from "firebase/firestore";
function App() {
useEffect(() => {
getdata();
}, []);
const names = []
async function getdata() {
getDocs(collection(db,'questions')).then((snapshot) => {
snapshot.forEach(doc => {
names.push(<p className="question" >{doc.id}</p>)
Object.values(doc.data()).forEach( test => {
names.push(<p className="answer" >{test}</p>)
});
});
})
}
return (
<div className="App">
{names}
</div>
)
}
export default App;

You need to introduce state into your app.
Put your items into state, and then render JSX from your state
import { collection, getDocs } from 'firebase/firestore';
import React, { useEffect, useState } from 'react';
import './App.css';
import db from './firebase';
function App() {
const [state, setState] = useState([]);
async function getdata() {
const snapshot = await getDocs(collection(db, 'questions'));
const items = [];
snapshot.forEach((doc) => {
items.push({
question: doc.id,
answers: Object.values(doc.data()),
});
});
setState(items);
}
useEffect(() => {
getdata();
}, []);
return (
<div className="App">
{state.map((item) => (
<React.Fragment key={item.question}>
<p className="question">{item.question}</p>
{item.answers.map((answer) => (
<p className="answer" key={answer}>
{answer}
</p>
))}
</React.Fragment>
))}
</div>
);
}
export default App;

Related

Why do I get undefined when mapping and passing through a prop (ReactJS)

I can get the data out correctly and see it in the console as I want it to display but mapping and getting it through is the issue I seem to fail on..
This is my code
import React, {useState, useEffect} from 'react'
import axios from 'axios';
import server from '../backend/server.jsx';
import Home from './Home';
function ListImage() {
const [api, setApi] = useState('');
const getAllData = () => {
axios
.get(`${server}/images`)
.then((response) => {
console.log(response.data);
setApi(response.data);
})
.catch((error) => {
console.log(error);
});
}
useEffect(() => {
getAllData();
},[]);
return (
<>
{api ?
api.map((item) => {
console.log(item);
return <Home data={item.data} />;
}): <p>Not found</p>}
</>
)
}
export default ListImage
And then passing to this where i only get undefined in console
import React from 'react'
function Home({ data }) {
console.log(data)
return (
<>
<p>{data}</p>
</>
)
}
export default Home
Try with this. I think your map function gets called when the initial render. Make sure the response. data should return an array.
ps: You render your home component in an array. That's wrong. Pass the data to the component and map your array on the home component.
import React, {useState, useEffect} from 'react'
import axios from 'axios';
import server from '../backend/server.jsx';
import Home from './Home';
function ListImage() {
const [data, setData] = useState(null);
const getAllData = () => {
axios
.get(`${server}/images`)
.then((response) => {
console.log(response.data);
setData(response.data);
})
.catch((error) => {
console.log(error);
});
}
useEffect(() => {
getAllData();
},[]);
return (
<>
{api ? <Home data={data} /> : <p>Loading...</p>}
</>
)
}
export default ListImage

Value of state imported from custom hook not showing

I created a custom hook, Custom.js:
import React, {useState, useEffect} from 'react';
import Clarifai from 'clarifai';
const app = new Clarifai.App({
apiKey: 'XXXXXXXXXXXXXX'
})
const Custom = () => {
const [input, setInput] = useState('');
const [imgUrl, setImgUrl] = useState('');
function onInputChange (text) {
setInput(text);
}
useEffect(()=>{
setImgUrl(input)
}, [input])
function onSubmit () {
console.log('submitted');
console.log(imgUrl)
app.models.predict(Clarifai.COLOR_MODEL, "https://www.takemefishing.org/getmedia/bde1c54e-3a5f-4aa3-af1f-f2b99cd6f38d/best-fishing-times-facebook.jpg?width=1200&height=630&ext=.jpg").then(
function(response) {
console.log(response);
},
function(err) {
// there was an error
}
);
}
return {input, imgUrl, onInputChange, onSubmit}
}
export default Custom;
I imported this custom hook into 2 of my other components, FaceRecognition.js and InputForm.js.
FaceRecognition.js:
import React from 'react';
import Custom from '../Custom';
const FaceRecognition = () => {
const { imgUrl } = Custom();
function yes (){
return console.log(imgUrl)
}
yes()
return (
<div>
<h1 className='white'>The url is {ImgUrl} </h1>
<img width={'50%'} alt=''src={imgUrl}/>
</div>
);
}
export default FaceRecognition;
ImportForm.js:
import React, {useState} from 'react';
import './InputForm.css'
import Custom from '../Custom';
const InputForm = () => {
const { onInputChange, onSubmit } = Custom();
return (
<>
<p className='txt f3'>Enter image link address</p>
<div className='center flex w-70'>
<input type='text' className='w-80 pa1' onChange={(e)=>onInputChange(e.target.value)}/>
<button className='w-20 pa1 pointer' onClick={onSubmit}>Detect</button>
</div>
</>
);
}
export default InputForm;
The functions onSubmit and onImputChange work as expected for InputForm.js and the value of imgUrl logs on the console when function onSubmit runs, as expected. But the imgUrl state which is a string fails to show up between the h1 tags <h1 className='white'>The url is {imgUrl} boy</h1> from my FaceRecognition.js snippet above, and it also doesn't work as the src of the image <img width={'50%'} alt=''src={imgUrl}/> below the h1 tag. This is my problem.
Issue
React hooks don't magically share state. You've two separate instances of this Custom function, each with their own useState hook. I say "function" because you've also mis-named your hook. All React hooks should be named with a "use-" prefix so React can identify it and apply the Rules of Hooks against it.
Solution
If you want separate instances of your useCustom hook to share state then the state needs to be lifted to a common component to be shared. For this you should use a React Context.
Example:
import React, { createContext, useContext, useState, useEffect } from 'react';
import Clarifai from 'clarifai';
const app = new Clarifai.App({
apiKey: 'XXXXXXXXX'
});
const CustomContext = createContext({
input: '',
imgUrl: '',
onInputChange: () => {},
onSubmit: () => {}
});
const useCustom = () => useContext(CustomContext);
const CustomProvider = ({ children }) => {
const [input, setInput] = useState('');
const [imgUrl, setImgUrl] = useState('');
function onInputChange (text) {
setInput(text);
}
useEffect(()=>{
setImgUrl(input);
}, [input]);
function onSubmit () {
console.log('submitted');
console.log(imgUrl);
app.models.predict(
Clarifai.COLOR_MODEL,
"https://www.takemefishing.org/getmedia/bde1c54e-3a5f-4aa3-af1f-f2b99cd6f38d/best-fishing-times-facebook.jpg?width=1200&height=630&ext=.jpg"
).then(
function(response) {
console.log(response);
},
function(err) {
// there was an error
}
);
}
return (
<CustomContext.Provider value={{ input, imgUrl, onInputChange, onSubmit }}>
{children}
</CustomContext.Provider>
);
}
export {
CustomContext,
useCustom
};
export default CustomProvider;
Usage:
Wrap your app with your CustomProvider component.
import CustomProvider from '../path/to/CustomProvider';
...
return (
<CustomProvider>
<App />
</CustomProvider>
);
Import and use the useCustom hook in consumers.
import React from 'react';
import { useCustom } from '../path/to/CustomProvider';
const FaceRecognition = () => {
const { imgUrl } = useCustom();
useEffect(() => {
console.log(imgUrl);
});
return (
<div>
<h1 className='white'>The url is {ImgUrl}</h1>
<img width={'50%'} alt='' src={imgUrl}/>
</div>
);
}
export default FaceRecognition;
...
import React, {useState} from 'react';
import './InputForm.css'
import { useCustom } from '../path/to/CustomProvider';
const InputForm = () => {
const { onInputChange, onSubmit } = useCustom();
return (
<>
<p className='txt f3'>Enter image link address</p>
<div className='center flex w-70'>
<input
type='text'
className='w-80 pa1'
onChange={(e) => onInputChange(e.target.value)}
/>
<button
className='w-20 pa1 pointer'
onClick={onSubmit}
>
Detect
</button>
</div>
</>
);
}
export default InputForm;
try to put your return statement inside the .then of the predict

Can't get data from firestore by Id on my ProductDetail page

I've created a dynamic routing for a ProductDetail page (each Restaurant has its own details that should be shown on this page). the routing is working but I don't get any data and I can't figure out the right way to get data from firestore by the Id of each restaurant.
Update:
Now the product details are rendering in the console but the the problem still how to return them in my detailpage
Update !! : ProductDetail.js
import { useParams, useRouteMatch } from "react-router-dom";
import { firestore } from "../../../../../fire";
function ProductDetail() {
const { productId }= useParams();
const [product, setProduct] = useState();
useEffect( () => {
firestore
.collection("Restaurants")
.doc(productId).get()
.then( doc => {
console.log(doc.data())
setProduct(doc.data());
})
}, () => {
}
);
return (
<div className="col-12">
<div className="card">
<h1>{productId.name_restaurant} </h1>
<p>Price:${productId.Currency}</p>
<p>{productId.email} </p>
</div>
</div>
);
}
export default ProductDetail;
this my console : all details of the restaurant are returned
Still cannot return details on my page
import React, {useEffect, useState} from "react";
import { useRouteMatch } from "react-router-dom";
function ProductDetail() {
const { params: {productId} }= useRouteMatch("/react/ecom-product-detail/:productId");
const [product, setProduct] = useState(null);
useEffect(() => {
firestore
.collection("Restaurants")
.doc(productId)
.get()
.then((snap) => setProduct(snap.data()));
}, []);
return (
<div className="col-12">
<div className="card">
<h1>{product.name_restaurant}</h1>
<p>Price:${product.Currency}</p>
<p>{product.email} </p>
</div>
</div>
);
}
export default ProductDetail;

Sibling component not re-rerendering on state change (using useEffect, useState and Context)

In my Main.js I create a first global state with a username and a list of users I'm following.
Then, both the Wall component and FollowingSidebar render the list of follows and their messages (plus the messages of the main user).
So far so good. But in a nested component inside FollowingSidebar called FollowingUser I have an onClick to remove a user. My understanding is that, because I change the state, useEffect would take care of the Wall component to re-render it, but nothing happens... I've checked several examples online but nothing has helped my use case so far.
Needless to say I'm not overly experienced with React and Hooks are a bit complex.
The code here:
Main.js:
import React, { useEffect, useState } from "react";
import ReactDom from "react-dom";
import db from "./firebase.js";
// Components
import Header from "./components/Header";
import FollowingSidebar from "./components/FollowingSidebar";
import SearchUsers from "./components/SearchUsers";
import NewMessageTextarea from "./components/NewMessageTextarea";
import Wall from "./components/Wall";
// Context
import StateContext from "./StateContext";
function Main() {
const [mainUser] = useState("uid_MainUser");
const [follows, setFollows] = useState([]);
const setInitialFollows = async () => {
let tempFollows = [mainUser];
const user = await db.collection("users").doc(mainUser).get();
user.data().following.forEach(follow => {
tempFollows.push(follow);
});
setFollows(tempFollows);
};
useEffect(() => {
setInitialFollows();
}, []);
const globalValues = {
mainUserId: mainUser,
followingUsers: follows
};
return (
<StateContext.Provider value={globalValues}>
<Header />
<FollowingSidebar />
<SearchUsers />
<NewMessageTextarea />
<Wall />
</StateContext.Provider>
);
}
ReactDom.render(<Main />, document.getElementById("app"));
if (module.hot) {
module.hot.accept();
}
FollowingSidebar component:
import React, { useState, useEffect, useContext } from "react";
import db from "../firebase.js";
import StateContext from "../StateContext";
import FollowingUser from "./FollowingUser";
export default function FollowingSidebar() {
const { followingUsers } = useContext(StateContext);
const [users, setUsers] = useState(followingUsers);
useEffect(() => {
const readyToRender = Object.values(followingUsers).length > 0;
if (readyToRender) {
db.collection("users")
.where("uid", "in", followingUsers)
.get()
.then(users => {
setUsers(users.docs.map(user => user.data()));
});
}
}, [followingUsers]);
return (
<section id="following">
<div className="window">
<h1 className="window__title">People you follow</h1>
<div className="window__content">
{users.map((user, index) => (
<FollowingUser avatar={user.avatar} username={user.username} uid={user.uid} key={index} />
))}
</div>
</div>
</section>
);
}
FollowingUser component:
import React, { useState, useContext } from "react";
import db from "../firebase.js";
import firebase from "firebase";
import StateContext from "../StateContext";
export default function FollowingUser({ avatar, username, uid }) {
const { mainUserId, followingUsers } = useContext(StateContext);
const [follows, setFollows] = useState(followingUsers);
const removeFollow = e => {
const userElement = e.parentElement;
const userToUnfollow = userElement.getAttribute("data-uid");
db.collection("users")
.doc(mainUserId)
.update({
following: firebase.firestore.FieldValue.arrayRemove(userToUnfollow)
})
.then(() => {
const newFollows = follows.filter(follow => follow !== userToUnfollow);
setFollows(newFollows);
});
userElement.remove();
};
return (
<article data-uid={uid} className="following-user">
<figure className="following-user__avatar">
<img src={avatar} alt="Profile picture" />
</figure>
<h2 className="following-user__username">{username}</h2>
<button>View messages</button>
{uid == mainUserId ? "" : <button onClick={e => removeFollow(e.target)}>Unfollow</button>}
</article>
);
}
Wall component:
import React, { useState, useEffect, useContext } from "react";
import db from "../firebase.js";
import Post from "./Post";
import StateContext from "../StateContext";
export default function Wall() {
const { followingUsers } = useContext(StateContext);
const [posts, setPosts] = useState([]);
useEffect(() => {
console.log(followingUsers);
const readyToRender = Object.values(followingUsers).length > 0;
if (readyToRender) {
db.collection("posts")
.where("user_id", "in", followingUsers)
.orderBy("timestamp", "desc")
.get()
.then(posts => setPosts(posts.docs.map(post => post.data())));
}
}, [followingUsers]);
return (
<section id="wall">
<div className="window">
<h1 className="window__title">Latest messages</h1>
<div className="window__content">
{posts.map((post, index) => (
<Post avatar={post.user_avatar} username={post.username} uid={post.user_id} body={post.body} timestamp={post.timestamp.toDate().toDateString()} key={index} />
))}
</div>
</div>
</section>
);
}
StateContext.js:
import { createContext } from "react";
const StateContext = createContext();
export default StateContext;
The main issue is the setting of state variables in the Main.js file (This data should actually be part of the Context to handle state globally).
Below code would not update our state globally.
const globalValues = {
mainUserId: mainUser,
followingUsers: follows
};
We have to write state in a way that it get's modified on the Global Context level.
So within your Main.js set state like below:
const [globalValues, setGlobalValues] = useState({
mainUserId: "uid_MainUser",
followingUsers: []
});
Also add all your event handlers in the Context Level in Main.js only to avoid prop-drilling and for better working.
CODESAND BOX DEMO: https://codesandbox.io/s/context-api-and-rendereing-issue-uducc
Code Snippet Demo:
import React, { useEffect, useState } from "react";
import FollowingSidebar from "./FollowingSidebar";
import StateContext from "./StateContext";
const url = "https://jsonplaceholder.typicode.com/users";
function App() {
const [globalValues, setGlobalValues] = useState({
mainUserId: "uid_MainUser",
followingUsers: []
});
const getUsers = async (url) => {
const response = await fetch(url);
const data = await response.json();
setGlobalValues({
...globalValues,
followingUsers: data
});
};
// Acts similar to componentDidMount now :) Called only initially
useEffect(() => {
getUsers();
}, []);
const handleClick = (id) => {
console.log(id);
const updatedFollowingUsers = globalValues.followingUsers.filter(
(user) => user.id !== id
);
setGlobalValues({
...globalValues,
followingUsers: updatedFollowingUsers
});
};
return (
<StateContext.Provider value={{ globalValues, handleClick }}>
<FollowingSidebar />
</StateContext.Provider>
);
}
export default App;

useContext give error Cannot read property '...' of undefined

I need help with this issue, my app component as in the image below. I want to store track object inselectedTrack in the state using useState when I click on the view details button. Then use it to display track details in instead of making another fetch from API to get tack details, but when I use useContext inside give me this error TypeError: Cannot read property 'selectedTrack' of undefined.
React Components
import React from 'react';
import Header from './Header';
import Search from '../tracks/Search';
import Tracks from '../tracks/Tracks';
import Footer from './Footer';
import TrackContextProvider from '../../contexts/TrackContext';
const Main = () => {
return (
<div>
<TrackContextProvider>
<Header />
<Search />
<Tracks />
<Footer />
</TrackContextProvider>
</div>
);
};
export default Main;
TrackContext.js
import React, { createContext, useState, useEffect } from 'react';
export const TrackContext = createContext();
const TrackContextProvider = props => {
const [tracks, setTracks] = useState([]);
const [selectedTrack, setSelectedTrack] = useState([{}]);
const API_KEY = process.env.REACT_APP_MUSICXMATCH_KEY;
useEffect(() => {
fetch(
`https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?chart_name=top&page=1&page_size=10&country=fr&f_has_lyrics=1&apikey=${API_KEY}`
)
.then(response => response.json())
.then(data => setTracks(data.message.body.track_list))
.catch(err => console.log(err));
// to disable the warning rule of missing dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// state for heading
const [heading, setHeading] = useState(['Top 10 Tracks']);
return (
<TrackContext.Provider value={{ tracks, heading, selectedTrack, setSelectedTrack }}>
{props.children}
</TrackContext.Provider>
);
};
export default TrackContextProvider;
import React, { Fragment, useContext } from 'react';
import { Link } from 'react-router-dom';
import { TrackContext } from '../../contexts/TrackContext';
const TrackDetails = () => {
const { selectedTrack } = useContext(TrackContext);
console.log(selectedTrack);
return (
<Fragment>
<Link to="/">
<button>Go Back</button>
</Link>
<div>
{selectedTrack === undefined ? (
<p>loading ...</p>
) : (
<h3>
{selectedTrack.track.track_name} by {selectedTrack.track.artist_name}
</h3>
)}
<p>lyrics.............</p>
<div>Album Id: </div>)
</div>
</Fragment>
);
};
export default TrackDetails;
import React, { useState, useContext, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { TrackContext } from '../../contexts/TrackContext';
const Track = ({ trackInfo }) => {
const { selectedTrack, setSelectedTrack } = useContext(TrackContext);
const handleClick = e => {
setSelectedTrack(trackInfo);
};
console.log(selectedTrack);
return (
<li>
<div>{trackInfo.track.artist_name}</div>
<div>Track: {trackInfo.track.track_name}</div>
<div>Album:{trackInfo.track.album_name}</div>
<div>Rating:{trackInfo.track.track_rating}</div>
<Link to={{ pathname: `/trackdetails/${trackInfo.track.track_id}`, param1: selectedTrack }}>
<button onClick={handleClick}>> View Lyric</button>
</Link>
</li>
);
};
export default Track;
UPDATE: adding Tracks component
import React, { useContext, Fragment } from 'react';
import Track from './Track';
import { TrackContext } from '../../contexts/TrackContext';
const Tracks = () => {
const { heading, tracks } = useContext(TrackContext);
const tracksList = tracks.map(trackInfo => {
return <Track trackInfo={trackInfo} key={trackInfo.track.track_id} />;
});
return (
<Fragment>
<p>{heading}</p>
{tracks.length ? <ul>{tracksList}</ul> : <p>loading...</p>}
</Fragment>
);
};
export default Tracks;
I think the issue here is that since the selectedTrack is loaded asynchronously, when it is accessed from the context, it is undefined (you can get around the TrackContext being undefined by passing in a default value in the createContext call). Since the selectedTrack variable is populated anychronously, you should store it in a Ref with useRef hook, and return that ref as part of the context value. That way you would get the latest value of selectedTrack from any consumer of that context.
const selectedTracks = useRef([]);
useEffect(() => {
fetch(
`https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?chart_name=top&page=1&page_size=10&country=fr&f_has_lyrics=1&apikey=${API_KEY}`
)
.then(response => response.json())
.then(data => {
selectedTrack.current = data.message.body.track_list;
})
.catch(err => console.log(err));
// to disable the warning rule of missing dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Categories