I have followed this tutorial to make a hamburger menu in react/next.js: https://youtu.be/prbOI7G0RvY
import { useState } from "react";
import user from '../styles/userview.module.css'
export function PageHeader() {
const [isOpen, setIsOpen] = useState(false);
const openMenu= ()=> setIsOpen(!isOpen);
return (
<header className={user.header}>
<nav className={user.navbar}>
<a className={user.navlogo}>[BrandLogo]</a>
<ul className={isOpen === false ?
user.navmenu : user.navmenu + ' ' + user.active}>
<li className={user.navitem}>
<a className={user.navlink}>Home</a>
</li>
<li className={user.navitem}>
<a className={user.navlink}>About</a>
</li>
<li className={user.navitem}>
<a className={user.navlink}>Contact</a>
</li>
</ul>
<button className={isOpen === false ? user.hamburger : user.hamburger + ' ' + user.active}
onClick= {openMenu}
>
<span className={user.bar}></span>
<span className={user.bar}></span>
<span className={user.bar}></span>
</button>
</nav>
</header>
)
}
But I keep getting this error message:
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)
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/link/invalid-hook-call for tips about how to debug and fix this problem.
It also indicates the problem is at this row: const [isOpen, setIsOpen] = useState(false);
Can someone please help me to understand what is wrong?
EDIT:
Here is a picture of the exact error message: https://pasteboard.co/3xyAu6m8IeAW.png
EDIT 2:
I opened up my repo so you can see/test it on your own. Maybe the fault is in another file? The burgermenu is in the main-branch and ..components/userview and the page that imports and show the burgermenu is ../pages/hamburgertestfile.
You cant call hooks from a regular javascript function as specified in the docs, therefore try importing React in your first line to convert the javascript function into a React function
import React, { useState } from "react";
The answer was provided by a user of the Next.js-github-forum:
https://github.com/vercel/next.js/discussions/34858
Related
I am having an issue with pagination on a page in my React application. On the page, search results are rendered when one types into the search bar (naturally). I think my issue arises from how pagination is set up on this page.
Pagination works fine as long as the user clicks back to the first page before searching for anything else. For example, if the user is on page 3 and then types something new into the search bar, the new search will not display without the user clicking 'page 1' again on the pagination bar. However if they returned to page 1 of their initial search before doing the new search, page 1 of the new search displays properly. Hopefully this makes sense. Here is the page where the issue occurs:
import React, { useState } from "react";
import Pagination from "#material-ui/core/Pagination";
import usePagination from "./usePagination.js";
export default function Main({reviews, web3}) {
const [search, setSearch] = useState("");
const [page, setPage] = useState(1);
const updateSearch = (event) => {
setSearch(event.target.value.substr(0, 20));
}
let filteredReviews = reviews.filter(
(review) => {
return review.restaurantName.indexOf(web3.utils.toHex(search)) !== -1;
});
let paginatedReviews = usePagination(filteredReviews, 2);
const handleChange = (e, p) => {
setPage(p);
paginatedReviews.jumpPage(p);
}
return (
<div className="container-fluid mt-5" style={{ minHeight: "100vh" }}>
<div className="row">
<main role="main" className="col-lg-12 ml-auto mr-auto" style={{ maxWidth: '500px' }}>
<div className="content mr-auto ml-auto">
<input type="text" className="form-control" value={search} onChange={updateSearch} />
{filteredReviews.length > 0 ? paginatedReviews.pageData().map((review, key) => {
return (
<>
<div key={key}>
// search result item
</div>
</>
)
})
{filteredReviews.length > 1
? <Pagination
count={paginatedReviews.maxPage}
page={page}
onChange={handleChange}
/>
: null
)
</div>
</main>
</div>
</div>
);
}
and here is usePagination:
import { useState } from "react";
export default function usePagination(allReviews, perPage) {
const [currentPage, setCurrentPage] = useState(1);
const maxPage = Math.ceil(allReviews.length / perPage);
function pageData() {
const start = (currentPage - 1) * perPage;
const end = start + perPage
return allReviews.slice(start, end);
}
function jumpPage(page) {
const pageNumber = Math.max(1, page);
setCurrentPage((currentPage) => Math.min(pageNumber, maxPage));
}
return { jumpPage, pageData, currentPage, maxPage }
}
I thought I could resolve the issue I'm having by adding setPage(1) to updateSearch in order to have the page automatically move to page 1 for each new search, but that didn't work, as you still had to click page 1 on the actual pagination bar for the results to show up.
Edit: I tried renaming currentPage and setCurrentPage in the hook so that they shared the same names as on my page, but that also did not work.
Thanks in advance for any help you can offer. If you need me to elaborate on anything I will happily do so.
How about updating the page in a useEffect? That way you'll make sure all hooks have run and their return values are up-to-date (useEffect runs after render). If you reset the page too early, at the same time as the search query, jumpPage might rely on stale data: your search results and the internal usePagination values like maxPage will not have had a chance to recalculate yet.
Here is a working example based off your codesandbox: https://codesandbox.io/s/restless-dust-28351
Note that to make sure useEffect runs on search change only, you need to wrap jumpPage in a useCallback so that the jumpPage function reference remains the same. In general, I'd recommend you do that to methods returned from custom hooks. This way those methods are safer to consume anywhere, including as dependencies to useEffect, useCallback etc.
Also I'd recommend destructuring the custom hook return values, so that each of them can be used on its own as a hook dependency, like jumpPage in my example above.
I've also removed the page state from App, as it's already tracked in usePagination and returned from there. Having usePagination as a single source of truth that encapsulates all your pagination stuff makes things simpler. Simplicity is a great ideal to strive for:)
Lastly, a small side note: it's best not use <br /> purely as a spacer. It clutters up the markup without contributing any useful semantics, and it's better to leave the spacing concern to CSS.
And good luck with your React endeavors, you're doing great!
In React JSX I want to convert a part of the text into an anchor tag dynamically. Also on click of the anchor tag, I want to do some API call before redirecting it to the requested page. But I am failing to achieve this. Can anyone have a look and let me know where am I going wrong?
I have recreated the issue on code sandbox: here is the URL: Code Sandbox
Relevant code from sandbox:
import React from "react";
import "./styles.css";
export default function App() {
let bodyTextProp =
"This text will have a LINK which will be clicked and it will perform API call before redirect";
let start = 22;
let end = 26;
let anchorText = bodyTextProp.substring(start, end);
let anchor = `<a
href="www.test.com"
onClick={${(e) => handleClick(e)}}
>
${anchorText}
</a>`;
bodyTextProp = bodyTextProp.replace(anchorText, anchor);
const handleClick = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
const handleClick2 = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
return (
<div className="App">
<h3 dangerouslySetInnerHTML={{ __html: bodyTextProp }} />
<a href="www.google.com" onClick={(e) => handleClick2(e)}>
Test Link
</a>
</div>
);
}
The problem is variable scope. While it is entirely possible to use the dangerouslySetInnerHTML as you are doing, the onClick event isn't going to work the same way. It's going to expect handleClick to be a GLOBAL function, not a function scoped to the React component. That's because React doesn't know anything about the "dangerous" html.
Normally React is using things like document.createElement and addEventListener to construct the DOM and add events. And since it's using addEventListener, it can use the local function. But dangerouslySetInnerHTML bypasses all of that and just gives React a string to insert directly into the DOM. It doesn't know or care if there's an event listener, and doesn't try to parse it out or anything. Not really a good scenario at all.
The best solution would be to refactor your code so you don't need to use dangerouslySetInnerHTML.
*Edit: since you say that you need to do multiple replacements and simply splitting the string won't suffice, I've modified the code to use a split.
When used with a RegExp with a capturing group, you can keep the delimiter in the array, and can then look for those delimiters later in your map statement. If there is a match, you add an a
import React from "react";
import "./styles.css";
export default function App() {
let bodyTextProp =
"This text will have a LINK which will be clicked and it will perform API call before redirect";
let rx = /(\bLINK\b)/;
let array = bodyTextProp.split(rx);
const handleClick = (e) => {
console.log("The link was clicked.");
e.preventDefault();
};
const handleClick2 = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
return (
<div className="App">
<h3>
{array.map((x) => {
if (rx.test(x))
return (
<a href="www.test.com" onClick={handleClick}>
{x}
</a>
);
else return x;
})}
</h3>
<a href="www.google.com" onClick={(e) => handleClick2(e)}>
Test Link
</a>
</div>
);
}
I'm quite new to React, and I'm trying to use Parallax.js library in react. Now I have done the basics and installed the library using npm, I have imported the library and I have followed this topic that related to my question.
But now I'm having difficulty to make parallax work, I recieve the following error:
index.js:1 Warning: React does not recognize the `dataDepth` prop on a DOM element. If you intentionally
want it to appear in the DOM as a custom attribute, spell it as lowercase `datadepth` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
in li (at home.js:17)
in ul (at home.js:16)
in section (at home.js:15)
in ParallaxComponent (at App.js:7)
in App (at src/index.js:9)
in StrictMode (at src/index.js:8)
And this is my code
import React, { Component } from 'react'
import './css/style.css';
import headerbg from './images/header-bg.svg';
import Parallax from 'parallax-js' // Now published on NPM
class ParallaxComponent extends Component {
componentDidMount() {
this.parallax = new Parallax(this.scene)
}
componentWillUnmount() {
this.parallax.disable()
}
render() {
return (
<section className="header">
<ul ref={el => this.scene = el}>
<li className="layer" dataDepth="1.00">
<img src={headerbg} alt="Header background"/>
</li>
</ul>
<div className="content">
<h1>אנחנו דואגים להכל</h1>
<h2>אתם רק צריכים לאשר</h2>
<p>
אצלנו ב Triple V אין פשרות, איכות היא המטרה העליונה! <br/>
כל האתרים שלנו נבנים תחת פלטפורמת וורדפרס עם ציוני <br/>
מהירות שלא יורדים מ80 למובייל! <br/>
למה זה חשוב אתם שואלים? גוגל אוהב מהירות
<br/>
ככל שהאתר שלכם יותר מהיר ככה גוגל יותר מרוצה.
</p>
</div>
</section>
)
}
}
export default ParallaxComponent;
How can I run Parallax.js inside React library properly?
Try using data-depth attribute instead of DataDepth on your layers.
I was trying to resolve this problem, but I have no luck...
I'm using React and 'react-bootstrap'. Getting data from firebase with useState, as you can see in the next code. But also I'm calling a modal as a component, and this modal use useState to show and hide the modal.
export const Models = () => {
const [models, setModels] = useState(null);
useEffect(() => {
firebase.database().ref('Models').on('value', (snapshot) => {
setModels(snapshot.val())
});
}, []);
return models;
}
the problem result when I click on the url to access the modal, this one is shown and the main component goes to firebase and tries to get the data again. So, if I click 3 times on the modal, I will get my data from firebase 3 times.
How can I fix this? to get my data from firebase only one time, regardless of the times that you open the modal window?
The other part of the code
const Gallery = () => {
const [fireBaseDate, setFireBaseDate] = useState(null);
axios.post('https://us-central1-models-gallery-puq.cloudfunctions.net/date',{format:'DD/MM/YYYY'})
.then((response) => {
setFireBaseDate(response.data)
});
let content = Models();
let models = [];
const [imageModalShow, setImageModalShow] = useState(false);
const [selectedModel, setSelectedModel] = useState('');
if(content){
Object.keys(content).map((key, index) =>
models[index] = content[key]
);
models = shuffleArray(models);
console.log(models)
return(
<div className="appContentBody">
<Jumbo />
<Promotion models={models}/>
<div className="Gallery">
<h1>Galería - Under Patagonia</h1>
<Filter />
<div className="img-area">
{models.map((model, key) =>{
let myDate = new Date(model.creationDate);
let modelEndDate = new Date(myDate.setDate(myDate.getDate() + 30)).toLocaleDateString('en-GB')
if(fireBaseDate !== modelEndDate && model.active === true){
return (
<div className="img-card filterCard" key={key}>
<div className="flip-img">
<div className="flip-img-inner">
<div className="flip-img-front">
<img className="single-img card-img-top" src={model.thumbnail} alt="Model"/>
</div>
<div className="flip-img-back">
<h2>{model.certified ? 'Verificada!' : 'No Verificada'}</h2>
<p>Número: {model.contact_number}</p>
<p>Ciudad: {model.city}</p>
<p>Servicios: {model.services}</p>
</div>
</div>
</div>
<h5>{model.name}</h5>
<Button variant="danger" onClick={() => {
setImageModalShow(true)
setSelectedModel(model)}
}>
Ver
</Button>
</div>
);
}
return 0})}
</div>
<Image
show={imageModalShow}
onHide={() => setImageModalShow(false)}
model={selectedModel}
/>
</div>
<Footer />
</div>
)} else {
return (
<div className="loading">
<h1>Loading...</h1>
</div>
)}
}
export default Gallery;
Thanks for your time!
Models is a regular javascript function, not a functional component. So this is not a valid use of hooks, and will not work as expected. See docs on rules of hooks.
A functional component receives props and returns JSX or another React element.
Since it does not, it is basically restarting and calling your effect each time its called by the parent.
Looking at your edit, you should probably just remove the Models function and put the logic in the Gallery component.
The way I read your above component makes it seem like you've defined a custom hook for getting data from firebase.
So first off, I would rename it to useFbData and treat it as a custom hook, so that you can make use of the ESLint Plugin for Hooks and make sure you're following the rules of hooks.
The way you have this above, if it's a function within a component, your function will fire on every render, so the behaviour you are describing is what I would expect.
Depending on how expensive your request is/how often that component renders, this might be what you want, as you probably don't want to return stale data to your component. However, if you feel like the response from the DB should be cached and you have some logic to invalidate that data you could try something like this:
import { useEffect, useRef } from 'react';
const useFbData = invalidationFlag => {
const data = useRef(null);
useEffect(() => {
if (!data.current || invalidationFlag) {
firebase.database().ref('Data').on('value', (snapshot) => {
data.current = snapshot.val();
});
}
}, [invalidationFlag]);
return data.current;
};
export default useFbData;
This way, on the initial run and every time you changed the value of invalidationFlag, your effect inside the useFbData hook would run. Provided you keep track of invalidationFlag and set it as required, this could work out for you.
The reason I used a ref here instead of state, is so that the effect hook doesn't take the data in the dependency array (which would cause it to loop indefinitely if we used state).
This will persist the result of the db response between each call and prevent the call being made multiple times until you invalidate. Remember though, this will mean the data you're using is stale until you invalidate.
I have a footer component with three links. When a user clicks a link, besides taking the user to a new page, I am trying to use a mixin to track the click event. When I set a breakpoint in chrome devtools, it appears that this implementation is not working. I imported my constants file, and the mixin.
footer, one link for brevity
<template>
<footer>
<div class="container">
<div class="row">
<div class="col align-center">
<a
href="/"
target="_blank"
class="btn"
name="item"
#click="logButtonClick(ANALYTICS.ITEM)">{{ $t('footer.item') }}</a>
</div>
</div>
</div>
</footer>
</template>
<script>
import analytics from '#/mixins/analytics'
import { ANALYTICS } from "#/constants"
export default {
name: 'PageFooter',
mixins: [analytics]
}
</script>
mixin
methods: {
logButtonClick (buttonType) { // breakpoint here, get nothing
this.$analytics.track({
identifier: `Consumer ${this.$options.name} - ${buttonType} Button`
})
}
}
Am I missing something? Should this implementation work or should I have a method such as:
methods: {
selectLink(str) {
if (str === item) {
this.logButtonClick(ANALYTICS.ITEM)
}
}
}
The original error I received was
"Property or method ANALYTICS not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class based components, by initializing the property."
and
"Uncaught TypeError: Cannot read property ITEM of undefined at click event...."
Essentially this means I did not define ANALYTICS either in PageFooter (because this is a dumb component, I did not want to add a data object to it, I wanted to keep it strictly presentational) or on the vue instance at a root level. Since ANALYTICS is undefined, ITEM then throws another error because it can not be a property of undefined.
This is my solution, I used a switch case in the and in the template tag added #click="selectLink('asd')"
methods: {
selectLink (str) {
switch (true) {
case str === 'asd':
this.logButtonClick(ANALYTICS.ITEM)
break
case str === 'efg':
this.logButtonClick(ANALYTICS.ITEM2)
break
case str === 'hij':
this.logButtonClick(ANALYTICS.ITEM3)
break
}
}
}
and the unit test:
it('[positive] should track analytics if `asd` is passed to selectLink()', () => {
const str = 'asd'
const mockFn = jest.fn()
jest.spyOn(wrapper.vm, 'logButtonClick')
wrapper.find('a').trigger('click')
mockFn(str)
expect(wrapper.vm.logButtonClick).toHaveBeenCalledWith(ANALYTICS.COOKIE_CONSENT)
})
https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties
Moral of the story, question things when senior engineers tell you to do something funky in a code review.