I have a layout where I get the user data and then a few pages.
what´s the correct way to use user data from the layout in the child components?
this is my Main component
const Main = () => {
return (
<Router>
<Route path="/" exact strict component={Homepage} />
<Layout>
<Route path="/friends" exact strict component={Friends} />
<Route path="/family" exact strict component={Family} />
</Layout>
</Router>
)
}
this is the layout
const Layout (props) => {
const [user, setUser] = useState({})
useEffect(() => {
getUser()
.then(setUser)
.catch(console.log('redirect to login page'))
}, [])
return(
<div>
<header>welcome {user.name}</header>
<div>{props.children}</div>
<footer>
<NavLink to="/family">Family</Navlink>
<NavLink to="/friends">Friends</Navlink>
</footer>
)
}
const Family = () => {return (<p>family</p>)}
const Friends = () => {return (<p>friends</p>)}
I´m not sure how to change the Main component to route those 2 urls to the components but using the Layout.
also this is rendering the layout evenn if I´m in the homepage
I´m wrapping Friends and Family in the Layout but it should be the other way around? how to avoid the render of the layout if im not in one of the pages that should use it?
Use render prop, pass in the user value from child(Layout) and call the children in Main component.
Also use a Switch component to avoid rendering the Layout component when you are on HomePage
Main Component
const Main = () => {
return (
<>
<Switch>
<Route path="/" exact strict component={Homepage} />
<Layout>
{(user) => <>
<Route path="/friends" exact strict render={(props) => <Friends {...props} user={user} />} />
<Route path="/family" exact strict render={(props) => <Family {...props} user={user} />} />
</>}
</Layout>
</Switch>
</>
)
}
Layout Component
const Layout = (props) => {
const [user, setUser] = useState({})
useEffect(() => {
getUser()
.then(setUser)
.catch(console.log('redirect to login page'))
}, []);
return(
<div>
<header>welcome {user.name}</header>
<div>{props.children(user)}</div>
<footer>
<NavLink to="/family">Family</Navlink>
<NavLink to="/friends">Friends</Navlink>
</footer>
</div>)
}
Issue: Rendering the layout even if I´m in the homepage
Solution: This is because Layout component is not a part of route. You need to use Switch
Issue Passing user prop to Family and Friends from Layout component
Solution
You can render Friends and Family as renderProp to route by defining the routes inside Layout component and passing user as prop.
const Layout = props => {
const [user, setUser] = useState({});
useEffect(() => {
getUser()
.then(setUser)
.catch(console.log('redirect to login page'))
}, []);
return (
<div>
<header>welcome {user.name}</header>
<Route path="/friends" exact strict render={(props) => <Friends {...props} user={user}/>} />
<Route path="/family" exact strict render={(props) => <Family {...props} user={user}/>} />
<footer>
<NavLink to="/family">Family</NavLink>
<NavLink to="/friends">Friends</NavLink>
</footer>
</div>
);
};
const Main = () => {
return (
<Router>
<Switch>
<Route path="/" exact strict component={Homepage} />
<Route component={Layout} />
</Switch>
</Router>
);
};
Solution 2:
Make use of context. In order to use context, ContextProvider needs to be in the hierarchy so route components need to be rendered directly inside the Layout component. In such case this can be used as below. Context is especially useful when your components have children and you need to use user down in the hierarchy
const UserContext = React.createContext(null);
const Friends = () => {
const user = React.useContext(UserContext);
return (
<div>
Friends<div>{user.name}</div>
</div>
);
};
const Family = props => {
const user = React.useContext(UserContext);
return (
<div>
Family<div>{user.name}</div>
</div>
);
};
const Layout = props => {
const [user, setUser] = useState({});
useEffect(() => {
// getUser()
// .then(setUser)
// .catch(console.log('redirect to login page'))
setUser({ name: "test name" });
}, []);
return (
<UserContext.Provider value={user}>
<div>
<header>welcome {user.name}</header>
<Route path="/friends" exact strict component={Friends} />
<Route path="/family" exact strict component={Family} />
<footer>
<NavLink to="/family">Family</NavLink>
<NavLink to="/friends">Friends</NavLink>
</footer>
</div>
</UserContext.Provider>
);
};
Related
I have been on it for a while now, researching useRef, forwardRef just to be able to add the Skip Navigation Link to my React project. Since multiple sources say that ElementById and querySelector are not the correct way to do it, I ended up forwarding a ref to ALL my routes. Then on each page I forward the ref further to a RefMain component. It all seems like a massive complication for a small accessibility feature.
const App = () => {
const beginningOfMainRef = useRef(null);
const handleFocusMain = () => beginningOfMainRef.current.focus();
return (
<Theme>
<GlobalStyle />
<SkipNavigationButton onClick={handleFocusMain}>
Skip Navigation Links
</SkipNavigationButton>
<Header />
<Routes>
<Route path="/" element={<HomePage ref={beginningOfMainRef} />} />
<Route
path="/distributors"
element={<DistributorsPage ref={beginningOfMainRef} />}
/>
<Route
path="/contacts"
element={<ContactsPage ref={beginningOfMainRef} />}
/>
</Routes>
<Footer />
</Theme>
);
};
export default App;
Than in each page:
const AboutPage = forwardRef((props, ref) => {
return (
<RefMain ref={ref}>
<h1>hi, i'm about</h1>
</RefMain>
);
});
export default AboutPage;
And finally:
const RefMain = forwardRef((props, ref) => {
return (
<Main>
<BeginningOfMain tabIndex="-1" ref={ref}>
{props.title} main content
</BeginningOfMain>
{props.children}
</Main>
);
});
export default RefMain;
This is of course a simplified example with a reduced number of Pages. Looking at the routes with passed ref, they are anything, but dry. Am I missing some neat trick here? I tried using useRef within the RefMain component, but that left me unable to pass the ref to the handler function higher in the tree (App.js).
Since you apply RefMain in each routed component it would better to abstract it as a layout route component instead of a wrapper component around each page's content. Instead of rendering the children prop it renders an Outlet component for nested routes to render their content into.
const RefMain = forwardRef((props, ref) => {
const [title, setTitle] = React.useState();
return (
<Main>
<BeginningOfMain tabIndex="-1" ref={ref}>
{title} main content
</BeginningOfMain>
<Outlet context={{ setTitle }} />
</Main>
);
});
export const RefWrapper = ({ children, title }) => {
const { setTitle } = useOutletContext();
React.useEffect(() => {
setTitle(title);
}, [title]);
return children;
};
export default RefMain;
const AboutPage = () => {
return (
<h1>hi, i'm about</h1>
);
}
export default AboutPage;
You can use another ref and div element to set the focus back to the top of the page when the page location updates.
const App = () => {
const topRef = useRef();
const beginningOfMainRef = useRef(null);
const location = useLocation();
useEffect(() => {
topRef.current.scrollIntoView();
topRef.current.focus();
}, [location.pathname]);
const handleFocusMain = () => beginningOfMainRef.current.focus();
return (
<Theme>
<div ref={topRef} tabIndex={-1} />
<GlobalStyle />
<SkipNavigationButton onClick={handleFocusMain}>
Skip Navigation Links
</SkipNavigationButton>
<Header />
<Routes>
<Route element={<RefMain ref={beginningOfMainRef} />}>
<Route path="/" element={<HomePage />} />
<Route
path="/distributors"
element={(
<RefWrapper title="Distributors">
<DistributorsPage />
</RefWrapper>
)}
/>
<Route
path="/contacts"
element={(
<RefWrapper title="Contacts">
<ContactsPage />
</RefWrapper>
)}
/>
</Route>
</Routes>
<Footer />
</Theme>
);
};
export default App;
I didn't stand up any sandbox to test this but it is my belief it should be close to the desired behavior you describe. If there are issues let me know and I'll work on a live demo.
This isn't a react answer but a skip link should be very simple. They're typically the first DOM element on the page and all it does is link to a spot further down on the page. A skip link does not link to a different page.
skip to main
...
<main id="skip" tabindex="-1">
stuff on the page
</main>
It all seems like a massive complication for a small accessibility feature.
Are you applying your personal preference as to how useful that small accessibility feature is? Do you use a sip-and-puff device? Do you get worn down by how many times you have to puff to navigate through all the items in a top level menu just to get to the main part of the page?
I am using React Router v6 in an application. I have a layout page, which uses an outlet to then show the main content. I would also like to include a title section that changes based on which path has been matched, but I am unsure how to do this.
function MainContent() {
return (
<div>
<div>{TITLE SHOULD GO HERE}</div>
<div><Outlet /></div>
</div>
);
}
function MainApp() {
return (
<Router>
<Routes>
<Route path="/projects" element={<MainContent />} >
<Route index element={<ProjectList />} title="Projects" />
<Route path="create" element={<CreateProject />} title="Create Project" />
</Route>
<Routes/>
</Router>
);
}
Is something like this possible? Ideally, I would like to have a few other props besides title that I can control in this way, so a good organization system for changes like this would be great.
The most straightforward way would be to move the title prop to the MainContent layout wrapper and wrap each route individually, but you'll lose the nested routing.
An alternative could be to create a React context to hold a title state and use a wrapper component to set the title.
const TitleContext = createContext({
title: "",
setTitle: () => {}
});
const useTitle = () => useContext(TitleContext);
const TitleProvider = ({ children }) => {
const [title, setTitle] = useState("");
return (
<TitleContext.Provider value={{ title, setTitle }}>
{children}
</TitleContext.Provider>
);
};
Wrap the app (or any ancestor component higher than the Routes component) with the provider.
<TitleProvider>
<App />
</TitleProvider>
Update MainContent to access the useTitle hook to get the current title value and render it.
function MainContent() {
const { title } = useTitle();
return (
<div>
<h1>{title}</h1>
<div>
<Outlet />
</div>
</div>
);
}
The TitleWrapper component.
const TitleWrapper = ({ children, title }) => {
const { setTitle } = useTitle();
useEffect(() => {
setTitle(title);
}, [setTitle, title]);
return children;
};
And update the routed components to be wrapped in a TitleWrapper component, passing the title prop here.
<Route path="/projects" element={<MainContent />}>
<Route
index
element={
<TitleWrapper title="Projects">
<ProjectList />
</TitleWrapper>
}
/>
<Route
path="create"
element={
<TitleWrapper title="Create Project">
<CreateProject />
</TitleWrapper>
}
/>
</Route>
In this way, MainContent can be thought of as UI common to a set of routes whereas TitleWrapper (you can choose a more fitting name) can be thought of as UI specific to a route.
Update
I had forgotten about the Outlet component providing its own React Context. This becomes a little more trivial. Thanks #LIIT.
Example:
import { useOutletContext } from 'react-router-dom';
const useTitle = (title) => {
const { setTitle } = useOutletContext();
useEffect(() => {
setTitle(title);
}, [setTitle, title]);
};
...
function MainContent() {
const [title, setTitle] = useState("");
return (
<div>
<h1>{title}</h1>
<div>
<Outlet context={{ title, setTitle }} />
</div>
</div>
);
}
...
const CreateProject = ({ title }) => {
useTitle(title);
return ...;
};
...
<Router>
<Routes>
<Route path="/projects" element={<MainContent />}>
<Route index element={<ProjectList title="Projects" />} />
<Route
path="create"
element={<CreateProject title="Create Project" />}
/>
</Route>
</Routes>
</Router>
I was facing the same issue for a left-right layout: changing sidebar content and main content, without repeating styling, banner, etc.
The simplest approach I found was to remove nested routing, and create a layout component in which I feed the changing content through properties.
Layout component (stripped for this post):
export function Layout(props) {
return (
<>
<div class="left-sidebar">
<img id="logo" src={Logo} alt="My logo" />
{props.left}
</div>
<div className='right'>
<header className="App-header">
<h1>This is big text!</h1>
</header>
<nav>
<NavLink to="/a">A</NavLink>
|
<NavLink to="/b">B</NavLink>
</nav>
<main>
{props.right}
</main>
</div>
</>
);
}
Usage in react router:
<Route path="myPath" element={
<Layout left={<p>I'm left</p>}
right={<p>I'm right</p>} />
} />
Another solution is to use the handle prop on the route as described in the useMatches documentation.
import { useMatches } from "react-router-dom";
function MainContent() {
const matches = useMatches()
const [title] = matches
.filter((match) => Boolean(match.handle?.title))
.map((match) => match.handle.title);
return (
<div>
<div>{title}</div>
<div><Outlet /></div>
</div>
);
}
function MainApp() {
return (
<Router>
<Routes>
<Route path="/projects" element={<MainContent />} >
<Route index element={<ProjectList />} handle={{ title: "Projects" }} />
<Route path="create" element={<CreateProject />} handle={{ title: "Create Project" }} />
</Route>
<Routes/>
</Router>
);
}
Need help with pagination. Right now my app can change page, but if I want send request like /character?page=4 it always throw me /character?page=1 this is not help, coz I use router. I have no idea how to resolve my problem
My app.js
function App() {
return (
<>
<Router>
<Header />
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/character' component={AllCharacters} />
<Route component={NotFound} />
</Switch>
<Footer />
</Router>
</>
);
}
CharacterList.jsx
// API Data
const url = "https://rickandmortyapi.com/api/character";
// Fetching Page
const fetchPage = (page) => {
// Init loading while page load
setLoading(true);
const query = `${url}?page=${page}`;
fetchData(query);
setLoading(false);
};
// Change pages
const { push } = useHistory();
const handleChanger = (event, page) => {
fetchPage(page);
push({
pathname: "/character",
search: `?page=${page}`,
});
};
<Pagination
count={info.pages}
showLastButton
showFirstButton
onChange={handleChanger}
/>
Dynamic Pagination : App.js
function App() {
return (
<>
<Router>
<Header />
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/character/page=:page' component={DynamicPagination} />
<Route component={NotFound} />
</Switch>
<Footer />
</Router>
</>
);
}
Dynamic pagination Demo : DynamicPaginaton.js
export default function DynamicPagination() {
const history = useHistory();
const [page, setPage] = React.useState(0);
const [pageCount, setPagcount] = useState(10);
const handlePageChange = (event, value) => {
history.push(`/character/page=${value}`)
setPage(value);
};
return (
<div >
<Typography>page:{page}</Typography>
<Pagination
count={pageCount}
page={page}
onChange={handlePageChange}
style={{ marginTop: 20 }}
/>
</div>
);
}
Im trying to figure out how to structure a Router to use different routes for admin, user and public.
I have seen this post and the answer describing a key cloak - but I haven't been able to make sense of it.
I've seen this code sandbox which looks logical to me, but I'm having trouble incorporating it.
I have a constants file where I and defining routes as:
export const NEWBLOG = '/admin/newblog';
export const VIEWBLOG = '/viewblog';
I'm importing that into my App.js and then wanting to define different consts for Admin, User and Public as follows:
import * as ROUTES from '../../util/constants/Routes';
import NewBlog from '../../components/blog/admin/New';
// admin routes
const Admin = ({ match }) => (
<React.Fragment>
<Route path={`${match.path}/${ROUTES.NEWBLOG}`} component={NewBlog} />
<Route path={`${match.path}/2`} render={() => <h2>test</h2>} />
</React.Fragment>
);
// authenticated user routes
const Other = ({ match }) => (
<React.Fragment>
<Switch>
<Route path={`${match.path}/2`} render={() => <h2>one</h2>} />
<Route path={`${match.path}/2`} render={() => <h2>two</h2>} />
</Switch>
</React.Fragment>
);
// public routes
const Public = ({ match }) => (
<React.Fragment>
<Switch>
<Route path={`${match.path}/2`} render={() => <h2>one</h2>} />
<Route path={`${match.path}/2`} render={() => <h2>two</h2>} />
</Switch>
</React.Fragment>
);
Then inside the router statement I have:
const App = () => (
<Router>
<Navigation />
<Switch>
<Route path="/a" component={Admin} />
<Route path="/u" component={Other} />
<Route path="/p" component={Public} />
<Route
component={({ location }) => {
return (
<div
style={{
padding: "50px",
width: "100%",
textAlign: "center"
}}
>
<ErrorMessage />
</div>
);
}}
/>
</Switch>
</Router>
);
export default App;
This all works until I try to use the routes constants inside the back ticks part of the Admin constant.
I can't seem to use that approach.
Can anyone help with a source of reference materials to find a way through this?
There are few things you need to know
Child Routes will render only when the Parent route path is matched
For the Child Route the path needs to be the path that matched the parent + the child route path
You can write wrappers over route which are responsible for deciding if the user is authenticated or an admin
In all such scenarios you need to store the user authentication state within state, context or redux store.
When you render the Route in Admin like
<Route path={`${match.path}/${ROUTES.NEWBLOG}`} component={NewBlog} />
The path to the component actually becomes /a/admin/newBlog which is actually incorrect
Overall you can change your code to something like this
App.js
const App = () => (
<Router>
<Navigation />
<Switch>
<Route path="/admin" component={Admin} />
<Route path="/user" component={Other} />
<Route path="/public" component={Public} />
</Switch>
</Router>
);
AuthRoute.js
const AuthRoute = (props) => {
const {path, match, component: Component, render, ...rest} = props;
const {user, isLoading} = useContext(AuthContext); // Assuming you use context to store route, you can actually get this values from redux store too.
return (
<Route
{...rest}
path={`${match.path}${path}`}
render={(routerProps) => {
if(isLoading) return <div>Loading...</div>
if(!user) return <div>Not Authenticated</div>
return Component? <Component {...rest} {...routerProps} /> : render(routerProps)
}}
/>
}
An adminRoute needs to both check whether the user is admin as well as check if he is authenticated or not so you component would look like
AdminRoute.js
const AdminRoute = (props) => {
const {path, match, ...rest} = props;
const {user, isLoading} = useContext(AuthContext); // Assuming you use context to store route, you can actually get this values from redux store too.
return (
<Route
{...rest}
path={`${match.path}${path}`}
render={(routerProps) => {
if(isLoading) return <div>Loading...</div>
if(!user) return <div>Not Authenticated</div>
if(user.role !== "admin") return <div>Need to be an admin to access this route</div>
return Component? <Component {...rest} {...routerProps} /> : render(routerProps)
}}
/>
}
Now you can use the above two components to separate out the Admin and Auth Routes
Also keep in mind that AuthRoutes and public routes paths cannot be the same
Route constants
export const NEWBLOG = '/newblog';
export const VIEWBLOG = '/viewblog';
Routes
import * as ROUTES from '../../util/constants/Routes';
import NewBlog from '../../components/blog/admin/New';
// admin routes
const Admin = (props) => (
<React.Fragment>
<AdminRoute {...props} path={ROUTES.NEWBLOG} component={NewBlog} />
<AdminRoute {...props} path='/2' render={() => <h2>test</h2>} />
</React.Fragment>
);
// authenticated user routes
const Other = (props) => (
<React.Fragment>
<Switch>
<AuthRoute {...props} path={'/3'} render={() => <h2>one</h2>} />
<AuthRoute {...props} path={'/4'} render={() => <h2>two</h2>} />
</Switch>
</React.Fragment>
);
// public routes
const Public = ({ match }) => (
<React.Fragment>
<Switch>
<Route path={`${match.path}/5`} render={() => <h2>one</h2>} />
<Route path={`${match.path}/6`} render={() => <h2>two</h2>} />
</Switch>
</React.Fragment>
);
I have this Router, any page that should has the Layout is wrapped with the withLayout HOC.
I need to pass to some of the pages the user context, how can I add a user prop?
const withLayout = () => Component => props => (
<div css={pageWrap}>
<Header user={props.user} />
<Component {...props} />
</div>
);
export default function Router() {
return (
<AuthConsumer>
{({ user }) => (
<Switch>
<Route exact path="/" component={withLayout()(Home, { user })} />
<Route exact path="/page1" component={withLayout()(Page1)} />
<Route exact path="/page2" component={withLayout()(Page2)} />
</Switch>
)}
</AuthConsumer>
);
}
I think you have problem with your withLayout. It should be:
const withLayout = () => (Component, props = {}) => (
<div css={pageWrap}>
<Header user={props.user} />
<Component {...props} />
</div>
);
What's AuthConsumer?
You could use contextType = AuthContext inside your page components. [from]
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* render something based on the value */
}
}
I was able to get this to work like this:
<Route exact path="/" render={props => withLayout()(Home)({ ...props, user })} />