Why is react skeleton not rendering? - reactjs

I have a component Recommended that makes a service call to firebase and renders the returned data. During the loading delay at the database call, I want to render a react skeleton, as follows:
Recommended.js
import { useState, useEffect } from "react";
import Skeleton from "react-loading-skeleton";
import { getVenues } from "../services/firebase";
import VenueCard from "./VenueCard";
const Reccomended = () => {
const [venues, setVenues] = useState([]);
useEffect(() => {
async function getAllVenues() {
const response = await getVenues();
await setVenues(response);
}
getAllVenues();
}, []);
venues[0] ? console.log(true) : console.log(false)
return (
<div>
{!venues[0] ? (
<>
<Skeleton />
</>
) : (
<>
<p className="recommended">Recommended for Josh</p>
<VenueCard venues={venues} />
</>
)}
</div>
);
};
export default Reccomended;
However, the skeleton is not rending during loading. The returning data is saved to the state variable venues, and I'm using the 'truthiness' as a conditional for the render. I tested this by logging the following:
venues[0] ? console.log(true) : console.log(false)
In the browser it initially logged false, followed quickly by true
So given this, I don't understand why the skeleton isn't loading - any suggestions?
I've also passed parameters into <Skeleton/> which didn't change anything.

You must include the CSS styles, or you won't see anything. Just add
import "react-loading-skeleton/dist/skeleton.css";
with the rest of the imports.
This is documented in the package readme in the react-loading-skeleton basic Usage section

Related

loading components twice, probably because of useEffect wrong set-up

I have built a ToDo React App (https://codesandbox.io/s/distracted-easley-zjdrkv) that does the following:
User write down an item in the input bar
User hit "enter"
Item is saved into the list below (local storage, will update later)
There is some logic to parse the text and identify tags (basically if the text goes "#tom:buy milk" --> tag=tom, text=buy milk)
The problem I am facing are:
useEffect runs twice at load, and I don't understand why
After the first item gets saved, if I try saving a second item, the app crashes. Not sure why, but I feel it has to do with the point above...and maybe the event listener "onKeyDown"
App
import { useState, useEffect } from 'react'
import './assets/style.css';
import data from '../data/data.json'
import InputBar from "./components/InputBar/InputBar"
import NavBar from "./components/NavBar/NavBar"
import TabItem from "./components/Tab/TabItem"
function App() {
const [dataLoaded, setDataLoaded] = useState(
() => JSON.parse(localStorage.getItem("toDos")) || data
)
useEffect(() => {
localStorage.setItem("toDos", JSON.stringify(dataLoaded))
console.log('update')
}, [dataLoaded])
function deleteItem(id){
console.log(id)
setDataLoaded(oldData=>{
return {
...oldData,
"items":oldData.items.filter(el => el.id !== id)
}
})
}
return (
<div className='container'>
<NavBar/>
<InputBar
setNewList = {setDataLoaded}
/>
{
//Items
dataLoaded.items.map(el=>{
console.log(el)
return <TabItem item={el} key={el.id} delete={deleteItem}/>
})
}
</div>
)
}
export default App
InputBar
import { useState, useEffect } from 'react'
import { nanoid } from 'nanoid'
import '../../assets/style.css';
export default function InputBar(props){
const timeElapsed = Date.now();
const today = new Date(timeElapsed);
function processInput(s) {
let m = s.match(/^(#.+?:)?(.+)/)
if (m) {
return {
tags: m[1] ? m[1].slice(1, -1).split('#') : ['default'],
text: m[2],
created: today.toDateString(),
id:nanoid()
}
}
}
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(e.target.value), ...oldData.items]
}
}
)
e.target.value=""
}
}
return(
<div className="main-input-div">
<input type="text" onKeyDown={(e) => handleKeyDown(e)}/>
</div>
)
}
Tab
import { useState } from 'react'
import "./tab-item.css"
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTrash } from "#fortawesome/free-solid-svg-icons";
export default function TabItem(props) {
return (
<div className="tab-item">
<div className="tab-item-text">{props.item.text}</div>
<div className="tab-item-actions">
<FontAwesomeIcon icon={faTrash} onClick={()=>props.delete(props.item.id)}/>
</div>
<div className="tab-item-details">
<div className="tab-item-details-tags">
{
props.item.tags.map(el=><div className="tab-item-details-tags-tag">{el}</div>)
}
</div>
</div>
<div className="tab-item-date">{props.item.created}</div>
</div>
)
}
The above answer is almoost correct. I am adding more info to the same concepts.
useEffect running twice:
This is most common ask in recent times. It's because the effect runs twice only in development mode & this behavior is introduced in React 18.0 & above.
The objective is to let the developer see & warn of any bugs that may appear due to a lack of cleanup code when a component unmounts. React is basically trying to show you the complete component mounting-unmounting cycle. Note that this behavior is not applicable in the production environment.
Please check https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed for a detailed explanation.
App crashes on second time: It's probably because you are trying to update the input value from event.target.value if you want to have control over the input value, your input should be a controlled component meaning, your react code should handle the onChange of input and store it in a state and pass that state as value to the input element & in your onKeyDown handler, reset the value state. That should fix the crash.
export default function InputBar(props){
const [inputVal, setInputVal] = useState("");
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(e.target.value), ...oldData.items]
}
}
)
setInputVal("")
}
}
return(
<div className="main-input-div">
<input
type="text"
value={inputVal}
onChange={(e) => {setInputVal(e.target.value)}}
onKeyDown={(e) => handleKeyDown(e)}
/>
</div>
)
}
Hope this helps. Cheers!
Your app is using strict mode, which in a development mode renders components twice to help detect bugs (https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects).
root.render(
<StrictMode>
<App />
</StrictMode>
);
As for the crash, I think it's happening due to props.setNewList being an asynchronous call and the resetting of e.target.value - something like this seemed to fix it for me:
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
const inputVal = e.target.value;
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(inputVal), ...oldData.items]
}
}
)
e.target.value=""
}
}
I will add, that using document.querySelector to get values isn't typical usage of react, and you might want to look into linking the input's value to a react useState hook.
https://reactjs.org/docs/forms.html#controlled-components

Error Hydration failed with react-speech-recognition

I wanted to try speech recognition in NextJS 13. I installed react-speech-recognition and copy/pasted the provided example. But I am getting Error: Hydration failed because the initial UI does not match what was rendered on the server.
I tried to rollback react to v18.1, removed .next folder but it didn't help. I scrolled NextJS documentation about React Hydration Error, but I don't call windows and don't put div tag in p.
Any ideas what can be the issue?
Code:
'use client'
import 'regenerator-runtime/runtime'
import React from 'react'
import SpeechRecognition, {
useSpeechRecognition,
} from 'react-speech-recognition'
export default function page() {
const {
transcript,
listening,
resetTranscript,
browserSupportsSpeechRecognition,
} = useSpeechRecognition()
if (!browserSupportsSpeechRecognition) {
return <span>Browser doesn't support speech recognition.</span>
}
return (
<div>
<p>Microphone: {listening ? 'on' : 'off'}</p>
<button onClick={SpeechRecognition.startListening}>Start</button>
<button onClick={SpeechRecognition.stopListening}>Stop</button>
<button onClick={resetTranscript}>Reset</button>
<p>{transcript}</p>
</div>
)
}
The hydration error is caused by these lines:
if (!browserSupportsSpeechRecognition) {
return <span>Browser doesn't support speech recognition.</span>
}
Because you are using the 'use client' directive, this component behaves as traditional page components on previous Next.js versions (The page is pre-rendered and then sent to the client to be hydrated). The library you are using checks if webkitSpeechRecognition or SpeechRecognition exists in the window object in order to set the browserSupportsSpeechRecognition boolean, but window is not available server-side (it is undefined). The condition above evaluates to true thus creating the mismatch between what was rendered on the server and on the client-side's first render (You can view the page's source and you will notice that the not supported text was rendered on the server).
You can solve the issue using useState and useEffect hooks, taking advantage of the fact that useEffect only runs on the client-side:
'use client'
import React, { useState, useEffect } from 'react'
import 'regenerator-runtime/runtime'
import SpeechRecognition, {
useSpeechRecognition
} from 'react-speech-recognition'
const Page = () => {
const [speechRecognitionSupported, setSpeechRecognitionSupported] =
useState(null) // null or boolean
const {
transcript,
listening,
resetTranscript,
browserSupportsSpeechRecognition
} = useSpeechRecognition()
useEffect(() => {
// sets to true or false after component has been mounted
setSpeechRecognitionSupported(browserSupportsSpeechRecognition)
}, [browserSupportsSpeechRecognition])
if (speechRecognitionSupported === null) return null // return null on first render, can be a loading indicator
if (!speechRecognitionSupported) {
return <span>Browser does not support speech recognition.</span>
}
return (
<div>
<p>Microphone: {listening ? 'on' : 'off'}</p>
<button onClick={SpeechRecognition.startListening}>Start</button>
<button onClick={SpeechRecognition.stopListening}>Stop</button>
<button onClick={resetTranscript}>Reset</button>
<p>{transcript}</p>
</div>
)
}
export default Page
I had the same problem, but I think checking for server rendering (when the window object is undefined) is a little bit less messy solution:
const isServer = typeof window === "undefined";
if (!browserSupportsSpeechRecognition && !isServer) {
return <div>Your browser does not support speech recognition.</div>;
}
Works well!

Hook requires data from another hook, but getting Error: Rendered more hooks than during the previous render

I'm getting Error:
Rendered more hooks than during the previous render.
I found some answers saying I should put all hook calls on the top.
However, in the following code, session prints undefined 3 times while it's loading until it prints the session object the fourth time.
Hence, I added a check for the loading state.
import { signIn, signOut, useSession } from "next-auth/client";
import { request } from "graphql-request";
import useSWR from "swr";
export default function Profile() {
const [session, loading] = useSession();
if (loading) {
return <p className="">loading...</p>;
}
if (!session) {
signIn();
}
const { data: user } = useSWR([USER_QUERY, session.email], (query, email) =>
request("/api/graphql", query, { email })
);
return (
<div className="">
<p className="">{session.user.nickname}</p>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}
However, this is throwing an error.
A workaround for this particular issue is to define a NextAuth callback for session, but I don't think that's the correct way to fix it, since I definitely will need to make other hook calls based on the value in the session object in the future.
How can I fix this?
Because hooks are tracked by array index internally by React, you can't invoke them conditionally. You need the same hook calls in the same order each time a given component renders.
You could move the useSWR and subsequent markup to a separate component to avoid the conditional hook calls.
In the example below I've moved the signIn call to a separate component too, but that's not strictly necessary. If you wanted to leave that inline you could just return null instead.
export default function Profile() {
const [session, loading] = useSession();
if (loading) {
return <p className="">loading...</p>;
}
return !session ? <SignIn /> : <UserStuff />
}
function SignIn () {
signIn();
return null;
}
function UserStuff () {
const { data: user } = useSWR([USER_QUERY, session.email], (query, email) =>
request("/api/graphql", query, { email })
);
return (
<div className="">
<p className="">{session.user.nickname}</p>
<button onClick={() => signOut()}>Sign out</button>
</div>
);
}

Custom Admin component with custom dataprovider

I need custom Admin component to update the list of Resource components dynamically.
The documentation provides an example:
import * as React from 'react';
import { useEffect, useState } from 'react';
import { AdminContext, AdminUI, Resource, ListGuesser, useDataProvider } from 'react-admin';
function App() {
return (
<AdminContext dataProvider={myDataProvider}>
<AsyncResources />
</AdminContext>
);
}
function AsyncResources() {
const [resources, setResources] = useState([]);
const dataProvider = useDataProvider();
useEffect(() => {
// Note that the `getResources` is not provided by react-admin. You have to implement your own custom verb.
dataProvider.getResources().then(r => setResources(r));
}, []);
return (
<AdminUI>
{resources.map(resource => (
<Resource name={resource.name} key={resource.key} list={ListGuesser} />
))}
</AdminUI>
);
}
It is supposed to work with custom dataprovider that has getResources() function.
Ok. But how this function is supposed to look like? Or rather what it should return? Because tried many things and none is flying.
And also. There is a warning about React Hook useEffect missing a dependency: 'dataProvider'. Solutions online aren't usable so far. I don't think my example doesn't work because of that, but it's possible. How to fix that in that case?
So yeah, in short - how to make this provided in documentation example to work?
+++
Edit: In case anyone wondering here is how I apply my dataprovider:
import dataProvider from './dataprovider';
function App() {
return (
<AdminContext dataProvider={dataProvider}>
<AsyncResources />
</AdminContext>
);
}
And here is my get_resources function:
getResources: () => {
const url = `${apiUrl}/get_resources`;
return httpClient(url).then(
({ headers, json }) => ({
data: json["data"],
total: json["total"]
}));
},
Edit 2:
Problem maybe lies within dataprovider, since it fails with error message "TypeError: can't convert undefined to object" while trying to call "isDataProviderOptions" function from here: node_modules/ra-core/esm/dataProvider/getDataProviderCallArguments.js:19.
Or it's calling the wrong dataprovider. Not the custom one I've supplied to the AdminContext component.
Should I add the code of my custom dataprovider?

Creating Toast Message without button pressed in react js

**Hi Everyone am new to react js can anyone please guide me on how to display Toast Message without using button, i want to show error message on page load using Toast Notification can anyone help me or suggest me on how to do it
If it's just a simple, one time toast message, you could use the React Hooks API.
Some pseudocode, not considering animations and styles:
import React, { useEffect, useState } from 'react'
function ToastMessage({ message, duration = 2000 }) {
const [ showToast, setShowToast ] = useState(false)
// the empty array as the second argument
// ensures it is fired only after component mount.
useEffect(() => {
setShowToast(true)
// Hides the message after 2 default seconds (configurable)
setTimeout(() => setShowToast(false), duration)
}, [])
return (
showToast &&
<div>{props.message}</div> || null
)
}
// Then in your page...
// Assuming you have your error coming from somewhere in your props
function Page(props) {
const [ hasError, setHasError ] = useState(props.error !== '')
return (
<>
<Header />
<PageContent />
<Footer />
{ hasError &&
<ToastMessage
message={props.error}
duration={1000} />
}
</>
)
}

Resources