I want to render the footer component to all pages except the 404 page and request timeout page.
Should I add footer component to all page component individually?
Generally: No,
you should avoid repeating code,
in React you should use composition a lot (break the ui into a component-hierarchy).
Of course, if your App is very simple or has very few pages, it might be quicker and cleaner to just add the Footer where you want it.
You should have some kind of "page layout" component. (You probably already have one.)
Then you have different options to tell your app if the footer should be shown or not:
(A) Seperate layout components
You could use 2 (or 3) different specialized page layout components.
e.g:
// -- main layout --
export const MainPageLayout = (props) => {
return (<>
<PageHeader />
{ props.children }
</>);
};
// -- specialized layouts --
export const NormalPageLayout = (props) => {
return (<MainPageLayout>
{ props.children }
<PageFooter />
</MainPageLayout>);
};
export const ExceptionalPageLayout = (props) => {
return (<MainPageLayout>
{ props.children }
</MainPageLayout>);
};
// -- pages --
export const ExamplePage = (props) => {
return (
<NormalPageLayout>
<h1>example page</h1>
<div>example page content</div>
</NormalPageLayout>
);
};
export const Page404 = (props) => {
return (
<ExceptionalPageLayout>
<h1>404</h1>
<div>not found</div>
</ExceptionalPageLayout>
);
};
(B) use a prop
You could use the same page layout component with e.g. a "page type" property.
Even if this is shorter, I would generally not recommend this solution, because IMO it is less declarative, but I must admit that I have trouble to reason this.
e.g:
// -- layout --
export const MainPageLayout = (props) => {
return (<>
<PageHeader />
{ props.children }
{ props.pageType === 'exceptional'
? null
: <PageFooter />
}
</>);
};
// -- pages --
export const ExamplePage = (props) => {
return (
<MainPageLayout>
<h1>example page</h1>
<div>example page content</div>
</MainPageLayout>
);
};
export const Page404 = (props) => {
return (
<MainPageLayout pageType={ 'exceptional' }>
<h1>404</h1>
<div>not found</div>
</MainPageLayout>
);
};
Related
I'm covering app js with layout. I have the sidebar on the left and my pages on the right. But what I want is that the sidebar should not appear on the login page, how can I edit it?
_app.js
Layout.js
you can add a condition with pathname to showing the component or not
something like this:
const router = useRouter():
return (
...
{router.pathname !== '/login' && <Sidebar path={router.route} />}
...
)
If you have some pages that are protected and can be seen by logged in user than you would need Public and Protected Routes and you can show in your Public routes only
If this is not the case then solution mentioned by #kaveh karami is good
I'm thinking you should use Option 4 of the Persistent Layout Patterns article by Adam Wathan (the getLayout Pattern) to implement the layout and pass a prop to conditionally render the sidebar.
In this way, your Login / Register page controls the layout rendering
// Layout.js
export default function Layout({ showSidebar, children, ...rest }){
return (
...
{showSidebar && <Sidebar />}
...
)
}
export function getLayout(page, props) {
return <Layout {...props}>{page}</DefaultLayout>
}
// Login.js
import { getLayout as getDefaultLayout } from './Layout.js'
function Login(){
return (
...
)
}
Login.getLayout = page => getDefaultLayout(page, { showSidebar: true})
I would create a HOC(Higher-Order-Component) called WithSidebar:
import Main from '../../components/Main/Main';
import Sidebar from '../../components/Sidebar/Sidebar';
import classes from './WithSidebar.module.scss';
const WithSidebar = (Component) => {
return props => (
<div className={classes.WithSidebar}>
<Sidebar />
<Main className={classes.Container}>
<Component {...props} />
</Main>
</div>
);
};
export default WithSidebar;
And then export the pages that should include the sidebar like so:
import WithSidebar from '../hoc/WithSidebar/WithSidebar';
const MyPage = () => {
return (...)
}
export default WithSidebar(MyPage)
I find this approach really cleaner than conditionally rendering based on the pathname.
Background to the problem
HOCs
When dealing with Next.Js pages a common practice is to use HOC (Higher-Order Component) to avoid retyping the base of a page.
For example, an authentication HOC can be used to check if a user is authenticated or not. Depending on the outcome the user can either access the page or be redirected to a sign-in page.
Layouts
Another practice that is commonly used by Next.Js programmers is Persistent Layouts. The Persistent layout is an "area" on the page that will not re-render when the user is navigating through pages. This is great for UX (User Experience), for example, the scroll-position of a menu remains on page switch.
Some good links
NextJs.org - Persistent Layout Documentation
CheatCode.co - How to Handle Authenticated Routes (The HOC used below)
The problem
When combining these two practices a problem with persistence occurs. (Read comments inside AuthenticationRoute.js
This is a very simple index.js
import authenticatedRoute from '#components/auth/AuthenticatedRoute';
const App = () => {
return (
<section>
<h1>Logged In</h1>
<h1>App</h1>
</section>
);
};
export default authenticatedRoute(App);
The layout Layout.js
import Link from 'next/link';
import Navbar from '#components/layouts/Navbar';
import SideMenu from '#components/layouts/SideMenu';
function Layout({ children }) {
const baseUrl = '/app';
return (
<section>
<Navbar>
<li>
<Link href={`${baseUrl}/`}>Home</Link>
</li>
<li>
<Link href={`${baseUrl}/test1`}>Test 1</Link>
</li>
<li>
<Link href={`${baseUrl}/test2`}>Test 2</Link>
</li>
</Navbar>
<main>{children}</main>
</section>;
);
}
export default Layout;
And lastly the HOC AuthenticationRoute.js
import { Component as component } from 'react';
import Router from 'next/router';
import Layout from '#components/layouts/app/Layout';
const authenticatedRoute = (Component = null, options = {}) => {
class AuthenticatedRoute extends component {
state = {
loading: true,
};
componentDidMount() {
const isSignedIn = true;
if (isSignedIn) {
this.setState({ loading: false });
} else {
Router.push(options.pathAfterFailure || '/sign_in');
}
}
render() {
const { loading } = this.state;
if (loading) {
return <div />;
}
// This will return the page without the layout
return <Component {...this.props} />;
// Removing the line above and using this instead
// the page now renders with the layout. BUT...
// The layout is not persistent, it will re-render
// everytime a user navigate.
const getLayout = (page) => <Layout>{page}</Layout>;
return getLayout(<Component {...this.props} />);
}
}
return AuthenticatedRoute;
};
export default authenticatedRoute;
Without HOC
Inside index.js this is working when not calling authenticatedRoute:
App.getLayout = getLayout;
export default App;
So my guess is that the authenticatedRoute should return something else then return <Component {...this.props} />;
Trying to render state from Context API, but in console it shows as undefined and doesn't render anything.
here is Context file
import React, { useReducer, createContext } from "react"
export const GlobalStateContext = createContext()
export const GlobalDispatchContext = createContext()
const initialState = {
isLoggedIn: "logged out",
}
function reducer(state, action) {
switch (action.type) {
case "TOGGLE_LOGIN":
{
return {
...state,
isLoggedIn: state.isLoggedIn === false ? true : false,
}
}
break
default:
throw new Error("bad action")
}
}
const GlobalContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<GlobalStateContext.Provider value={state}>
{children}
</GlobalStateContext.Provider>
)
}
export default GlobalContextProvider
and here is where value should be rendered
import React, { useContext } from "react"
import {
GlobalStateContext,
GlobalDispatchContext,
} from "../context/GlobalContextProvider"
const Login = () => {
const state = useContext(GlobalStateContext)
console.log(state)
return (
<>
<GlobalStateContext.Consumer>
{value => <p>{value}</p>}
</GlobalStateContext.Consumer>
</>
)
}
export default Login
I tried before the same thing with class component but it didn't solve the problem. When I console log context it looks like object with undefined values.
Any ideas?
The Context API In General
From the comments, it seems the potential problem is that you're not rendering <Login /> as a child of <GlobalContextProvider />. When you're using a context consumer, either as a hook or as a function, there needs to be a matching provider somewhere in the component tree as its parent.
For example, these would not work:
<div>
<h1>Please log in!</h1>
<Login />
</div>
<React.Fragment>
<GlobalContextProvider />
<Login />
</React.Fragment>
because in both of those, the Login component is either a sibling of the context provider, or the provider is missing entirely.
This, however, would work:
<React.Fragment>
<GlobalContextProvider>
<Login />
</GlobalContextProvider>
</React.Fragment>
because the Login component is a child of the GlobalContextProvider.
Related To Gatsby
This concept is true regardless of what library or framework you're using to make your app. In Gatsby specifically there's a little bit of work you have to do to get this to work at a page level, but it's possible.
Let's say you have a Layout.jsx file defined, and the following page:
const Index = () => (
<Layout>
<h1>{something that uses context}</h1>
</Layout>
)
You have 2 options:
The easier option is to extract that h1 into its own component file. Then you can put the GlobalContextProvider in the Layout and put the context consumer in the new component. This would work because the h1 is being rendered as a child of the layout.
Is to do some shuffling.
You might be inclined to put the Provider in the layout and try to consume it in the page. You might think this would work because the h1 is still being rendered as a child of the Layout, right? That is correct, but the context is not being consumed by the h1. The context is being rendered by the h1 and consumed by Index, which is the parent of <Layout>. Using it at a page level is possible, but what you would have to do is make another component (IndexContent or something similar), consume your context in there, and render that as a child of layout. So as an example (with imports left out for brevity):
const Layout = ({children}) => (
<GlobalContextProvider>
{children}
</GlobalContextProvider>
);
const IndexContent = () => {
const {text} = useContext(GlobalStateContext);
return <h1>{text}</h1>;
}
const Index = () => (
<Layout>
<IndexContent />
</Layout>
);
I have already created a HOC in my react app following this, and its working fine. However i was wondering if there is a way to create a HOC as functional component(With or without state)??? since the given example is a class based component.
Tried to find the same over web but couldn't get anything. Not sure if thats even possible?? Or right thing to do ever??
Any leads will be appreciated :)
I agree with siraj, strictly speaking the example in the accepted answer is not a true HOC. The distinguishing feature of a HOC is that it returns a component, whereas the PrivateRoute component in the accepted answer is a component itself. So while it accomplishes what it set out to do just fine, I don't think it is a great example of a HOC.
In the functional component world, the most basic HOC would look like this:
const withNothing = Component => ({ ...props }) => (
<Component {...props} />
);
Calling withNothing returns another component (not an instance, that's the main difference), which can then be used just like a regular component:
const ComponentWithNothing = withNothing(Component);
const instance = <ComponentWithNothing someProp="test" />;
One way to use this is if you want to use ad-hoc (no pun intended lol) context providers.
Let's say my application has multiple points where a user can login. I don't want to copy the login logic (API calls and success/error messages) across all these points, so I'd like a reusable <Login /> component. However, in my case all these points of login differ significantly visually, so a reusable component is not an option. What I need is a reusable <WithLogin /> component, which would provide its children with all the necessary functionality - the API call and success/error messages. Here's one way to do this:
// This context will only hold the `login` method.
// Calling this method will invoke all the required logic.
const LoginContext = React.createContext();
LoginContext.displayName = "Login";
// This "HOC" (not a true HOC yet) should take care of
// all the reusable logic - API calls and messages.
// This will allow me to pass different layouts as children.
const WithLogin = ({ children }) => {
const [popup, setPopup] = useState(null);
const doLogin = useCallback(
(email, password) =>
callLoginAPI(email, password).then(
() => {
setPopup({
message: "Success"
});
},
() => {
setPopup({
error: true,
message: "Failure"
});
}
),
[setPopup]
);
return (
<LoginContext.Provider value={doLogin}>
{children}
{popup ? (
<Modal
error={popup.error}
message={popup.message}
onClose={() => setPopup(null)}
/>
) : null}
</LoginContext.Provider>
);
};
// This is my main component. It is very neat and simple
// because all the technical bits are inside WithLogin.
const MyComponent = () => {
const login = useContext(LoginContext);
const doLogin = useCallback(() => {
login("a#b.c", "password");
}, [login]);
return (
<WithLogin>
<button type="button" onClick={doLogin}>
Login!
</button>
</WithLogin>
);
};
Unfortunately, this does not work because LoginContext.Provider is instantiated inside MyComponent, and so useContext(LoginContext) returns nothing.
HOC to the rescue! What if I added a tiny middleman:
const withLogin = Component => ({ ...props }) => (
<WithLogin>
<Component {...props} />
</WithLogin>
);
And then:
const MyComponent = () => {
const login = useContext(LoginContext);
const doLogin = useCallback(() => {
login("a#b.c", "password");
}, [login]);
return (
<button type="button" onClick={doLogin}>
Login!
</button>
);
};
const MyComponentWithLogin = withLogin(MyComponent);
Bam! MyComponentWithLogin will now work as expected.
This may well not be the best way to approach this particular situation, but I kinda like it.
And yes, it really is just an extra function call, nothing more! According to the official guide:
HOCs are not part of the React API, per se. They are a pattern that emerges from React’s compositional nature.
Definitely you can create a functional stateless component that accepts component as an input and return some other component as an output, for example;
You can create a PrivateRoute component that accepts a Component as a prop value and returns some other Component depending on if user is authenticated or not.
If user is not authenticated(read it from context store) then you redirect user to login page with <Redirect to='/login'/>else you return the component passed as a prop and send other props to that component <Component {...props} />
App.js
const App = () => {
return (
<Switch>
<PrivateRoute exact path='/' component={Home} />
<Route exact path='/about' component={About} />
<Route exact path='/login' component={Login} />
<Route exact path='/register' component={Register} />
</Switch>
);
}
export default App;
PrivateRoute.jsx
import React, { useContext , useEffect} from 'react';
import { Route, Redirect } from 'react-router-dom'
import AuthContext from '../../context/auth/authContext'
const PrivateRoute = ({ component: Component, ...rest }) => {
const authContext = useContext(AuthContext)
const { loadUser, isAuthenticated } = authContext
useEffect(() => {
loadUser()
// eslint-disable-next-line
}, [])
if(isAuthenticated === null){
return <></>
}
return (
<Route {...rest} render={props =>
!isAuthenticated ? (
<Redirect to='/login'/>
) : (
<Component {...props} />
)
}
/>
);
};
export default PrivateRoute;
Higher Order Components does not have to be class components, their purpose is to take a Component as an input and return a component as an output according to some logic.
The following is an over simplified example of using HOC with functional components.
The functional component to be "wrapped":
import React from 'react'
import withClasses from '../withClasses'
const ToBeWrappedByHOC = () => {
return (
<div>
<p>I'm wrapped by a higher order component</p>
</div>
)
}
export default withClasses(ToBeWrappedByHOC, "myClassName");
The Higher Order Component:
import React from 'react'
const withClasses = (WrappedComponent, classes) => {
return (props) => (
<div className={classes}>
<WrappedComponent {...props} />
</div>
);
};
export default withClasses;
The component can be used in a different component like so.
<ToBeWrappedByHOC/>
I might be late to the party but here is my two-cent regarding the HOC
Creating HOC in a true react functional component way is kind of impossible because it is suggested not to call hook inside a nested function.
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in-depth below.)
Rules of Hooks
Here is what I have tried and failed
import React, { useState } from "react";
import "./styles.css";
function Component(props) {
console.log(props);
return (
<div>
<h2> Component Count {props.count}</h2>
<button onClick={props.handleClick}>Click</button>
</div>
);
}
function Component1(props) {
console.log(props);
return (
<div>
<h2> Component1 Count {props.count}</h2>
<button onClick={props.handleClick}>Click</button>
</div>
);
}
function HOC(WrapperFunction) {
return function (props) {
const handleClick = () => {
setCount(count + 1);
};
const [count, setCount] = useState(0);
return (
<WrapperFunction handleClick={handleClick} count={count} {...props} />
);
}
}
const Comp1 = HOC((props) => {
return <Component {...props} />;
});
const Comp2 = HOC((props) => {
return <Component1 {...props} />;
});
export default function App() {
return (
<div className="App">
<Comp1 name="hel" />
<Comp2 />
</div>
);
}
CodeSandBox
Even though the code works in codesandbox but it won't run in your local machine because of the above rule, you should get the following error if you try to run this code
React Hook "useState" cannot be called inside a callback
So to go around this I have done the following
import "./styles.css";
import * as React from "react";
//macbook
function Company(props) {
return (
<>
<h1>Company</h1>
<p>{props.count}</p>
<button onClick={() => props.increment()}>increment</button>
</>
);
}
function Developer(props) {
return (
<>
<h1>Developer</h1>
<p>{props.count}</p>
<button onClick={() => props.increment()}>increment</button>
</>
);
}
//decorator
function HOC(Component) {
// return function () {
// const [data, setData] = React.useState();
// return <Component />;
// };
class Wrapper extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<Component count={this.state.count} increment={this.handleClick} />
);
}
}
return Wrapper;
}
const NewCompany = HOC(Company);
const NewDeveloper = HOC(Developer);
export default function App() {
return (
<div className="App">
<NewCompany name={"Google"} />
<br />
<NewDeveloper />
</div>
);
}
CodeSandbox
I think for functional component this works fine
import {useEffect, useState} from 'react';
// Target Component
function Clock({ time }) {
return <h1>{time}</h1>
}
// HOC
function app(C) {
return (props) => {
const [time, setTime] = useState(new Date().toUTCString());
useEffect(() => {
setTimeout(() => setTime(new Date().toUTCString()), 1000);
})
return <C {...props} time={time}/>
}
}
export default app(Clock);
You can test it here: https://codesandbox.io/s/hoc-s6kmnv
Yes it is possible
import React, { useState } from 'react';
const WrapperCounter = OldComponent =>{
function WrapperCounter(props){
const[count,SetCount] = useState(0)
const incrementCounter = ()=>{
SetCount(count+1)
}
return(<OldComponent {...props} count={count} incrementCounter={incrementCounter}></OldComponent>)
}
return WrapperCounter
}
export default WrapperCounter
import React from 'react';
import WrapperCounter from './WrapperCounter';
function CounterFn({count,incrementCounter}){
return(
<button onClick={incrementCounter}>Counter inside functiona component {count}</button>
)
}
export default WrapperCounter(CounterFn)
Outer component (page layout):
var Layout = React.createClass({
render() {
return (
<div className="container">
<header>
<h1>{this.props.title}</h1>
</header>
<section>
{this.props.children}
</section>
</div>
);
}
});
Component one (page 1):
var PageOne = React.createClass({
render() {
return (
<Layout title="Component One">
<p>This is component one.</p>
</Layout>
);
}
});
Component two (page 2):
var PageTwo = React.createClass({
render() {
return (
<Layout title="Component Two">
<p>This is component two.</p>
</Layout>
);
}
});
Now, if we render these components to document.body dynamically, based on which page user is located (using HTML5 History API), how would that impact performance (as opposed to switching just Page components without re-rendering the outer (layout) component)?
var React = require('react');
var {Router} = require('director');
var render = (page) => { React.renderComponent(page(), document.body); };
var routes = {
'/page-one': () => { render(require('./pages/PageOne')); },
'/page-two': () => { render(require('./pages/PageTwo')); }
};
Router(routes).configure({html5history: true}).init();
P.S.: The HTML markup in these sample components is intentionally simplified. On StackOverflow.com example, there could be page components such as Questions, Tags, Users, Badges, AskQuestion, all contained inside a layout component which itself contains header, footer, navigation, sidebar.
You can't do this without rerender because this two page components are not similar. You can avoid lot of mutations if you create one component with some calculated parameters.
Component Page:
var Page = React.createClass({
render() {
return (
var _component_title = "Component" + this.props.componentName
<Layout title={_component_title}>
<p>This is {_component_title}</p>
</Layout>
);
}
});
Router:
var Page = require('./pages/Page')
var render = (page) => { React.renderComponent(Page({componentName: page}), document.body); };
var routes = {
'/page-one': () => { render('One'); },
'/page-two': () => { render('Two'); }
};
React will rerender only nodes with component name.
But you no need wory about rerender, React do this really fast.