Instead of using useMemo, I simply define the "person" object outside of the component. which fix the issue of unnecessary rerender for reference changing of props
App.js
import React, { useState } from "react";
import Child from "./Child";
let person = { fname: "mahabub", lname: "shaon" };
export default function App() {
const [state, setState] = useState(0);
console.log("rendering parent");
return (
<>
<button onClick={() => setState(state + 1)}>click to render</button>
<Child person={person} />
</>
);
}
Child.js
import React from "react";
function Child({ person }) {
console.log("rendering child");
return (
<h1>
{person.fname} {person.lname}
</h1>
);
}
export default React.memo(Child);
Is it ok or any drawbacks of this approach?
Related
I am new to reactJS and stuck in an issue. i have a button in header that needs to toggle a class 'show' in a menu which is in some other file. I tried to use global state but do not know how to do that. here is what i did;
LAYOUT FILE
import React, { useState } from "react";
// importing header / menu etc.
function LayoutHome({ children }) {
const [state, setState] = React.useState({ MsgMenu: 'messages-dropdown' });
const handleOpenMsgMenu = (e) => {
e?.preventDefault();
setState({MsgMenu:'messages-dropdown show'});
};
return (
<>
<Header handleOpenMsgMenu={handleOpenMsgMenu} />
<MessageMenu handleOpenMsgMenu={state.MsgMenu} />
{children}
<Footer />
</>
);
}
HEADER
import React, { useState } from "react";
function Header({handleOpenMsgMenu}) {
<button type="button" onClick={handleOpenMsgMenu} className="header-notification-btn">MENU</button >
}
MENU
import React, { useState } from "react";
function MessageMenu({handleOpenMsgMenu}) {
<div id="messages-dropdown" className={handleOpenMsgMenu}>
// CONTENT
</div>
}
To achieve this you can use useState hook to toggle the display of the Menu.
create a new toggle state in global and pass it onto the menu component.
below is the complete code.
import React from "react";
export default function App({children}) {
const [state, setState] = React.useState({ MsgMenu: 'messages-dropdown' });
const [toggle, setToggle] = React.useState(false);
const handleOpenMsgMenu = (e) => {
e?.preventDefault();
setToggle(!toggle);
};
return (
<>
<Header handleOpenMsgMenu={handleOpenMsgMenu} />
<MessageMenu handleOpenMsgMenu={state.MsgMenu} toggle={toggle} />
{children}
</>
);
}
// Header
import React from "react";
function Header({handleOpenMsgMenu}) {
return <button type="button" onClick={handleOpenMsgMenu} className="header-notification-btn">MENU</button >
}
// Menu
import React from "react";
function MessageMenu({handleOpenMsgMenu, toggle}) {
return <div id="messages-dropdown" style={{display: toggle?"block":"none"}}>
<ul>
<li>
{handleOpenMsgMenu}
</li>
</ul>
</div>
}
You can toggle state with !value and then change your class depending on that value
setMenu(() => {
return {
...menu,
show: !menu.show // toggle
};
});
I've made a sample here
For the global state, check out Context or Redux
I am generating my state in the parent component. latestFeed generates a series of posts from my backend:
import React, { useState, useEffect } from "react";
import { getLatestFeed } from "../services/axios";
import Childfrom "./Child";
const Parent= () => {
const [latestFeed, setLatestFeed] = useState("loading");
const [showComment, setShowComment] = useState(false);
useEffect(async () => {
const newLatestFeed = await getLatestFeed(page);
setLatestFeed(newLatestFeed);
}, []);
return (
<div className="dashboardWrapper">
<Child posts={latestFeed} showComment={showComment} handleComment={handleComment} />
</div>
);
};
export default Parent;
then latestFeed gets generated into a series of components that all need to hold their own state.
import React, { useState } from "react";
const RenderText = (post, showComment, handleComment) => {
return (
<div key={post._id} className="postWrapper">
<p>{post.title}</p>
<p>{post.body}</p>
<Comments id={post._id} showComment={showComment} handleComment={() => handleComment(post)} />
</div>
);
};
const Child= ({ posts, showComment, handleComment }) => {
return (
<div>
{posts.map((post) => {
return RenderText(post, showComment, handleComment);
})}
</div>
);
};
export default Child;
In its current form, the state of RenderText's is all set at the same time. I need each child of Child to hold its own state.
Thank you!
Instead of using RenderText as a function, call it as a component:
{posts.map((post) => (
<RenderText key={post.id} post={post} showComment={showComment} />
))}
This is because when used as a component, it will have it's own lifecycle and state. If used as a function call, React does not instantiate it the same way - no lifecycle, no state, no hooks, etc.
I have one base hook(baseHook.js) which has some functions in it. Using composition I am trying to call those functions in child hook(formHook.js).
baseHook.js is as follow
import React, { Fragment, useEffect, useState} from "react";
import PropTypes from "prop-types";
const BaseHook = ({ ...props }) => {
const [show, setshow] = useState(false);
//when hovered on activeInput -->'activeInput' is declared but its value is never read.
const activeInput = (input) => {
setshow(true);
}
return (
<Fragment>
{props.children}
{show ? <div>
<p>Div is visible</p>
</div> : null}
</Fragment>
);
};
BaseHook.propTypes = {
activeInput:PropTypes.func,
};
export default BaseHook;
Now I am trying to use baseHook.js in another formHook.js where onFocus of input activeInput should get called.
import React, { Fragment, useEffect, useState} from "react";
import BaseHook from "components/BaseHook";
const FormHook = ({ ...props }) => {
return (
<BaseHook>
<Fragment>
<input
title= {"Input"}
onFocus={() => activeInput(InputValue)}
value={InputValue}
className="required-field"
/>
</Fragment>
<BaseHook>
);
};
export default FormHook;
activeInput function is not getting called from baseHook hence not able to setshow(true).
I am able to achieve this with react-class components using inheritance but is there way to call functions in composition in react-hooks?
I'm trying to get hold of ref on children component but it doesn't seem to be working. The same approach works fine with the React class component but not with hooks.
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref) // this logs null always}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
export function Test(props) {
return <div className="test" {...props}>
{props.children}
</div>
}
Your component is okay. It is probably because the are no children rendered to that component. I reproduced it with using TestContainer in App and put <h2>Ref</h2> as a child of TestContainer:
(removed the comment of course, since it has been hiding the } )
App.js
import React from "react";
import "./styles.css";
import TestContainer from "./TestContainer";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<TestContainer>
<h2>Ref</h2>
</TestContainer>
</div>
);
}
TestContainer.js
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref)}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
CodeSndbox:
HERE
I'm trying to use React Context to update navbar title dynamically from other child components. I created NavbarContext.js as follows. I have wrapped AdminLayout with NavContext.Provider and use useContext in Course.js to dynamically update navbar title inside useEffect. However, when I'm doing this, react throws the following error on the screen.
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
How can I use context properly so that I can update Header title from Course.js inside its useEffect?
NavbarContext.js
import React, {useState} from 'react'
export default () => {
const [name,setName] = useState("")
const NavContext = React.createContext({
name: "",
changeName: name => setName(name)
})
const NavProvider = NavContext.Provider
const NavConsumer = NavContext.Consumer
return NavContext
}
AdminLayout.js
<NavContext.Provider>
<div className={classes.wrapper}>
<Sidebar
routes={routes}
logoText={"Widubima"}
logo={logo}
image={image}
handleDrawerToggle={handleDrawerToggle}
open={mobileOpen}
color={color}
{...rest}
/>
<div className={classes.mainPanel} ref={mainPanel}>
<Navbar
routes={routes}
handleDrawerToggle={handleDrawerToggle}
{...rest}
/>
{/* On the /maps route we want the map to be on full screen - this is not possible if the content and conatiner classes are present because they have some paddings which would make the map smaller */}
{getRoute() ? (
<div className={classes.content}>
<div className={classes.container}>{switchRoutes}</div>
</div>
) : (
<div className={classes.map}>{switchRoutes}</div>
)}
</div>
</div>
</NavContext.Provider>
Navbar.js
import NavContext from "context/NavbarContext"
export default function Header(props) {
function makeBrand() {
var name;
props.routes.map(prop => {
if (window.location.href.indexOf(prop.layout + prop.path) !== -1) {
name = prop.name;
document.title = name;
}
return null;
});
return name;
}
return (
<AppBar className={classes.appBar + appBarClasses}>
<Toolbar className={classes.container}>
<div className={classes.flex}>
{/* Here we create navbar brand, based on route name */}
<NavContext.Consumer>
{({ name, setName }) => (
<Button
color="transparent"
href="#"
className={classes.title}
style={{ fontSize: "1.5em", marginLeft: "-2%" }}
>
{makeBrand() || name}
</Button>
)}
</NavContext.Consumer>
</Toolbar>
</AppBar>
);
}
Course.js
import React, { useState, useEffect, useContext } from "react";
import NavContext from "context/NavbarContext"
const AdminCourse = props => {
const context = useContext(NavContext);
useEffect(() => {
Axios.get('/courses/'+props.match.params.courseId).then(
res => {
context.changeName("hello")
}
).catch(err => {
console.log(err)
})
return () => {
setCourseId("");
};
});
return (
<GridContainer>
</GridContainer>
);
};
export default AdminCourse;
i think problem is there with your NavbarContext.js.
you are not exporting NavContext also.
you are defining provider, consumer but you are not using them either.
here's how you can solve your problem.
first create context and it's provider in a file as following.
NavContext.js
import React, { useState } from "react";
const NavContext = React.createContext();
const NavProvider = props => {
const [name, setName] = useState("");
let hookObject = {
name: name,
changeName: setName
};
return (
<NavContext.Provider value={hookObject}>
{props.children}
</NavContext.Provider>
);
};
export { NavProvider, NavContext };
in above code first i am creating context with empty value.
the i am creating NavProvider which actually contains value name as a state hook inside it.hookObject exposes state as per your naming conventions in code.
now i for testing purpose i defined two consumers.
one is where we update name in useEffect, that is ,
ConsumerThatUpdates.js
import React, { useContext, useEffect } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatUpdates = () => {
const { changeName } = useContext(NavContext);
useEffect(() => {
changeName("NEW NAME");
}, [changeName]);
return <div>i update on my useeffect</div>;
};
export default ConsumerThatUpdates;
you can update useEffect as per your needs.
another is where we use the name,
ConsumerThatDisplays.js
import React, { useContext } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatDisplays = () => {
const { name } = useContext(NavContext);
return <div>{name}</div>;
};
export default ConsumerThatDisplays;
and finally my App.js looks like this,
App.js
import React from "react";
import "./styles.css";
import { NavProvider } from "./NavContext";
import ConsumerThatDisplays from "./ConsumerThatDisplays";
import ConsumerThatUpdates from "./ConsumerThatUpdates";
export default function App() {
return (
<div className="App">
<NavProvider>
<ConsumerThatDisplays />
<ConsumerThatUpdates />
</NavProvider>
</div>
);
}
hope this helps!!
if you want to know more about how to use context effectively, i recooHow to use React Context effectively