I'm trying to reference a React icon through a ref, and attempting to do so yields to error:
Function components cannot be given refs. Attempts to access this ref will fail. Did you
mean to use React.forwardRef()?
Is there any way to work around this? I tried wrapping the icon in a React.forwardRef() to no avail.
import React, { useRef } from 'react'
import { FaChevronDown } from "react-icons/fa"
export default function Dashboard() {
const chevronRef = useRef(null)
const ChevronIconWrapper = React.forwardRef((props, ref) =>(
<FaChevronDown className="icon" ref={ref} />
));
const AccountLabel = () => {
{/* Neither of these seem to work. */}
<FaChevronDown className="icon" ref={chevronRef} />
<ChevronIconWrapper ref={chevronRef}/>
}
return(
<AccountLabel />
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Related
I am using nextJS and running into a little problem here and am not sure where I am going wrong. I've built an app where everything works in dev. However, I am trying to build my app but I am receiving the error:
./pages/genre.js/[genre].js
13:18 Error: React Hook "useRouter" is called in function "genre" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use". react-hooks/rules-of-hooks
The useRouter is located is on my page called [genre].js and the code is detailed below:
import React from "react";
import { useRouter } from "next/router";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import { useState } from "react";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
export default function genre() {
const router = useRouter();
const { genre } = router.query;
if (genre == "Trending") {
let mymovies = useFetchTrendingCatagory();
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
} else if (genre == "Top Rated") {
let mymovies = useFetchTopRatedCatagory();
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
} else {
let mymovies = useFetchMovieGenreResults(genre);
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
}
The official for Nextjs suggests using the useRouter to access the URL parameters
Why does this error occur? What am I missing? Any workarounds?
Update
I am trying to go with the solution below.
import React from "react";
import { useRouter } from "next/router";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import { useState } from "react";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
const useMovies = (genre) => {
switch (genre) {
case 'Trending':
return useFetchTrendingCatagory()
case 'Top Rated"':
return useFetchTopRatedCatagory()
default:
return useFetchMovieGenreResults(genre)
}
}
export default function Genre () {
const router = useRouter();
const { genre } = router.query;
const mymovies = useMovies(genre)
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
)
}
However I am still getting the errors below while trying to build my code
**./pages/genre.js/[genre].js
15:14 Error: React Hook "useFetchTrendingCatagory" is called conditionally. React Hooks must be called in the
exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
17:14 Error: React Hook "useFetchTopRatedCatagory" is called conditionally. React Hooks must be called in the
exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks
19:14 Error: React Hook "useFetchMovieGenreResults" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return? react-hooks/rules-of-hooks**
Would not the switch statement be considered a conditional render?
Any workaround for this?
You simply need to rename your component to start with a capital letter, i.e., Genre. The reason for this, is that components must start with uppercase letters, and only components and hooks can make use of hooks (such as useRouter)
export default function Genre()
For the hook problem you can't conditionally call hooks or call them within blocks, they're a lot like import statements, they need to be called at the top of their parents (for the hook, the parent will be the function, i.e., you need to call hooks at the top of the component/function). To still do what you want to do, rather than conditionally calling the hook, you can conditionally pass in parameters using the ternary operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator). I recommed reading into hook patterns (https://www.patterns.dev/posts/hooks-pattern/):
const router = useRouter();
const { genre } = router.query;
// instead of calling the hook conditionally, change the parameter you're calling in the hook conditionally, this will fix the error you're getting with regards to conditionally calling hooks
const mymovies = useFetchTrendingCategory(genre == "Trending" || genre == "Top Rated" ? undefined : genre)
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
);
I can see 2 problem here.
The first one is the component name (as #Ameer said). Try to rename it to Genre.
Second one is the use of hook in a conditionally render. This is not a good practice.
You can refactor code like this:
import React from "react";
import { useRouter } from "next/router";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import { useState } from "react";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
export default function Genre () {
const router = useRouter();
const { genre } = router.query;
const [movies, setMovies] = useState([]) // is that an array?
useEffect(() => {
let fetchedMovies
switch (genre) {
case 'Trending':
fetchedMovies = useFetchTrendingCatagory()
case 'Top Rated"':
fetchedMovies = useFetchTopRatedCatagory()
default:
fetchedMovies = useFetchMovieGenreResults(genre)
}
setMovies(fetchedMovies)
}, [genre])
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={movies} />
</div>
)
}
or like this:
import React from "react";
import { useRouter } from "next/router";
import useFetchMovieGenreResults from "../../hooks/useFetchMovieGenreResults";
import { useState } from "react";
import useFetchTrendingCatagory from "../../hooks/useFetchTrendingCatagory";
import useFetchTopRatedCatagory from "../../hooks/useFetchTopRatedCatagory";
const useMovies = (genre) => {
switch (genre) {
case 'Trending':
return useFetchTrendingCatagory()
case 'Top Rated"':
return useFetchTopRatedCatagory()
default:
return useFetchMovieGenreResults(genre)
}
}
export default function Genre () {
const router = useRouter();
const { genre } = router.query;
const mymovies = useMovies(genre)
return (
<div>
{/* <Navbar /> */}
<div>{genre}</div>
<Moviegenreresults movies={mymovies} />
</div>
)
}
import React from "react";
import "./SideBarPanel.css";
import AddIcon from "#mui/icons-material/Add";
import ExpandMoreIcon from '#mui/icons-material/ExpandMore';
const SideBarIcon = () => {
return (
<div class="sidebar-icon group">
<AddIcon/>
</div>
);
};
const SideBarPanel = () => {
return (
<div className="sidebar-icons">
<SideBarIcon />
<SideBarIcon />
</div>
);
};
export default SideBarPanel;
I want to pass the ExpandMoreIcon into the second SideBarIcon component.
You should be able to pass a component down to a child component via props just like a normal variable. There is one caveat though: when you render it in the child component, you have to ensure it is named with first letter a capital letter.
import React from "react";
import "./SideBarPanel.css";
import AddIcon from "#mui/icons-material/Add";
import ExpandMoreIcon from '#mui/icons-material/ExpandMore';
const SideBarIcon = (props) => {
const {icon: Icon} = props;
return (
<div class="sidebar-icon group">
{Icon && <Icon />}
<AddIcon/>
</div>
);
};
const SideBarPanel = () => {
return (
<div className="sidebar-icons">
<SideBarIcon />
<SideBarIcon icon={ExpandMoreIcon} />
</div>
);
};
export default SideBarPanel;
Here I assign icon to a new variable Icon while destructuring. This is important because <icon /> will not work because the first letter is not capitalized.
Sandbox
Source: https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized
I would write <Icon /> as a child of <SideBarIcon />. You can have it as optional child. Also in this case you would approach component composition, which is recommended by React.
I have a large project with multiple files. I want a button to render the content of some other components upon clicking. I am trying to understand why the following does not work, and what could I do to make it work instead?
Index.js
import React from "react";
import ReactDOM from "react-dom";
import { unitState, setUnitState } from './changeUnit.js'
function App() {
return (
<div>
<h1>{unitState}</h1>
<button onClick={setUnitState("°C")}>°C</button>
<button onClick={setUnitState("°F")}>°F</button>
</div>
);
}
// ------
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
changeUnit.js
export let unitState = "°C";
export function setUnitState(unit) {
(unit==="°C") ? unitState="°C" : unitState="°F";
}
with codesandbox link here
Currently, upon clicking any of the buttons the text does not change. But the code does reach inside the setUnitState method. I suspect that is because the main component is not rerendering. I tried to change my changeUnit.js code to
import App from './index.js';
export let unitState = "°C";
export function setUnitState(unit) {
(unit==="°C") ? unitState="°C" : unitState="°F";
App.forceUpdate();
}
but I get the error _index.default.update is not a method.
I have tried to use the useState hook from React but I just can't make it work for the life of me. I have tried many combinations but it seems that I can't return the setState function as a return from the custom hook.
Does anyone know how I can solve this problem?
I'm not sure why you're not using the useState hook, or what went wrong with your custom hook.
With useState():
function App() {
let [unitState, setUnitState] = React.useState('°C')
return (
<div>
<h1>{unitState}</h1>
<button onClick={() => setUnitState('°C')}>°C</button>
<button onClick={() => setUnitState('°F')}>°F</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
With custom hook:
function App() {
let { unitState, setCelsius, setFahrenheit } = useSwitch()
return (
<div>
<h1>{unitState}</h1>
<button onClick={setCelsius}>°C</button>
<button onClick={setFahrenheit}>°F</button>
</div>
);
}
function useSwitch() {
let [unitState, setUnitState] = React.useState('°C')
let setCelsius = () => setUnitState('°C')
let setFahrenheit = () => setUnitState('°F')
return { unitState, setCelsius, setFahrenheit }
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I'm guessing you probaly weren't using useState right. this is fairly simple with use state.
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
// import { unitState, setUnitState } from "./changeUnit.js";
function App() {
const [unitState, setUnitState] = useState("°C");
return (
<div>
<h1>{unitState}</h1>
<button onClick={() => setUnitState("°C")}>°C</button>
<button onClick={() => setUnitState("°F")}>°F</button>
</div>
);
}
// ------
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I think you should just use setState, it might be the case that you were trying to call the setState without the lambda?
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App() {
const [unitState, setUnitState] = useState("C");
return (
<div>
<h1>{unitState}</h1>
<button onClick={() => setUnitState("C")}>C</button>
<button onClick={() => setUnitState("F")}>F</button>
</div>
);
}
// ------
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
https://codesandbox.io/s/changeunitstate-forked-zbrym
So I am playing around with my first Nextjs app and am having trouble with adding a persistent sidebar.
I found Adam Wathan's article on persistent layouts in nextjs, but it seems like there is a newer pattern that was added recently using the _app.js page. I went to the docs and a few of the github issues around it, but it doesn't looks like there's a lot of documentation around it yet.
So for my example, I have my _app.js file:
import '../css/tailwind.css'
import Head from 'next/head'
import Sidebar from "../components/Sidebar";
export default function App({Component, pageProps}){
return(
<>
<Head />
<Component {...pageProps} />
</>
)
}
import React, { useState } from "react";
import Transition from "../components/Transition";
import Link from 'next/link'
function Sidebar({ children }) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [hideSidebarMenu, setHideSidebarMenu] = useState(true);
const openSidebar = () => {
setIsSidebarOpen(true);
setHideSidebarMenu(false);
};
const closeSidebar = () => {
setIsSidebarOpen(false);
};
const hideSidebar = () => {
setHideSidebarMenu(true);
};
return(
<div>
/*sidebar links here*/
</div>
)
}
export default Sidebar;
How do I integrate my sidebar component into this? I've tried adding it next to component and wrapping component and a few other iterations with no luck. Hopefully I'm just missing something simple.
This is odd. I could have swore I tried this very simple solution before, but something like this was completely sufficient.
This solution will feed in the page that you are on using the children prop in the sidebar.
import '../css/tailwind.css'
import Head from 'next/head'
import Sidebar from "../components/Sidebar";
export default function App({Component, pageProps}){
return(
<>
<Head />
<Sidebar >
<Component {...pageProps} />
</Sidebar>
</>
)
}
this option will just render the sidebar along with the content
import '../css/tailwind.css'
import Head from 'next/head'
import Sidebar from "../components/Sidebar";
export default function App({Component, pageProps}){
return(
<>
<Head />
<Sidebar />
<Component {...pageProps} />
</>
)
}
This is my code:
import React, { Component } from 'react'
import { BackButton } from 'components/button'
class LandingHeader extends Component {
render() {
const back = (props) => <BackButton forcedBackUrl={props.back.forcedBackUrl} />
return (
<div>
{back}
{this.props.children}
</div>
)
}
}
export default LandingHeader
If i put the <BackButton> component directly it works but if I use a stateless component and return it inside this one it wont. What im missing?
Im following the official documentation (https://facebook.github.io/react/docs/reusable-components.html) and I can't see whats wrong. Thanks.
Looking at the facebook documentation that you provided they give the example of :
const HelloMessage = (props) => <div>Hello {props.name}</div>;
ReactDOM.render(<HelloMessage name="Sebastian" />, mountNode);
and not just returning {HelloMessage}
therefore replace
{back}
with
<Back />
and you should be good to go
You've declared a ReactClass but you aren't rendering it - you have to turn it into a ReactElement:
const Back = (props) => <BackButton forcedBackUrl={props.forcedBackUrl} />
return (
<div>
<Back {...this.props.back} />
{this.props.children}
</div>
);
You need to inject the props into the component
import React, { Component } from 'react'
import { BackButton } from 'components/button'
class LandingHeader extends Component {
render() {
const back = (props) => <BackButton forcedBackUrl={props.back.forcedBackUrl} />
return (
<div>
{back(this.props)}
{this.props.children}
</div>
)
}
}