I finally got a function to compile, but when I call it from within an event handler errors start flying!
The calling code and function is below.
The objective was to get a function to return a 'assettype' as a string and use that String to make a make routing decision.
What am I missing here?
Any chance of some guidance.
Here are the errors:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)dom16.14.0 and react 64.2
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
▶ 2 stack frames were collapsed.
...
GetAssetTypeNameFunction
C:/React-Springboot-CRUD-App/react-frontend/src/Services/GetAssetTypeNameFunction.js:7
const GetAssetTypeNameFunction = (props) =>{
const { assettype_assettypeId } = props;
> const [assetType,setAssetType] = useState()
AssetTypeService.getAssetTypeById(assettype_assettypeId).then( (res) =>
setAssetType(res.data));View compiled
ListAssetsComponent.editAssets
C:/React-Springboot-CRUD-App/react-frontend/src/components/ListAssetsComponent.jsx:62
editAssets(assetsid,assettype_assettypeId){
> if (GetAssetTypeNameFunction(assettype_assettypeId) === "Tower")
{
this.props.history.push(`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`);
}
...
Button in a Rendered List. onClick calls code that references function 'GetAssetTypeNameFunction'
...
<button onClick={ () => this.editAssets(assets.assetsid, assets.assettype_assettypeId)} className="btn btn-info">Update </button>
editAssets(assetsid,assettype_assettypeId){
if (GetAssetTypeNameFunction(assettype_assettypeId) === "Tower")
{this.props.history.push(`/add-assetstower${assetsid}/this.props.match.params.sitemasterid`);}
}
...
Finally the GetAssetTypeNameFunction function that is called:
...
import React, { useState} from 'react';
import AssetTypeService from './AssetTypeService'
const GetAssetTypeNameFunction = (props) =>{
// destructuring
const { assettype_assettypeId } = props;
const [assetType,setAssetType] = useState()
AssetTypeService.getAssetTypeById(assettype_assettypeId).then( (res) =>
setAssetType(res.data));
const arrayMap = assetType.map((post)=>{
return(
<ul>
{post.assettypeName}
</ul>
);})
return (
{arrayMap}
);
}
export default GetAssetTypeNameFunction;
...
I am new to the use of functions. I saw a reference that said: "Do not call in event handlers".
try this
import React, { useState} from 'react';
import AssetTypeService from './AssetTypeService'
const GetAssetTypeNameFunction = (props) =>{
const { assettype_assettypeId } = props;
const [assetType,setAssetType] = useState([])
useEffect(() => {
AssetTypeService.getAssetTypeById(assettype_assettypeId).then( (res) =>
setAssetType(res.data)).catch((error) => {
console.error('Error:', error);
});
}, [])
return (
<>
{
assetType.length > 0 ? assetType.map((post) => {
<ul>
{post.assettypeName}
</ul>
})
:
null
}
</>
)
export default GetAssetTypeNameFunction ;
Related
I am building an app with React using Remix, fly.io for deployment. I have a custom React hook useCountdown that has the following code:
import { useEffect, useState } from 'react';
const useCountdown = (targetSeconds) => {
const countDownSeconds = targetSeconds
const [countDown, setCountDown] = useState(countDownSeconds);
useEffect(() => {
const interval = setInterval(() => {
setCountDown(countDownSeconds);
return () => clearInterval(interval);
}, [countDownSeconds]);
return getReturnValues(countDown);
}, [])
};
const getReturnValues = (countDown) => {
const seconds = Math.floor((countDown % (1000 * 60)) / 1000);
return [seconds];
}
export { useCountdown }
The DateTimeDisplay component has the following code and is a component dependency of the hook:
import React from 'react';
const DateTimeDisplay = ({ value, type, isDanger }) => {
return (
<div className={isDanger ? 'countdown danger' : 'countdown'}>
<p>{value}</p>
<span>{type}</span>
</div>
);
};
export default DateTimeDisplay;
Finally, I have a CountdownTimer component that has the following code:
import React from 'react';
import DateTimeDisplay from './DateTimeDisplay';
import ExpiredNotice from './ExpiredNotice'
import { useCountdown } from '../hooks/useCountdown';
const ShowCounter = ({ seconds }) => {
return (
<div className="show-counter">
<DateTimeDisplay value={seconds} type={'seconds'} isDanger={false} />
</div>
);
};
const CountdownTimer = ({ targetSeconds }) => {
const [seconds] = useCountdown(targetSeconds);
if (seconds <= 0) {
return <ExpiredNotice />;
} else {
return (
<ShowCounter
seconds={seconds}
/>
);
}
};
export default CountdownTimer;
Upon trying to utilize the useCountdown() hook, I get the following error:
TypeError: useCountdown is not a function or its return value is not iterable
at CountdownTimer (/Users/tduke/Desktop/dev/drawesome/app/components/CountdownTimer.jsx:17:23)
at processChild (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:3353:14)
at resolve (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:3270:5)
at ReactDOMServerRenderer.render (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:3753:22)
at ReactDOMServerRenderer.read (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:3690:29)
at renderToString (/Users/tduke/Desktop/dev/drawesome/node_modules/react-dom/cjs/react-dom-server.node.development.js:4298:27)
at handleRequest (/Users/tduke/Desktop/dev/drawesome/app/entry.server.jsx:10:16)
at handleDocumentRequest (/Users/tduke/Desktop/dev/drawesome/node_modules/#remix-run/server-runtime/server.js:400:18)
at requestHandler (/Users/tduke/Desktop/dev/drawesome/node_modules/#remix-run/server-runtime/server.js:49:18)
at /Users/tduke/Desktop/dev/drawesome/node_modules/#remix-run/express/server.js:39:22
The line in question:
const [seconds] = useCountdown(targetSeconds);
Can someone explain to me this error, and what it exactly is telling me so I understand this error in it's entirety, and what the cause is in this instance?
How do I fix it?
useCountdown does not have a return statement, so it's implicitly returning undefined. Then when you try to destructure undefined, you get that error because array destructuring only works on arrays (or other iterables, which is why the error mentions "iterable"s, not arrays)
You did put a return statement inside your useEffect, but returning something in a useEffect is for cleaning up the effect. It won't cause useCountdown to return anything. To fix this, move your return statement to the body of useCountdown, and move the effect cleanup to be the return from the effect:
const useCountdown = (targetSeconds) => {
const countDownSeconds = targetSeconds
const [countDown, setCountDown] = useState(countDownSeconds);
useEffect(() => {
const interval = setInterval(() => {
setCountDown(countDownSeconds);
}, [countDownSeconds]);
return () => clearInterval(interval);
}, [])
return getReturnValues(countDown);
};
I was wondering if anybody could help.
I've been looking into lots of solutions to check when elements are displayed in the viewpoint and am currently trying to integrate this method into my project - https://www.webtips.dev/webtips/react-hooks/element-in-viewport, this method uses refs however the content I'm wishing to have on the page isn't static but rather mapped. I've found this method to dynamically use refs - https://dev.to/nicm42/react-refs-in-a-loop-1jk4 however I think I'm doing it incorrectly as I'm getting the following error:
TypeError: Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.
Any help at all is most appreciated.
Many thanks, code below.
Component.js
import { useRef, createRef } from 'react'
import useIntersection from './useIntersection.js'
const MyComponent = ({ someStateData }) => {
const ref= useRef([]);
ref.current = someStateData.map((title, i) => ref.current[i] ?? createRef());
const inViewport = useIntersection(ref, '0px');
if (inViewport) {
console.log('in viewport:', ref.current);
}
{someStateData.map((title, i) => {
return (
<div key={title.id} id={title.id} ref={ref.current[i]}>
{{ title.id }}
</div>
)
})}
}
useIntersection.js
import { useState, useEffect } from 'react'
const useIntersection = (element, rootMargin) => {
const [isVisible, setState] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
setState(entry.isIntersecting);
}, { rootMargin }
);
element && observer.observe(element);
return () => observer.unobserve(element);
}, []);
return isVisible;
};
export default useIntersection
ref.current has a collection of refs
const ref= useRef([]);
ref.current = someStateData.map((title, i) => ref.current[i] ?? createRef());
The observe function needs an Element to observe but the code calls with the whole ref
const inViewport = useIntersection(ref, '0px');
const useIntersection = (element, rootMargin) => {
...
...
observer.observe(element);
quoting #backtick "is an array of objects with current properties", the proper call should be something like
observer.observe(ref.current[i].current)
I would expect this useEffect to fail on the first render, since I would assume the innerCarouselRef.current would be undefined on the first render and it makes a call to getBoundingClientRect. Why does it work/why is the innerCarouselRef.current defined when the useEffect runs?
import React from 'react';
import { debounce } from 'lodash';
export default function Carousel({ RenderComponent }) {
const [innerCarouselWidth, setInnerCarouselWidth] = React.useState(0);
const [itemWidth, setItemWidth] = React.useState(0);
const innerCarouselRef = useRef();
const itemRef = useRef();
const content = data.map((el, i) => {
return (
<div key={`item-${i}`} ref={i === 0 ? itemRef : undefined}>
<RenderComponent {...el} />
</div>
);
});
useEffect(() => {
const getElementWidths = () => {
setInnerCarouselWidth(innerCarouselRef.current.getBoundingClientRect().width); // why doesn't this call to getBoundingClientRect() break?
setItemWidth(itemRef.current.getBoundingClientRect().width);
};
getElementWidths();
const debouncedListener = debounce(getElementWidths, 500);
window.addEventListener('resize', debouncedListener);
return () => window.removeEventListener('resize', debouncedListener);
}, []);
return (
<div className="inner-carousel" ref={innerCarouselRef}>
{content}
</div>
)
}
React runs the effects after it has updated the DOM (we typically want it to work that way). In your case, the effect runs after the component has mounted and so innerCarouselRef.current is set.
I would recommend reading the useEffect docs to gain a better understanding.
Here is the where I am having the problem,
const handleCLick = () => {
const parsedId = getYouTubeID(videoLink);
console.log(parsedId);
setVideoId(parsedId);
console.log(videoId);
}
Here when I am trying to log the 'parsedId' it logs the data correctly
ioNng23DkIM
And after using the setVideoId() function when I try to log the value it returns undefined
undefined
Here is a snap shot of the log output.
Home.js code:
import React, { useRef, useState } from "react";
import { Link } from "react-router-dom";
import getYouTubeID from 'get-youtube-id';
function Home(props) {
const [videoLink, setVideoLink] = useState();
const [isBool, setBool] = useState(false);
const [videoId, setVideoId] = useState();
const urlRef = useRef();
const handleChange = (event) => {
setVideoLink(event.target.value);
if (urlRef.current.value === '') {
alert('Please enter a URL');
setBool(true);
} else {
setBool(false);
}
}
const handleCLick = () => {
const parsedId = getYouTubeID(videoLink);
console.log(parsedId);
setVideoId(parsedId);
console.log(videoId);
}
return (
<section className="homeLayout">
<div className="logo-display">
<img className="logo-img" alt="logo" src="./logo.png" />
<h1>WatchIt</h1>
</div>
<div className="searchlayer">
<form>
<input ref={urlRef} id="videoLink" placeholder="Enter the youtube video URL:" onBlur={handleChange} required />
<Link style={{ pointerEvents: isBool ? 'none' : 'initial' }} to={`/play?=${videoId}`} onClick={handleCLick}>Play</Link>
</form>
</div>
</section>
);
}
export default Home;
You can use useEffect to solve your problem.
Use effect will listen to you state change n then you can perform logic in there.
The problem you're facing is because setState will set the value eventually, not immediately (Usually this means the update will be visible when the component is rendered again). If you want to do something after the value is set, you need to use useEffect.
Splitting your handleClick we get,
const handleCLick = () => {
const parsedId = getYouTubeID(videoLink);
console.log(parsedId);
setVideoId(parsedId); // Queue the change for `videoId`
}
useEffect(() => {
console.log(videoId);
}, [videoId]); // Call this function when the value of `videoId` changes
I am using this:
https://dev.to/gabe_ragland/debouncing-with-react-hooks-jci
But when I add it to my React Project I am getting this issue:
Line 4:20: Parse errors in imported module './BusNew': JSX attributes must only be assigned a non-empty expression (67:17) import/no-named-as-default
Line 4:20: Parse errors in imported module './BusNew': JSX attributes must only be assigned a non-empty expression (67:17) import/no-named-as-default-member
Here is my code:
import React, { Component, useState, useEffect } from 'react';
import useDebounce from './use-debounce';
export class BusNew extends Component {
return (
<div>
<input
placeholder="Search Marvel Comics"
onChange={e => setSearchTerm(e.target.value)}
/>
</div>
{isSearching && <div>Searching ...</div>}
{results.map(result => (
<div
key={result.id
>
<h4>{result.title}</h4>
))}
</div>
);
}
function Search() {
// State and setters for ...
// Search term
const [searchTerm, setSearchTerm] = useState('');
// API search results
const [results, setResults] = useState([]);
// Searching status (whether there is pending API request)
const [isSearching, setIsSearching] = useState(false);
// Debounce search term so that it only gives us latest value ...
// ... if searchTerm has not been updated within last 500ms
// As a result the API call should only fire once user stops typing
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(
() => {
if (debouncedSearchTerm) {
setIsSearching(true);
searchCharacters(debouncedSearchTerm).then(results => {
setIsSearching(false);
// Filter out results with no thumbnail
const filteredResults = results.data.results.filter(
result => result.thumbnail.path.indexOf('image_not_available') === -1
);
setResults(filteredResults);
});
} else {
setResults([]);
}
},
[debouncedSearchTerm] // Only call effect if debounced search term changes
);
}
function searchCharacters(search) {
const apiKey = 'f9dfb1e8d466d36c27850bedd2047687';
return fetch(`https://gateway.marvel.com/v1/public/comics?apikey=${apiKey}&titleStartsWith=${search}`, {
method: 'GET'
}).then(r => r.json());
}
export default BusNew;
Sandbox example:
https://codesandbox.io/s/usedebounce-7byqd
Thank you for any help!