I need to handle 2 different header in my application:
Menu
A simple Bar with title
Firstly I’m thinking by using the local storage to save the menu I want to showUp (localStorage.setItem('menu', 'default’);)
So when I’m in a component who don’t need the menu but just the simple bar I just resetting the localStorage like this : localStorage.setItem('menu', ‘bar’);
But this idea (I know it's not the best) didn’t re-render my header.
What should I do to handle this case ?
In my render I have something like this :
render() {
let menu = localStorage.getItem('menu');
return (
<header>
{menu === 'bar' ? <TopBar/> : <MenuBar/>}
</header>
)
}
Your Header isn't rerendered, because its props / state aren't changed. You're changing only the localStorage and this won't rerender your component.
I can suggest to you two approaches:
1. Depending on which route you are, just use the proper Header:
// Home.js
const Home = props => <>
<MenuBar />
// The rest components of your page
</>
// Inside.js
const Inside = props => <>
<TopBar />
// The rest components of your page
</>
2. If you have a <PageLayout /> component, you can use a prop for conditionally render the Header:
<PageLayout /> is such a component, where we can reuse the page layout components composition. Every page has a header, body, footer. Instead of duplicating the same components structure in all the pages, it will be better to abstract the layout structure in <PageLayout />.
const PageLayout = ({ header === 'default', children }) => <>
<header>
{ header === 'bar' ? <TopBar /> : <MenuBar /> }
</header>
<body>
{children}
</body>
<Footer />
</>
// Home.js - Here will use the `default` Header
const Home = props => <PageLayout>
// The rest components of your page
</PageLayout
// Inside.js - Here we will use <TopBar />
const Inside = props => <PageLayout header='bar'>
// The rest components of your page
</PageLayout
Related
I've been working with Nextjs for a while and I'm trying to avoid repetitive tasks. For a dynamic site, the menu and footer which are repeated on the whole page cause me a serious problem. To avoid this, I thought of making a Wrapper that I repeat on all the pages and pass the data in props.
But what I'm doing is making a wrapper that loads the data and won't be repeated on all the pages.
Look at the picture to get an idea of what I say
https://www.figma.com/file/u4okWHg3CpbgLPpZEiYJxI/Untitled?node-id=0%3A1
Layout and wrapper are two different things.
BaseLayout used to share a common section across multiple pages.
const BaseLayout= (props) => {
const {
className,user,navClass,loading,children,noSideBar,} = props;
// Define renderer to render the view
const renderer = () => (
<div className={`baselayout ${className}`}>
{noSideBar ? "" : <SideBar />}
<main>
{/* Prop drilling here */}
<Header className={navClass} user={user} loading={loading} />
{children}
<Footer />
</main>
</div>
);
return <> {renderer()} </>;
};
export default BaseLayout;
Then you use it inside a page
const AboutMe = () => {
return (
<BaseLayout user={data} loading={loading}>
{/* YOUR PAGE COMPONENT HERRE */}
</BaseLayout>
);
};
Wrappers are used to create and support basic UI elements based on the data or prop values. These wrapper contain an instance of another class or component and provide a new way to use an object. For example provide pages to an admin
function withAuth(Component) {
return (role) => {
return (props) => {
// an hook to get the props. based on this we decide what to render
const { data, loading } = useGetUser();
if (loading) {
return <p>Loading...</p>;
}
if (!data) {
return <Redirect to="/api/v1/login" />;
} else {
// if not authorized redirect to login
if (role && !isAuthorized(data, role)) {
return <Redirect to="/api/v1/login" />;
}
// if it is authorized return compoentn
return <Component user={data} loading={loading} {...props} />;
}
};
};
}
export default withAuth;
Then maybe we have a page for only admin
const OnlyAdmin = ({ user, loading }: { user: IUser; loading: true }) => {
return (
<BaseLayout user={user} loading={loading}>
<h1>I am Admin Page - Hello {user.name}</h1>
</BaseLayout>
);
};
// we are wrapping the component to pass a new property
export default withAuth(OnlyAdmin)("admin");
What I've done for that situation is have a context that gets the header and footer data client side and store it as JSON in window.sessionStorage. Then your header and footer are only loaded when blank or when invalidated by 'something' e.g certain routes or user login status changes.
Keeps things very fast and avoids needless delays. If you are going for full SSR you could do the same thing with server side sessions.
I'm trying to use an IntersectionObserver.observe() on every single child component from a parent component, but for that I need their refs.
This is what my main component looks like:
// App component
<Nav>
<Section ref={useRef(null)} {/* other props */} />
<Section ref={useRef(null)} {/* other props */} />
<Section ref={useRef(null)} {/* other props */} />
<Section ref={useRef(null)} {/* other props */} />
</Nav>
I need the Nav component to access the ref of each Section component. I created the observer and got all children elements on a list:
// Nav component
const observer = new IntersectionObserver(observerCallback, options);
const sectionList: ReactElement<SectionProps>[] = [];
childrenToList(props.children, sectionList);
return (
<>
<nav className="nav">
{/* ... */}
</nav>
{sectionList}
</>
)
// Section component
const Section = React.forwardRef<HTMLDivElement, SectionProps>((props, ref) => {
return (
<section ref={ref} {/* other attributes */}>{props.children}</section>
);
});
This is what the childrenToList function is doing:
function childrenToList(children: ReactNode, listToPush: any[]) {
React.Children.forEach(children, (element) => {
if (!React.isValidElement(element)) return;
listToPush.push(element);
});
}
Then I need a way to do something like that (not an actual working code):
sectionList.forEach((section) => {observer.observe(section.ref.current)})
The problem is that I couldn't find a way to access the ref. I already tried having the ref as a prop, but it said it's value is null, or if I used with an if statement (like
const ref = section.props.ref
if (ref) {
observer.observe(ref.current)
}
) the page simply wouldn't render and would remain blank.
I am trying to call PopupDialog.tsx inside Content.tsx as a sibling of Item.tsx.
Previously PopupDialog.tsx is called inside C.tsx file but due to z index issue i am trying to bring it out and call it in Content.tsx
Is it possible to somehow pass the whole component(popupDialog and its parameters) in Content.tsx so that i could avoid passing back and forth the parameters needed for popupdialog in content.tsx.
Code in C.tsx where PopupDialog component is called.
const C = (props: Props) => (
<>
{props.additionalInfo ? (
<div className="infoButton">
<PopupDialog // need to take this code out and want to add in Content.tsx
icon="info"
callback={props.callback}
position={Position.Right}
>
<div className="popuplist">{props.additionalInfo}</div>
</PopupDialog>
</div>
) : (
<Button className="iconbutton"/>
)}
</>
);
Content.tsx where i would like to call PopupDialog.tsx with its parameters
const Content = (props: Props) => {
const [componentToRender, docomponentToRender] = React.useState(null);
const [isAnimDone, doAnim] = React.useState(false);
return (
<div className="ContentItems">
<PWheel agent={props.agent} />
{isAnimDone && (
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog/> //want to call here with all its parameters to be passed
}
</>
)}
</div>
);
};
Folder Structure
App.tsx
->ViewPort.tsx
->Content.tsx
->PWheel.tsx
->Item.tsx
->A.tsx
->B.tsx
->C.tsx
{props.additionalinfo &&
->PopupDialog.tsx
->PopupDialog.tsx
So if I understand the question correctly you want to pass one component into another so that you can use the properties or data of the passed componenet in your current component.
So there are three ways to achieve this.
1)Sending the data or entire component as prop.This brings disadvantage that even though components which don't require knowledge
about the passed component will also have to ask as a prop.So this is bascially prop drilling.
2)The other is you can use context api.So context api is a way to maintain global state variale.so if you follow this approach you don't need to pass data or componenet as props.Wherever you need the data you can inport context object and use it in componenet.
3)Using Redux library.This is similar to context api but only disadavantage is that we will have to write lot of code to implement this.Redux is a javascript library.
Let me know if you need more info.
You need to :
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog abc={componentToRender} /> //you must call in this component, in this case i name it is abc , i pass componentToRender state to it
}
</>
and then PopupDialog will receive componentToRender as abc, in PopupDialog , you just need to call props.abc and done .
If you need to know more about prop and component you can see it here
I think what you want to use is Higher-Order-Components (HOC).
The basic usage is:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Below is such an implementation that takes a component (with all its props) as a parameter:
import React, { Component } from "react";
const Content = WrappedComponent => {
return class Content extends Component {
render() {
return (
<>
{/* Your Content component comes here */}
<WrappedComponent {...this.props} />
</>
);
}
};
};
export default Content;
Here is the link for higher-order-components on React docs: https://reactjs.org/docs/higher-order-components.html
Make use of
useContext()
Follow this for details:
React Use Context Hook
To keep it simple, the detail page fetches data on mount based on the movie ID in the URL, this coming from path='movie/:id' in the Route.
It's child is called Recommended, which shows you recommended movies based again on the current URL.
class MovieDetailPage extends React.Component {
// Fetch movies and cast based on the ID in the url
componentDidMount() {
this.props.getMovieDetails(this.props.match.params.id)
this.props.getMovieCast(this.props.match.params.id)
}
render() {
<div>
Movies here
</div>
<Recommended id={this.props.match.params.id}/>
}
}
The Recommended component fetches data based on the current movie as well and generates another tag pointing to another movie.
class Recommended extends React.Component {
componentDidMount() {
this.props.getRecommended(this.props.id)
}
render() {
return (
<>
<Category title={'Recommended'}></Category>
<div className="movies">
{
this.props.recommended.map((movie) => {
return (
<Link key={movie.id} to={`movie/${movie.id}`} className="movies__item">
<img
key={movie.id}
src={`https://image.tmdb.org/t/p/w342${movie.poster_path}`}
className="movies__item-img"
alt={`A poster of ${movie.title}`}
>
</img>
</Link>
)
})
}
</div>
</>
)
}
}
Now how can I trigger another render of the parent component when clicking the Link generated in the Recommended component? The URL is changing but this won't trigger a render like I intent to do.
UPDATE:
<Route
path="/movie/:id"
render={(props) => (
<MovieDetailPage key={props.match.params.id}
{...props}
)}
/>
I passed in a unique key this time that triggered the re-render of the page. I tried this before but I might've screwed up the syntax.
This post got me in the right direction: Force remount component when click on the same react router Link multiple times
Add a key to the page
If you change route but your page is not getting its "mount" data then you should add a key to the page. This will cause your page to rerender and mount with the new id and get the data again.
You can read more about react keys here
A key tells react that this is a particular component, this is why you see them in on lists. By changing the key on your page you tell react that this is a new instantiation of the component and has changed. This will cause a remount.
Class component example
class MyPage extends React.Component {
componentDidMound() {
// this will fire each time the key changes since it triggers a mount
}
render() {
return (
<div key={props.pageId}>
{/* component stuff */}
</div>
)
}
}
Functional component example
const MyPage = (props) => {
React.useEffect(() => {
// this will fire each time the key changes
}, []);
return (
<div key={props.pageId}>
{/* component stuff */}
</div>
)
}
You can add another React lifecycle method that triggers on receiving new props (UNSAFE_componentWillReceiveProps, componentDidUpdate, getDerivedStateFromProps) in your Recommended component like this:
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
nextProps.getRecommended(nextProps.id);
};
}
You can also add key to your component (which forces it to re-render completely if key changed) like this:
<Recommended key={this.props.match.params.id} id={this.props.match.params.id}/>
You can also use React Hooks to handle this more easily with useEffect:
const Recommended = (props) => {
const { id, getRecommended, recommended } = props;
useEffect(() => {
id && getRecommended(id);
}, [id]);
return (
<>
<Category title={'Recommended'}></Category>
<div className="movies">
{recommended.map((movie) => {
return (
<Link key={movie.id} to={`movie/${movie.id}`} className="movies__item">
<img
key={movie.id}
src={`https://image.tmdb.org/t/p/w342${movie.poster_path}`}
className="movies__item-img"
alt={`A poster of ${movie.title}`}
></img>
</Link>
);
})}
</div>
</>
);
};
Note: adding key to component and complete its re-render is not best practice and you should be using Component's lifecycles to avoid it if possible
I have a parent component that should render another component when the URL is matches a certain path:
const View: React.SFC<Props> = ({
....
}) => {
return (
<div>
....
<Route path={jobPath} component={JobPanel} />} />
</div>
);
};
JobPanel.tsx will render if jobPath === /careers/:id which all works.
JobPanel.tsx has a link that will currently go back with this.props.history.push(/careers)
<BackLink
to="/company/careers"
onClick={(e: any) => { handleClose(); }}
>
<StyledChevron orientation={Orientation.Left} />
Go Back
</BackLink>
or
<BackLink
onClick={(e: any) => { this.props.history.push('/careers/); handleClose(); }}
>
<StyledChevron orientation={Orientation.Left} />
Go Back
</BackLink>
The problem is that JobPanel is supposed to have a transition in and out of the page with this Component:
class JobPanel extends Component {
render() {
const { isOpen, handleClose, job } = this.props;
return (
<StyledFlyout
active={isOpen}
Where isOpen is a boolean value in redux store.
While rendering JobPanel all works, I believe react-router is causing the page to re-render whenever the URL is changed. I'm not entirely sure on how to achieve no re-rendering.
Use the render prop instead of component in the Route. eg:
<Route path={jobPath} render={({ history }) => (
<JobPanel {...routeProps} />
)} />
From https://reacttraining.com/react-router/web/api/Route/component:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
For more details on using render see https://reacttraining.com/react-router/web/api/Route/render-func for more details.