Share component on multiple pages NextJS and avoid re render - reactjs

As mentioned in the title I have tabs component with few links.
tabs:
<AppBar color="default" position="relative">
<Tabs indicatorColor="primary" textColor="primary" value={activeTab} onChange={handleTabChange}>
{tabs.map((tab, i) => ( <Tab label={tab.label} id={`tab-${i}`} key={i} />))}
</Tabs>
</AppBar>
I have this tabs component placed in layout like this
<Container>
<TabsComponent />
{children}
</Container>
Now on each page I wrap my page content with this layout. Forgot to mention that these are nested routes.
Folder structure looks like this:
settings
application.tsx -> contains layout component
profile.tsx -> contains layout component
...
However this solution re-renders my tab component whenever I switch the page. Is there any way I can prevent this.

You can use the custom App file to add your TabsComponent to the whole app, like the following:
// _app.js
function MyApp({ Component, pageProps }) {
return(
<>
**<TabsComponent />**
<Component {...pageProps} />
</>
);
}
export default MyApp

Related

Using Tabs - Tab with Link from next JS (tab indicator not working)

I'm using Next js with material UI, and I have this so far
<Tabs value={value} onChange={handleChange}>
<Link href="/" passHref>
<Tab label="Home"/>
</Link>
...
</Tabs>
The active tab indicator is working without the Link component from Nextjs. But as soon as I enclose my tabs with Link component to handle the routing, the tab indicator refuses to work. No errors are thrown either.
You need to pass the props to the child and give a value to Link.
function LinkTab(props) {
return <Link {...props}>
{React.cloneElement(props.children, {...props})}
</Link>
}
function MyComponent(props) {
let tab = "tab1";
return <Tabs value={tab}>
<LinkTab value="tab1" href="/tab1"><Tab component="a" label="A Tab"/></LinkTab>
</Tabs>
}

Pass icon button as props using react

I have a common grid component where I define the structure of the grid and also structure of a button bar that goes on top of the grid.
Common-grid.js
<Box height='100%' width='100%' position='absolute'>
<div className="common-grid">
<div className="button-bar">
</div>
<div className="ag-grid">
</div>
</div>
</Box>
I pass data to the grid from my other component based to fill in the grid.
MyComponent.js
{gridData.length > 0 && <Grid tableData={gridData} columnData={activeListColumnDef} {...props}/>}
Along with the data, I would also like to pass icon buttons that I would like to see in button bar.
<IconButton
icon={<AddIcon />}
onClick={onClickOpenActiveListEditor}
/>
<IconButton
icon={<EditIcon />}
/>
I do not want to define icon buttons in the common component but pass it as props. Is it possible to pass such html elements along with its event listeners as props? Please help!
Sure, it's called a render prop. Just directly pass the node like this:
// in the parent component
<Grid
tableData={gridData}
columnData={activeListColumnDef}
icon={<AddIcon onClick={onClickOpenActiveListEditor} />}
{...props}
/>
// in the Grid component
function Grid({tableData, columnData, icon}){
return (
<>
// grid stuff
{icon && icon}
</>
)
}
If you need typescript support, the node would typed as such:
interface GridProps{
// stuff
icon?: React.ReactNode;
}
You could do something like:
const renderIcon = (onClick) => {
return <Icon onClick={onClick} />
}
...
<IconButton renderIcon={renderIcon} />
Then, inside IconButton:
{renderIcon()}

How to reset useState everytime I change my page?

I'm working on a Hamburger Navbar
here is my page look like:
the issues is whenever I clicked to another route but the navbar still appear on right side. I want to make it whenever I go to another route it will disappear.
Here is my code:
const App = (props) => {
const [ menuOpen, setMenuOpen ] = useState(false);
<HamburgerNav>
<HamburgerNavContainer>
<LogoLinkStyled to="/">
<LogoNav src="https://thebeuter.com/wp-content/uploads/2020/04/logo-black.png" />
</LogoLinkStyled>
<HamburgerUtilities>
<HamburgerUlityItem>
<Icon className="fal fa-search fa-rotate-90" onClick={openModalHandler} />
</HamburgerUlityItem>
<HamburgerUlityItem>
<Link to="/cart" style={{ color: 'black', textDecoration: 'none' }}>
<Icon className="fal fa-shopping-bag" />
<CartNumb>({props.basketProps.basketNumbers})</CartNumb>
</Link>
</HamburgerUlityItem>
<HamburgerUlityItem>
<HamburgerLine onClick={() => setMenuOpen(!menuOpen)} />
</HamburgerUlityItem>
</HamburgerUtilities>
</HamburgerNavContainer>
</HamburgerNav>
How can I fix this problem? Really appreciate it.!!!
Github project: https://github.com/nathannewyen/the-beuter
update Router code:
Here is my Router for all routes in navbar
<Router>
<ContactForm path="/contact" />
<SizeChart path="/size-chart" />
<ShippingAndReturn path="/shipping-return" />
<PrivacyAndPolicy path="/privacy-policy" />
<AboutUs path="/about-us" />
<ShopAllProducts path="/" />
<NewArrival path="/shop/new-arrival" />
<Tops path="/product-category/top" />
<Bottoms path="/product-category/bottom" />
<Product path="/product/:title_url" />
<SearchInfo path="/search/:title" searchTerm={searchTerm} title="Profile" />
<Cart path="/cart" />
<Checkout path="/checkout" />
</Router>
You need to add onClick to the "route" component that is not closing it and do something like this:
onClick={() => { setMenuOpen(prevState => {return !prevState}) }}
If for example you want it to close when you click on "T-Shirts", then "T-Shirts" must also have that onClick.
If you already have onClicks on these components with another function, you can just add multiple functions inside the anonymous function like this:
onClick={() => {
YourOtherFunction();
setMenuOpen(prevState => {return !prevState});
}
If your components are not in the App.js you need to somehow pass the onClick down too them.
Since its a state you wont be able to pass down the setMenuOpen itself, you need a wrapper function. So first create the wrapper:
const setMenuOpenWrapper = () => {
setMenuOpen(prevState => return { !prevState });
}
Then pass it down to the childrens like:
and then inside your ContactForm on the link to the contact form add the onClick:
...onClick={() => { closeMenuFunction(); }}
Ok I just checked your code, you need to pass down the function to the Sidenav component.
So in your App.js first create the wrapper function as I explained above
After that again in App.js on line 316 where you have <SideNav menuOpen={menuOpen} /> change it to <SideNav menuOpen={menuOpen} closeMenuFunction={setMenuOpenWrapper}/>
Then in your Sidenav.jsx on all of your menu items add an onclick:
onClick={props.closeMenuFunction}
And then it should work.

material-ui v4.0.1 warning "Expected an element that can hold a ref"

I upgraded to material-ui v4.0.1 and I see that Modals now require a forwarded ref. I'm having some trouble implementing a fix for this using class components and Dialogs.
I tried using React.createRef() as well as React.forwardRef((props, ref) => (...) in a few places but I can't figure out how to resolve this warning.
In my parent component I render a custom component
<ApolloFormDialog />
In ApolloFormDialog I render essentially:
<Dialog ...>
{title}
{subtitle}
{form}
</Dialog>
The full warning is Warning: Failed prop type: Invalid prop 'children' supplied to 'Modal'. Expected an element that can hold a ref. Did you accidentally use a plain function component for an element instead?
However I am using class components currently. Migrating to use function components is not an option right now as my app is rather large.
I have tried adding a ref to ApolloFormDialog as
<ApolloFormDialog ref={React.createRef()} />
as well as wrapping ApolloFormDialog's class with:
export default React.forwardRef((props, ref) => <ApolloFormDialog ref={ref} {...props}/>)
and then adding that ref to the dialog as
<Dialog ref={this.props.ref} />
but the warning still persists, and I'm not sure where to go from here.
My issue didn't actually have to do with Dialog, but with the prop TransitionComponent on Dialog.
I switch between two types of transitions in my ApolloFormDialog depending on if the screen is below a certain breakpoint, which was being called as function components:
<Dialog
open={open}
onClose={onRequestClose}
classes={{
paper: classnames(classes.dialogWidth, classes.overflowVisible),
}}
fullScreen={fullScreen}
TransitionComponent={
fullScreen ? FullscreenTransition : DefaultTransition
}
>
{content}
</Dialog>
FullscreenTransition and DefaultTransition come from a file and are defined as follows:
import React from 'react'
import Fade from '#material-ui/core/Fade'
import Slide from '#material-ui/core/Slide'
export function DefaultTransition(props) {
return <Fade {...props} />
}
export function FullscreenTransition(props) {
return <Slide direction='left' {...props} />
}
export function FullscreenExpansion(props) {
return <Slide direction='right' {...props} />
}
Changing these functions to the following fixed my issue:
import React from 'react'
import Fade from '#material-ui/core/Fade'
import Slide from '#material-ui/core/Slide'
export const DefaultTransition = React.forwardRef((props, ref) => (
<Fade {...props} ref={ref} />
))
export const FullscreenTransition = React.forwardRef((props, ref) => (
<Slide direction='left' {...props} ref={ref} />
))
export const FullscreenExpansion = React.forwardRef((props, ref) => (
<Slide direction='right' {...props} ref={ref} />
))
This was a relatively hard issue to solve on my end, so I'm going to leave this question up just in case someone else runs into a similar issue somewhere down the road.
I have had the same problem with "#material-ui/core/Tooltip" wrapping a new functional component. Even if the component was wrapped in a div inside its own code.
<!-- "Did you accidentally use a plain function component for an element instead?" -->
<Tooltip>
<NewFunctionalComponent />
</Tooltip>
<!-- Wrapped in a new div, devtools won't complain anymore -->
<Tooltip>
<div>
<NewFunctionalComponent />
</div>
</Tooltip>
<!-- No more warnings! -->
Wrapping my component to another div inside material-ui transition componet (like Slide, Fade etc) solves my issue.
Example code:
From
<Slide direction='Right' in = {isSideNavOpen} mountOnEnter unmountOnExit>
<MyComponent />
</Slide>
To
<Slide direction='Right' in = {isSideNavOpen} mountOnEnter unmountOnExit>
<div><MyComponent /></div>
</Slide>`
React.forwardRef fixes the issue for me, too.
To ensure that the React.forwardRef solution works with Typescript, you must implement the following as per the documentation in Material-UI:
const Transition = React.forwardRef<unknown, TransitionProps>(function Transition(props, ref) {
return <Slide ref={ref} {...props} />;
});
See the source code in their demo here.
I was getting the same error
expected an element that cand hold a ref
I solved by adding a <Box></Box> surrounding my children component.
From
<Modal open={open} onClose={handleClose}>
<DetailsCard />
</Modal>
To
<Modal open={open} onClose={handleClose}>
<Box>
<DetailsCard />
</Box>
</Modal>
I had a similar problem and I solved it by wrapping my functional component in a div.
In my case the problem ocurred with a Tooltip component wrapping a custom functional component.
<Tooltip tittle={...}>
<CustomFC />
</Tooltip>
And the solution was
<Tooltip tittle={...}>
<div>
<CustomFC />
</div>
</Tooltip>

How to hide the List Toolbar in react-admin?

I'm using react-admin 2.6.2 and trying currently to edit the layout of the List view. At first I wanted to remove action buttons completely, and I found the answer here at Stackoverflow. I thought, that using empty CardActions would be enough, but there's still empty ListToolbar taking space before my <List> starts. The toolbar is created by List automatically, is there any way to for example edit styles of that toolbar so I could hide it or set the height to 0px?
I guess one option is to create my custom List.js based on this, but it would be best to use the original source files, so they are also updated when there are new updates to react-admin.
JS code:
const NoneActions = props => (
<CardActions />
);
class DemoList extends Component {
render() {
return (
<div>
<List
{...props}
actions={<NoneActions />}
>
<Datagrid>
<TextField source="name" />
<ShowButton />
</Datagrid>
</List>
</div>
);
}
}
Here's the toolbar in DOM:
<div class="MuiToolbar-root-519 MuiToolbar-regular-521 MuiToolbar-gutters-520 ListToolbar-toolbar-293">
try: <List actions={null} {...props}> the empty space before the list disappears.

Resources