I would like to use useEffect()to instead of componentWillMount(), but I found the hook can not use in class components, so I change the code as Function component, but it will get more error for the whole component, all code with this.xxx are getting an error, how could I edit below code to make it work? it was not easy for the react beginner. Please help me.
Below code is working fine with componentWillMount().
import React, { Component, useEffect } from 'react';
import { Link, withRouter } from 'react-router-dom';
import logo from '../../assets/images/logo.png';
import './index.less';
import { Menu } from 'antd';
import menuList from '../../config/menuConfig';
const { SubMenu } = Menu;
class LeftNav extends Component {
getMenuNodes = menuList => {
const path = this.props.location.pathname;
return menuList.reduce((pre, item) => {
if (!item.children) {
pre.push(
<Menu.Item key={item.key} icon={item.icon}>
<Link to={item.key}>{item.title}</Link>
</Menu.Item>,
);
} else {
const cItem = item.children.find(cItem => cItem.key === path);
if (cItem) {
this.openKey = item.key;
}
pre.push(
<SubMenu key={item.key} icon={item.icon} title={item.title}>
{this.getMenuNodes(item.children)}
</SubMenu>,
);
}
return pre;
}, []);
};
componentWillMount() {
this.MenuNodes = this.getMenuNodes(menuList);
}
render () {
// useEffect(() => {
//this.MenuNodes = this.getMenuNodes(menuList);
// }, []);
const path = this.props.location.pathname;
console.log(path);
const openKey = this.openKey;
return (
<div className="left-nav">
<Link to="./" className="left-nav-header">
<img src={logo} alt="" />
<h1>Backend System</h1>
</Link>
<Menu
selectedKeys={[path]}
defaultOpenKeys={[openKey]}
mode="inline"
theme="dark"
>
{this.MenuNodes}
</Menu>
</div>
);
}
}
export default withRouter(LeftNav);
componentDidMount() is the lifecycle method you want to use, it can be compared to useEffect you use in functional components. componentWillMount() is deprecated and you should not use it.
I took a stab at refactoring your class component into a functional component using hooks. In the end I found that instead of the useEffect hook, it was better to use the useMemo hook. useMemo is used to memoize expensive calculations, in this case, the calculation of menuNodes and openKey. useMemo will run anytime it's dependencies change, in this case, [menuList, path].
You will also notice that instead of the withRouter Higher-Order-Component, I replaced it with a call to the useLocation hook from 'react-router-dom'.
Let me know if this works for you, and if you have any questions.
import React, { useMemo } from 'react';
import { Link, useLocation } from 'react-router-dom';
import logo from '../../assets/images/logo.png';
import './index.less';
import { Menu } from 'antd';
import menuList from '../../config/menuConfig';
const LeftNav = (props) => {
const location = useLocation();
const path = location.pathname;
const { MenuNodes, openKey } = useMemo(() => {
let openKey;
const getMenuNodes = (menuList) => {
return menuList.reduce((pre, item) => {
if (!item.children) {
pre.push(
<Menu.Item key={item.key} icon={item.icon}>
<Link to={item.key}>{item.title}</Link>
</Menu.Item>
);
} else {
const cItem = item.children.find((cItem) => cItem.key === path);
if (cItem) {
openKey = item.key;
}
pre.push(
<Menu.SubMenu key={item.key} icon={item.icon} title={item.title}>
{getMenuNodes(item.children)}
</Menu.SubMenu>
);
}
return pre;
}, []);
};
const nodes = getMenuNodes(menuList);
return { MenuNodes: nodes, openKey: openKey };
}, [path]);
return (
<div className="left-nav">
<Link to="./" className="left-nav-header">
<img src={logo} alt="" />
<h1>Backend System</h1>
</Link>
<Menu selectedKeys={[path]} defaultOpenKeys={[openKey]} mode="inline" theme="dark">
{MenuNodes}
</Menu>
</div>
);
};
export default LeftNav;
edit
Fixed recursive call in getMenuNodes
Related
I installed react-router-dom v6 and I want to use a class based component, in previous version of react-router-dom v5 this.props.history() worked for redirect page after doing something but this code not working for v6 .
In react-router-dom v6 there is a hook useNavigate for functional component but I need to use it in class base component , Please help me how to use navigate in class component ?
In the react-router-dom v6, the support for history has been deprecated but instead of it, navigate has been introduced. If you want to redirect user to a specific page on success of a specific event, then follow the steps given below:
Create a file named as withRouter.js, and paste the code given below in this file:
import { useNavigate } from 'react-router-dom';
export const withRouter = (Component) => {
const Wrapper = (props) => {
const navigate = useNavigate();
return (
<Component
navigate={navigate}
{...props}
/>
);
};
return Wrapper;
};
Now, in whichever class based component you want to redirect the user to a specific path/component, import the above withRouter.js file there and use this.props.navigate('/your_path_here') function for the redirection.
For your help, a sample code showing the same has been given below:
import React from 'react';
import {withRouter} from '.your_Path_To_Withrouter_Here/withRouter';
class Your_Component_Name_Here extends React.Component{
constructor(){
super()
this.yourFunctionHere=this.yourFunctionHere.bind(this);
}
yourFunctionHere()
{
this.props.navigate('/your_path_here')
}
render()
{
return(
<div>
Your Component Code Here
</div>
)
}
}
export default withRouter(Your_Component_Name_Here);
Above Code works Perfect. And this is just a small extension.
If you want onclick function here is the code:
<div className = "row">
<button className= "btn btn-primary"
onClick={this.yourFunctionHere}>RedirectTo</button>
</div>
in class base component for redirect user follow this step :
first import some component like this
import { Navigate } from "react-router-dom"
now make a state for Return a boolean value like this:
state = {
redirect:false
}
now insert Naviagate component to bottom of your component tree
but use && for conditional rendring like this :
{
this.state.redirect && <Navigate to='/some_route' replace={true}/>
}
now when you want redirect user to some page just make true redirect state
on a line of code you want
now you can see you navigate to some page :)
Try this:
import {
useLocation,
useNavigate,
useParams
} from "react-router-dom";
export const withRouter = (Component) => {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}
return ComponentWithRouterProp;
}
and just used this function, in my case:
import { withRouter } from '../utils/with-router';
import './menu-item.styles.scss';
const MenuItem = ({title, imageUrl, size, linkUrl,router}) =>(
<div
className={`${size} menu-item`} onClick={() => router.navigate(`${router.location.pathname}${linkUrl}`)}
>
<div className='background-image'
style={{
backgroundImage: `url(${imageUrl})`
}} />
<div className="content">
<h1 className="title">{title.toUpperCase()}</h1>
<span className="subtitle">SHOP NOW</span>
</div>
</div>
)
export default withRouter(MenuItem);
I found this solution here https://www.reactfix.com/2022/02/fixed-how-can-i-use-withrouter-in-react.html
Other solution is useNavigate, for example:
<button onClick={() => {navigate("/dashboard");}} >
Dashboard
</button>
In a react class component use <Navigate>. From the react router docs:
A <Navigate> element changes the current location when it is rendered. It's a component wrapper around useNavigate, and accepts all the same arguments as props.
Try creating a reusable functional Component like a simple button and you can use it in your class component.
import React from "react";
import { useNavigate } from "react-router-dom";
const NavigateButton = ( { buttonTitle, route,isReplaced}) => {
const navigate = useNavigate();
return (
<button
className = "btn btn-primary"
onClick = { () => {
navigate( route , {replace:isReplaced} )
}}
>
{buttonTitle}
</button>;
);
});
export default NavigateButton;
After this, you can use NavigateButton in any of your class Components. And it will work.
<NavigateButton title = {"Route To"} route = {"/your_route/"} isReplaced = {false}/>
Found this explanation from the GitHub react-router issue thread, this explained how to use react-router 6 with class components
https://github.com/remix-run/react-router/issues/8146
I got this code from the above issue explanation
import React,{ Component} from "react";
import { useNavigate } from "react-router-dom";
export const withNavigation = (Component : Component) => {
return props => <Component {...props} navigate={useNavigate()} />;
}
//classComponent
class LoginPage extends React.Component{
submitHandler =(e) =>{
//successful login
this.props.navigate('/dashboard');
}
}
export default withNavigation(LoginPage);
If you need to use params for data fetching, writing a logic in your ClassComponent and render component depending on them, then create wrapper for your ClassComponentContainer
import { useLocation, useParams } from 'react-router-dom';
import ClassComponentContainer from './ClassComponentContainer';
export default function ClassComponentWrap(props) {
const location = useLocation();
const params = useParams();
return <ClassComponentContainer location={location} params={params} />
}
after it just use params in ClassComponent which is in props
import React from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import PresentationComponent from './PresentationComponent';
class ClassComponent extends React.Component {
componentDidMount() {
let postID = this.props.params.postID;
axios.get(`https://jsonplaceholder.typicode.com/posts/${postID}`)
.then((response) => {console.log(response)})
}
render() {
return <PresentationComponent {...this.props} />
}
}
const mapStateToProps = (state) => {...}
const mapDispatchToProps = (dispatch) => {...}
const ClassComponentContainer = connect(mapStateToProps, mapDispatchToProps)(ClassComponent);
export default ClassComponentContainer;
and use ClassComponentWrap component in Route element attribute
import { BrowserRouter, Route, Routes } from "react-router-dom";
import ClassComponentWrap from './components/ClassComponentWrap';
export default function App(props) {
return (
<BrowserRouter>
<Routes>
<Route path="/posts/:postID?" element={<ClassComponentWrap />} />
</Routes>
</BrowserRouter>
);
}
Here is my solution:
import React, { Component } from "react";
import { useNavigate } from "react-router-dom";
class OrdersView extends Component {
Test(props){
const navigate = useNavigate();
return(<div onClick={()=>{navigate('/')}}>test{props.test}</div>);
}
render() {
return (<div className="">
<this.Test test={'click me'}></this.Test>
</div>);
}
}
I recently started working with React, and I'm trying to understand why my context.js is giving me so much trouble. Admittedly I'm not great with JavaScript to start, so I'd truly appreciate any insight.
Thank you, code and the error that it generates:
import React, { useState, useContext } from 'react';
const AppContext = React.createContext(undefined, undefined);
const AppProvider = ({ children }) => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const openSidebar = () => {
setIsSidebarOpen(true);
};
const closeSidebar = () => {
setIsSidebarOpen(false);
};
const toggle = () => {
if (isSidebarOpen) {
closeSidebar();
} else {
openSidebar();
}
};
return (
<AppContext.Provider
value={{
isSidebarOpen,
openSidebar,
closeSidebar,
toggle
}}
>
{children}
</AppContext.Provider>
);
};
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
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
Thank you again for taking the time to look!
EDIT: Sidebar App Added for context (double entendre!)
import React from 'react';
import logo from './logo.svg'
import {links} from './data'
import {FaTimes} from 'react-icons/fa'
import { useGlobalContext } from "./context";
const Sidebar = () => {
const { toggle, isSidebarOpen } = useGlobalContext();
return (
<aside className={`${isSidebarOpen ? 'sidebar show-sidebar' : 'sidebar'}`}>
<div className='sidebar-header'>
<img src={logo} className='logo' alt='NavTask Management'/>
<button className='close-btn' onClick={toggle}>
<FaTimes />
</button>
</div>
<ul className='links'>
{links.map((link) => {
const { id, url, text, icon } = link;
return (
<li key={id}>
<a href={url}>
{icon}
{text}
</a>
</li>
);
})}
</ul>
</aside>
);
};
export default Sidebar;
I'm currently making use of the Wordpress API using Next.js on the front end. I want to fetch my navigation/menu data and have it prerendered. I've tried but only an empty <nav> </nav> element is rendered when I check the source-code. Is there a simple solution to this?
Here is my Nav component:
import { Config } from "../config";
import Link from "next/link";
import useFetch from "../hooks/useFetch";
export default function MainNav() {
const links = useFetch(`${Config.apiUrl}/wp-json/menus/v1/menus/main-nav`);
return (
<nav>
{!!links &&
links.map((link) => (
<Link href="/">
<a>{link.title}</a>
</Link>
))}
</nav>
);
}
And my custom useFetch.js hook:
import { useEffect, useState } from "react";
export default function useFetch(url) {
const [links, setLinks] = useState();
// Must use useEffect in non-page component
useEffect(async () => {
let res = await fetch(url);
res = await res.json();
setLinks(res.items);
}, []);
return links;
}
Firs of all useEffect is not async at all, even if you define async inside the useEffect callback function, the proper way to do this, is to set up separate state - useState(false) for spinner or include that into exisiting one, which will control the spinner, since you are fetching the data via REST, basically the full example should look like this:
useFetch.js hook:
import { useEffect, useState } from "react";
export default function useFetch(url) {
const [{ links, isLoading }, setLinks] = useState({ links: [], isLoading: true });
// Must use useEffect in non-page component
useEffect(() => {
(async funtion() {
const res = await fetch(url);
const { items } = await res.json();
setLinks({ links: items, isLoading: false });
})()
}, []);
return [isLoading, links];
}
Nav.js component:
import { Config } from "../config";
import Link from "next/link";
import useFetch from "../hooks/useFetch";
export default function MainNav() {
const [links, isLoading] = useFetch(`${Config.apiUrl}/wp-json/menus/v1/menus/main-nav`);
if(isLoading) {
return <Spinner/>
}
return (
<nav>
{!!links && !isLoading &&
links.map((link) => (
<Link href="/">
<a>{link.title}</a>
</Link>
))}
</nav>
);
}
So I figured it out, I fetched the links data on the page that the component was nested onto and then fed the data down using component composition. The problem is that I have to nest them all on the page directly. If someone has a more elegant solution, please let me know :)
The page index.js:
import PostIndex from "../components/PostIndex";
import Layout from "../components/Layout";
import Header from "../components/Header";
import MainNav from "../components/MainNav";
import { Config } from "../config";
export default function Home(props) {
return (
<Layout>
<Header>
<MainNav links={props.links} />
</Header>
<h2>Home Page</h2>
<PostIndex limit={3} />
</Layout>
);
}
export async function getServerSideProps() {
const [data1, data2] = await Promise.all([
fetch(`${Config.apiUrl}/wp-json/wp/v2/posts?per_page=3`),
fetch(`${Config.apiUrl}/wp-json/menus/v1/menus/main-nav`),
]);
const posts = await data1.json();
const links = await data2.json();
return {
props: {
posts,
links,
},
};
}
The Layout.js component:
export default function Layout({ children }) {
return <div>{children}</div>;
}
The Header.js component:
import Link from "next/link";
export default function Header({ children }) {
return (
<div>
<Link href="/">
<a>
<h1>Wordpress Blog</h1>
</a>
</Link>
{children}
</div>
);
}
And the MainNev.js component:
import { Config } from "../config";
import Link from "next/link";
export default function MainNav({ links }) {
return (
<nav>
{!!links &&
links.items.map((item) => (
<Link href="/">
<a>{item.title}</a>
</Link>
))}
</nav>
);
}
I have found a code that solved my problem in Next JS re rendering when changing pages. But now i need to send props to the children component. I got no idea how i can make it works here, this is my layout.js code. As you can see i can send props to Header component but for children i dont know how, because it is a variable and not a component.
import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{children}
<Footer />
</Fragment>
);
}
Thank you for the help
Have you considered using React's Context API? The idea is that when using the Context API your component's state get's lifted, to be managed at a global scale. If a component needs a prop, instead of passing props down manually (prop drilling) you can simply wrap you component in what's known as a context provider. This will allow that Component to access the global state of your application. This is good because, when your application gets bigger, you may need to pass props down through many components which can clutter and add unneeded confusion.
React provides some great documentation to set your React application up to use the Context API. Highly recommend checking it out!
https://reactjs.org/docs/context.html
Try this
import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
function recursiveMap(children, fn) {
return React.Children.map(children, child => {
if (!React.isValidElement(child) || typeof child.type == 'string') {
return child;
}
if (child.props.children) {
child = React.cloneElement(child, {
children: recursiveMap(child.props.children, fn)
});
}
return fn(child);
});
}
// Add props to all child elements.
const childrenWithProps = recursiveMap(children, child => {
// Checking isValidElement is the safe way and avoids a TS error too.
if (isValidElement(child)) {
// Pass additional props here
return cloneElement(child, { currentUser: { ...user } })
}
return child;
});
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{childrenWithProps}
<Footer />
</Fragment>
);
}
You can use React's cloneElement to achieve that.
React.cloneElement(children, {
isRegisterPage: pageProps.isRegisterPage,
isLoginPage: pageProps.isLoginPage,
outHome: pageProps.outHome
})
Complete example in your case:
import Header from "../components/header";
import Footer from "../components/footer";
import React, { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{
React.cloneElement(children, {
isRegisterPage: pageProps.isRegisterPage,
isLoginPage: pageProps.isLoginPage,
outHome: pageProps.outHome
})
}
<Footer />
</Fragment>
);
}
From the answer of Lucas Raza, below is an example that uses Context API to apply themes to different components
1.Create a context File
//ThemeContex.js
import { createContext, useState } from "react";
export const ThemeContext = createContext();
export const withThemeContext = Component => {
const WrappedComp = props => {
const [darkColor,lightColor] = ["#3b3b3b", "#ddd"]
const [lightBackgoround,darkBackgoround] = ["#ececec","#1d2a35"]
const darkTheme = {
backgroundColor: darkBackgoround,
color:lightColor,
}
const lightTheme = {
backgroundColor: lightBackgoround,
color:darkColor,
}
const themes = {
darkTheme, lightTheme
}
const [theme, setTheme] = useState(lightTheme)
const children ={
theme,
themes,
setTheme,
}
return(
<StylesContext.Provider value={{...children}} >
<Component {...props} />
</StylesContext.Provider>
)
}
return WrappedComp;
}
In _app.js, import withThemeContext higher component and wrap MyApp with it when exporting it.
import { withThemeContext } from '../components'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default withThemeContext(MyApp)
You can know use theme any where in a component
import { useContext } from 'react'
import {ThemeContext} from '../components'
export default function Home() {
const { theme } =useContext(ThemeContext)
return (
<div id="home" style={theme}>
// Home logic...
</div>
)
}
I need help with this issue, my app component as in the image below. I want to store track object inselectedTrack in the state using useState when I click on the view details button. Then use it to display track details in instead of making another fetch from API to get tack details, but when I use useContext inside give me this error TypeError: Cannot read property 'selectedTrack' of undefined.
React Components
import React from 'react';
import Header from './Header';
import Search from '../tracks/Search';
import Tracks from '../tracks/Tracks';
import Footer from './Footer';
import TrackContextProvider from '../../contexts/TrackContext';
const Main = () => {
return (
<div>
<TrackContextProvider>
<Header />
<Search />
<Tracks />
<Footer />
</TrackContextProvider>
</div>
);
};
export default Main;
TrackContext.js
import React, { createContext, useState, useEffect } from 'react';
export const TrackContext = createContext();
const TrackContextProvider = props => {
const [tracks, setTracks] = useState([]);
const [selectedTrack, setSelectedTrack] = useState([{}]);
const API_KEY = process.env.REACT_APP_MUSICXMATCH_KEY;
useEffect(() => {
fetch(
`https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?chart_name=top&page=1&page_size=10&country=fr&f_has_lyrics=1&apikey=${API_KEY}`
)
.then(response => response.json())
.then(data => setTracks(data.message.body.track_list))
.catch(err => console.log(err));
// to disable the warning rule of missing dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// state for heading
const [heading, setHeading] = useState(['Top 10 Tracks']);
return (
<TrackContext.Provider value={{ tracks, heading, selectedTrack, setSelectedTrack }}>
{props.children}
</TrackContext.Provider>
);
};
export default TrackContextProvider;
import React, { Fragment, useContext } from 'react';
import { Link } from 'react-router-dom';
import { TrackContext } from '../../contexts/TrackContext';
const TrackDetails = () => {
const { selectedTrack } = useContext(TrackContext);
console.log(selectedTrack);
return (
<Fragment>
<Link to="/">
<button>Go Back</button>
</Link>
<div>
{selectedTrack === undefined ? (
<p>loading ...</p>
) : (
<h3>
{selectedTrack.track.track_name} by {selectedTrack.track.artist_name}
</h3>
)}
<p>lyrics.............</p>
<div>Album Id: </div>)
</div>
</Fragment>
);
};
export default TrackDetails;
import React, { useState, useContext, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { TrackContext } from '../../contexts/TrackContext';
const Track = ({ trackInfo }) => {
const { selectedTrack, setSelectedTrack } = useContext(TrackContext);
const handleClick = e => {
setSelectedTrack(trackInfo);
};
console.log(selectedTrack);
return (
<li>
<div>{trackInfo.track.artist_name}</div>
<div>Track: {trackInfo.track.track_name}</div>
<div>Album:{trackInfo.track.album_name}</div>
<div>Rating:{trackInfo.track.track_rating}</div>
<Link to={{ pathname: `/trackdetails/${trackInfo.track.track_id}`, param1: selectedTrack }}>
<button onClick={handleClick}>> View Lyric</button>
</Link>
</li>
);
};
export default Track;
UPDATE: adding Tracks component
import React, { useContext, Fragment } from 'react';
import Track from './Track';
import { TrackContext } from '../../contexts/TrackContext';
const Tracks = () => {
const { heading, tracks } = useContext(TrackContext);
const tracksList = tracks.map(trackInfo => {
return <Track trackInfo={trackInfo} key={trackInfo.track.track_id} />;
});
return (
<Fragment>
<p>{heading}</p>
{tracks.length ? <ul>{tracksList}</ul> : <p>loading...</p>}
</Fragment>
);
};
export default Tracks;
I think the issue here is that since the selectedTrack is loaded asynchronously, when it is accessed from the context, it is undefined (you can get around the TrackContext being undefined by passing in a default value in the createContext call). Since the selectedTrack variable is populated anychronously, you should store it in a Ref with useRef hook, and return that ref as part of the context value. That way you would get the latest value of selectedTrack from any consumer of that context.
const selectedTracks = useRef([]);
useEffect(() => {
fetch(
`https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?chart_name=top&page=1&page_size=10&country=fr&f_has_lyrics=1&apikey=${API_KEY}`
)
.then(response => response.json())
.then(data => {
selectedTrack.current = data.message.body.track_list;
})
.catch(err => console.log(err));
// to disable the warning rule of missing dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);