React shows unfinished elements before the page renders completely - reactjs

I am making a posts project with React. Whenever My home page just consists a bunch of posts in cards view. If I click on a certain card, it should navigate me to that post with some details. However, before the page renders completely, I still see some lines or 'unfinished' elements so to speak.
This is what I see before the post and it's description render completely
How can I fix this?
Here is my JS file with posts:
import React, { Component } from 'react'
import { Card } from "react-bootstrap";
import { Link } from "react-router-dom";
import './Posts.css'
export class Posts extends Component {
render() {
return (
<div>
{this.props.posts.map(post => (
<Link to={`/post/${post._id}`} key={post._id}>
<Card key={post._id} className="shadow-sm">
<Card.Img variant="top" src={post.image} />
<Card.Body>
<Card.Title id="cardtitle">{post.title}</Card.Title>
</Card.Body>
</Card>
</Link>
))}
</div>
)
}
}
And here is the Post detail:
import React, { useState, useEffect } from 'react'
import axios from "axios";
import { Media } from "react-bootstrap";
import './Post.css'
function Post({ match }) {
const [post, setPost] = useState({});
useEffect(() => {
const fetchPost = async () => {
const res = await axios.get(`${POST_URL}${match.params.id}`);
setPost(res.data);
}
fetchPost();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const POST_URL = `/api/v1/posts/`;
return (
<Media>
<img className="align-self-center mr-3 postImage" src={post.image} alt={post.title} />
<Media.Body>
<h5 className="postTitle text-center">{post.title}</h5>
<p>{post.description}</p>
</Media.Body>
</Media>
)
}
export default Post

You can create a function inside your component that conditionally render if the data has returned from the API. Something like this:
import React, { useState, useEffect } from 'react'
import axios from "axios";
import { Media } from "react-bootstrap";
import './Post.css'
function Post({ match }) {
const [post, setPost] = useState();
useEffect(() => {
const fetchPost = async () => {
const res = await axios.get(`${POST_URL}${match.params.id}`);
setPost(res.data);
}
fetchPost();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const POST_URL = `/api/v1/posts/`;
const renderPost = () => {
if (post) {
return(
<Media>
<img className="align-self-center mr-3 postImage" src={post.image} alt={post.title} />
<Media.Body>
<h5 className="postTitle text-center">{post.title}</h5>
<p>{post.description}</p>
</Media.Body>
</Media>
);
}
return <h1>Loading</h1>;
}
return (
renderPost();
)
}
export default Post
I recommend you to render a spinner or a placeholder to improve the user experience while the data is loading.

Related

How to store and access data from array [product] from context?

I have a context which contains a fetch() method used to retieve list of products from a server.I made this context so that I could reuse the fetched array values every webpage I might need.But I am unable to do so as it gives me an error in console.
this is the code for context
import React, { createContext, useState, useEffect } from 'react'
export const ProductContext = createContext()
const ProductContextProvider = (props) => {
const [product, setProduct] = useState([]);
const fetchData = () => {
fetch(`http://localhost:8080/product`)
.then((response) => response.json())
.then((actualData) => {
setProduct(actualData)
console.log(product);
})
};
useEffect(() => {
fetchData();
}, [])
return (
<ProductContext.Provider
value={{ product }}>
{props.children}
</ProductContext.Provider>
)
}
export default ProductContextProvider
and this is the error I am getting in console
enter image description here
I have done this too in index.js
enter image description here
and this is one page I want to call the product[]
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { useContext } from 'react'
import ProductContext from '../context/ProductContext';
function Product() {
const { product } = useContext(ProductContext)
console.log(product);
return (
<div className="products-row ">
{
product.map((data, num) => {
return (
<div className="product" key={num}>
<div className="card">
<a target="_blank" href="/design" >
<img src={data.thumbnail} alt={data.name} style={{ width: "100%" }} ></img>
</a>
<h1>{data.name}</h1>
<p className="price">${data.price}</p>
</div>
</div>
)
})
}
</div>
);
}
export default Product;
I believe it's an import issue. You probably meant to use the following:
import { ProductContext } from '../context/ProductContext';
Right now, your ProductContext is actually ProuductContextProvider, which is the default export as per your code.

Displaying audio waveforms in React

So i´m building this webpage which allow users to upload a song, and the displayind that sound as a card on the home-page. Sort of like Soundcloud...
Im just getting to learn React, after coming from html, css and JS. So please understand im new to this all.
I´ve been researched the topic alot, and no one has seemed to work for me.
Ive been trying howler.js, and wavesurfer.js, without any luck of displaying waveforms.
have anyone else tried doing this before? someone who could maybe help out?
import { ErrorResponse } from '#remix-run/router';
import React from 'react'
import wavesurfer from 'wavesurfer.js'
import "./css/audio.css"
import { useRef } from 'react';
export const AudioVisualizer = (props) => {
// the homepage has a function to map through all the objects in the
// database, and in return i get every object. I then get the link from each
// object and pass this link into this function as an ARgument.
let link = props;
const audioRef = useRef();
console.log("here is props: " + link);
try {
var audioTrack = wavesurfer.create({
container: audioRef,
wavecolor: "#eee",
progressColor: "red",
barWidth: 2,
});
audioTrack.load(link);
} catch (ErrorResponse) {
console.error("Something happened..");
return ErrorResponse;
};
return (
<div className='audio' ref={audioRef}>
</div>
)
}
From there I have the actual Home.js page where I want to display the returned from the function above.
the home.js file looks like this:
import React, { useEffect, useState } from 'react';
import '../components/css/home/home.css';
import {collection, getDocs, onSnapshot} from 'firebase/firestore';
import {db} from '../firebase'
import { useNavigate } from 'react-router-dom';
import {ClipLoader} from 'react-spinners';
import {AudioVisualizer} from "../components/audioVisualizer"
const Home = () => {
const [songs, setSongs] = useState([]);
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
useEffect(() => {
setLoading(true);
const retrieveSongs = onSnapshot(
collection(db, "songs"),
(snapshot) => {
let arrayList = [];
snapshot.docs.forEach((doc) => {
arrayList.push({ id: doc.id, ...doc.data() });
});
setSongs(arrayList);
setLoading(false);
},
(error) => {
console.log(error);
}
);
return () => {
retrieveSongs();
};
}, []);
return (
<div className='home_wrapper'>
<>
{loading ?
<ClipLoader color="#36d7b7" />
:
<div className='homepage_container'>
{ songs.map((data) => {
return (
<article key={data.id} className='card'>
<div className='card_content'>
<img className='card_image' src={data.image} />
<div className='song_info'>
<h2>{data.title}</h2>
<h4>{data.artist}</h4>
</div>
<div className='audioplayer'>
{AudioVisualizer(data.audio)}
{/* <ReactAudioPlayer src={data.audio} autoPlay controls/> */}
{/* <Waveform className="audio_file" audio={data.audio}/> */}
</div>
</div>
<div className='card_content_extra'>
<button onClick={() => navigate('/update/${data.id}')}>Edit</button>
<button >Listen</button>
</div>
{/* <div id="waveform"></div>
<button class="btn btn-primary" onclick="wavesurfer.playPause()">
<i class="glyphicon glyphicon-play"></i>Play/Pause
</button> */}
</article>
)
})}
</div>
}
</>
</div>
)
}
export default Home
UPDATE::
So as i described in my comment. When i am mapping through the songs object from my database, the waveform wont display. When i pass a direct link to the component it works. but when im passing my object "audio", and getting the value, , it will not show the waveform. When i try to console.log(data.audio) // it returns undefined.
see for yourself: As you can see from the console.log, it acts weird..
The reference to the DOM element is accessed by the .current property Not the reference object created by React.
You could use the useEffect hook, to load the data.
Then create the AudioVisualizer Component in the JSX react way and pass the link to the wavesurfer.
Also the wavesurfer dom object need to have some size.
Have a look at this mini example:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { useRef, useEffect } from 'react';
import wavesurfer from 'wavesurfer.js'
const AudioVisualizer = (props) => {
const audioRef = useRef();
useEffect(()=>{
if (audioRef.current){
let audioTrack = wavesurfer.create({
container: audioRef.current,
});
audioTrack.load(props.link);
}
})
return <div style={{minWidth: "200px"}} className='audio' ref={audioRef}></div>
}
function App(props) {
return (
<div className='App'>
<AudioVisualizer link={"https://actions.google.com/sounds/v1/science_fiction/creature_distortion_white_noise.ogg"}></AudioVisualizer>
</div>
);
}
ReactDOM.createRoot(
document.querySelector('#root')
).render(<App />)

How to dispatch to Reducer before React renders component?

React newcomer here.
I'm loading Astronomy Picture of the Day in a component using a loading spinner.
I want the page to get data every time I call it from navbar but it's flashing old data before showing the spinner.
How to avoid this behavior? I don't want to use ComponentWillMount because it's deprecated and I'm using functions.
The component code:
import { useEffect, useContext } from 'react'
import { getApod } from '../context/nasa/NasaActions'
import NasaContext from '../context/nasa/NasaContext'
import Spinner from './layout/Spinner'
function Apod() {
const {loading, apod, dispatch} = useContext(NasaContext)
useEffect(() => {
dispatch({type: 'SET_LOADING'})
const getApodData = async() => {
const apodData = await getApod()
dispatch({type: 'SET_APOD', payload: apodData})
}
getApodData()
}, [dispatch])
const {
title,
url,
explanation,
} = apod
if (loading) { return <Spinner /> }
return (
<div>
<h2>{title}</h2>
<img src={url} className='apod' alt='apod'/>
<p>{explanation}</p>
</div>
)
}
export default Apod
Thanks for your time.
Edit: I deleted the repository. It's already answared correctly.
I suggest you another solution to keep your navbar clean.
You can declare an instance variable loaded using the useRef hook. This variable will be initialized to false and set to true as soon as the apod is dispatched to your store.
import { useContext, useRef } from 'react'
function Apod() {
const {apod, dispatch} = useContext(NasaContext)
const loaded = useRef(false);
const {title, url, explanation} = apod
useEffect(() => {
dispatch({type: 'SET_LOADING'})
const loadApod = async() => {
const apodData = await getApod()
loaded.current = true;
dispatch({type: 'SET_APOD', payload: apodData})
}
loadApod()
}, [dispatch])
if (!loaded.current) { return <Spinner /> }
return (
<div>
<h2>{title}</h2>
<img src={url} className='apod' alt='apod'/>
<p>{explanation}</p>
</div>
)
}
export default Apod;
I had an idea, to clean the object in Context using onClick on the navbar button.
Is this the best way? I don't know but it's working as I wanted.
import NasaContext from '../../context/nasa/NasaContext'
import { useContext } from 'react'
import { Link } from 'react-router-dom'
import logo from './assets/logo.png'
function Navbar() {
const {dispatch} = useContext(NasaContext)
const resetApod = () => {
const pathname = window.location.pathname
if ( pathname !== '/' ) {
dispatch({type: 'SET_APOD', payload: {}})
}
}
return (
<div className="navbar">
<div className="navbar-logo">
<img src={logo} alt='Experimentum'/>
</div>
<div className="navbar-menu">
<Link to='/' onClick={resetApod}>APOD </Link>
<Link to='/about'>ABOUT </Link>
</div>
</div>
)
}
export default Navbar

Passing down Props to "Single Page Views" through components

Hey still new to React but I'm grinding my way through it slowly by building my own personal app/platform. I have a quick question of passing down props to single page views. This is my overview page that will pull in all the teams from my database as such:
import React, { useState, useEffect } from 'react';
import firebase from '../../firebase/firebase.utils'
import Button from '../../Components/GeneralComponents/Button.component'
import * as GoIcons from 'react-icons/go';
import TeamList from '../../Components/Teams/TeamList.Component'
function TeamsPage() {
const [teams, setTeams] = useState([]);
const [loading, setLoading] = useState(false);
const ref = firebase.firestore().collection("teams");
function getTeams() {
setLoading(true);
ref.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setTeams(items);
setLoading(false);
console.log(items);
});
}
useEffect(() => {
getTeams();
},[])
if(loading) {
return <h1>Loading...</h1>
}
return (
<div className="content-container">
<h2>Team Page</h2>
<div className="add-section">
<div className="actions">
<Button
className="bd-btn outlined add-team"
><GoIcons.GoGear/>
Add Team
</Button>
</div>
</div>
<TeamList teams={teams} />
</div>
)
}
export default TeamsPage;
This gets passed into my TeamList Component:
import React from 'react';
import { Link } from 'react-router-dom'
import { TeamCard } from './TeamCard.Component';
const TeamList = props => {
return(
<div className='teams-overview'>
{props.teams.map(team => (
<Link to={`/teams/${team.id}`}>
<TeamCard key={team.id} team={team}/>
</Link>
))}
</div>
)
}
export default TeamList;
Which maps through and then list the Team as a card component with a link that is supposed to route to their id and pass through their data.
Now in my single page view of a team I'm struggling to gain access to that prop data:
import React from 'react'
function TeamSinglePage(team) {
return (
<div className="content-container">
<h1>Single Page View</h1>
<p>Welcome, {team.teamName}</p>
</div>
)
}
export default TeamSinglePage;

child/parent problem rendering simple bar chart in react,

"EDITED"
I'm trying to make a very minimalist bar chart.
It doesn't, render. It seems that the child component isn't rendering after the parent component's state has changed.
for example. if I make a change in my code in the MiniChart component, and save the changed through my IDE. the charts will render correctly. but as soon as navigate to another page in my app through my browser and come back to where the charts are, then they won't render.
Any help will be much appreciated.
Child component:
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import {BarChart, Bar} from 'recharts';
const MiniChart = (props) => {
const [apiUrl] = useState("https://api.coingecko.com/api/v3/coins/"+props.id+"/market_chart?vs_currency=usd&days=30&interval=daily");
const [data, setData] = useState([]);
const [msg, setMsg] = useState([]);
const [r, setR] = useState([]);
// fetch data from api
useEffect(() => {
const fetchData = async () => {
if(parseInt(props.rank) < 5){
const result = await axios(apiUrl,);
setData(result.data.prices);
} else {setMsg("TEST : not loaded");}
}
setR(data.map(elem => ({ 'val': elem[1]})));
fetchData();
return()=>{
}
}, [apiUrl, props.rank]);
return (
<div>
<BarChart width={150} height={40} data={r}>
<Bar dataKey='val' fill="green" />
</BarChart>
</div>
)
}
export default MiniChart
Parent component:
import React, { useState} from 'react'
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faStar } from "#fortawesome/free-solid-svg-icons";
import { Link, useLocation } from 'react-router-dom';
import Cookies from 'universal-cookie';
import MiniChart from './MiniChart';
const Crypto = (props) => {
const location = useLocation();
const [starColor, setStarColor] = useState(props.defaultStarCol);
const cookies = new Cookies();
const getFavs = cookies.getAll();
// toggle color, re-render, remove or add to cookies
const handleFavToggle = (e) => {
if(starColor === '#ebc934'){
setStarColor('lightgrey');
cookies.remove(props.id, props.id, { path: '/' });
if(location.pathname === '/favorites'){
function refreshPage() {
window.location.reload(false);
}
refreshPage();
}
} else {
setStarColor('#ebc934');
cookies.set(props.id, props.id, { path: '/' });
//console.log(cookies.getAll());
}
}
return (
<div>
<li>
<div className="lidiv">
{props.id in getFavs? //ADD IF LOGGED IN !
<p className="pml"><FontAwesomeIcon style={{color:'#ebc934'}} onClick={handleFavToggle} className="star" icon={faStar}/></p>
: <p className="pml"><FontAwesomeIcon style={{color:'lightgrey'}} onClick={handleFavToggle} className="star" icon={faStar}/></p>}
<p className="pml">{props.rank}</p>
<img className="iconimg" src={props.img} alt=""/>
<p className="pxl coinName"><Link to="/crypto" style={{display: 'block'}}>{props.coin}</Link></p>
<p className="pml">{props.tag}</p>
<p className="pml4">{props.price}</p>
<p className="pml" style={{color: (props.oneday).charAt(0)==='-' ? 'red': 'green'}}>{props.oneday}%</p>
<p className="pxl daycash" style={{color: (props.oneday).charAt(0)==='-' ? 'red': 'green'}}>{props.onedaycurr} </p>
<p className="pxl-4">{props.mcap}M</p>
<MiniChart className="pxl" id={props.id} rank={props.rank}></MiniChart>
</div>
</li>
</div>
)
}
export default Crypto;

Resources