My app currently has three components, User to view a person's profile, Self for a user to view their dashboard, notifications, and settings and a login page.
Both User and Self share common components Nav and Side, where User would pass the self object and call the fetchUser action from redux to Nav and Side, while Self would pass the user and self object along with calling the fetchSelf action.
User.js
class User extends React.Component {
componentDidMount() {
this.props.fetchUser(this.props.username);
}
render() {
const { page, self, user } = this.props
return (
<main>
<Nav
self={self}
/>
<Side
page={page} user={user}
/>
<div>
.....
</div>
</main>
)
}
}
const mapStateToProps = state => ({
page: state.store.page,
self: state.store.self
});
export default connect(mapStateToProps, {fetchUser})(User);
Self.js
class Self extends React.Component {
componentDidMount() {
this.props.fetchSelf();
}
render() {
const { page, self } = this.props
return (
<main>
<Nav
self={self}
/>
<Side
page={page} self={self}
/>
{
tab === 'Dashboard'
? <Dashboard />
: tab === 'Notifications'
? <Notifications />
: tab === 'Settings'
? <Settings />
: null
}
</main>
)
}
}
const mapStateToProps = state => ({
page: state.store.page,
self: state.store.self
});
export default connect(mapStateToProps, {fetchSelf})(Self);
Login.js
class Login extends React.Component {
.....
handleChange = event => {
.....
}
render() {
return (
<div id="login">
.....
</div>
)
}
Side.js
const Side = (props) => {
const { page, self, user } = props;
return (
<aside>
{
page === 'user'
? <div>
<img src={'img/' + user.profile.avatar} alt={`${user.username}'s avatar`} />
</div>
: <div>
<img src={'img/' + self.profile.avatar} alt={`${self.username}'s avatar`} />
<div>
}
</aside>
)
}
What I'd like to do here is instead of using react-router like this
<BrowserRouter>
<Switch>
<Route path="/login" exact={true} component={Login} />
<Route path="/self" exact={true} component={Self} />
<Route path="/:username" component={User} />
</Switch>
</BrowserRouter>
I'd want to be able to do something like this instead
const LayoutForLoginAndSignup = (props) => {
return (
<div class="loginOrSignUp">
<ComponentToBePassedIn />
</div>
)
}
class LayoutWithNavAndSide extends React.Component {
componentDidMount() {
this.props.fetchSelf();
// this.props.fetchUser('someusername')
}
render() {
return (
<main>
<Nav self={this.props.self} />
<Side page={this.props.page} self={this.props.self} user={this.props.user} />
{Content of component that was passed in}
</main>
)
}
}
const mapStateToProps = state => ({
page: state.store.page,
self: state.store.self,
user: state.store.user
});
export default connect(mapStateToProps, {fetchUser, fetchSelf})(LayoutWithNavAndSide);
<BrowserRouter>
<Switch>
<LayoutForLoginAndSignup path="/login" exact={true} component={Login} />
<LayoutWithNavAndSide path='/self' component={Self} />
<LayoutWithNavAndSide path="/:username" component={User} />
</Switch>
</BrowserRouter>
Here's where I get confused as I'm still new to react/redux/react router, how do I get the component of User or Self to show up in the layout? how do I get it to call fetchUser (on componentDidMount) only if someone is accessing /someuser vice versa with fetchSelf only when they goto the /self route? is it possible to do the layout as a function rather than a class?
Create the component you will make a route for that contains both layouts and a condition.
const Layout = (props) => (
{props.layout ? <SelfLayout /> : <UserLayout />}
)
Create two layouts.
const SelfLayout = () => (
<div> Self Layout </div>
)
const UserLayout = () => )
<div> User Layout </div>
)
Create your route.
<Route path={`/layout/${this.state.somelayout}`} render={() => <Layout
layout={this.state.somelayout}/>} />
this.state.somelayout should be the conditional that decides which layout we are on and you can tailor it to the needs of your app this is just a guideline.
Related
I have a function App in App.js as below
function App() {
return (
<AuthProvider>
<Layout>
<Routes>
{/* Basic routes access allowed by all */}
<Route exact path={RoutesNames.GLOBAL} element={<MainHomePage />} />
<Route exact path={RoutesNames.LOGIN} element={<Login />} />
<Route exact path={RoutesNames.REGISTER} element={<Register />} />
<Route exact path={RoutesNames.CATALOGUE} element={<Catalogue />} />
{/* Protected routes access allowed by user connected */}
<Route exact path={RoutesNames.REGISTRATION_SUCCESS} element={<ProtectedRoute />}>
<Route exact path={RoutesNames.REGISTRATION_SUCCESS} element={<RegistrationSuccess />} />
</Route>
{/* Errors routes */}
<Route path="*" element={<PageNotFound />} />
</Routes>
</Layout>
</AuthProvider>
)
}
The Layout contain the Header bar and the childrens after.
Now when I login from the login page, I'm redirected to Catalogue Page. And I have my AuthProvider who set the isAuth state to true as below :
import React, { Component, createContext } from 'react'
import VerificationUserAuth from '../../utils/VerificationUserAuth'
const AuthContext = createContext()
class AuthProvider extends Component {
constructor(props) {
super(props)
this.state = {
isAuth: false,
}
}
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
render() {
if (!this.state.isAuth) {
this.checkAuth()
}
return (
<AuthContext.Provider value={{ isAuth: this.state.isAuth }}>
{this.props.children}
</AuthContext.Provider>
)
}
}
const AuthConsumer = AuthContext.Consumer
export { AuthProvider, AuthConsumer }
Now the problem is I check in my header who is envelopped by my AuthConsumer if he's authenticated or not and change the menu consequently. But each time I login, i need to refresh the page for Render the AuthProvider who check the authentification.
Btw there is my Header Bar :
export default function HeaderBar() {
return (
<AuthConsumer>
{({ isAuth }) => (
<header className="mb-5">
{isAuth ? (
<p>Connected</p>
) : (
<Navbar.Brand>
<Link to={RoutesNames.LOGIN}>
<Button className="btn btn-rounded btn-primary mr-2">Connexion</Button>
</Link>
<Link to={RoutesNames.REGISTER}>
<Button className="btn btn-rounded btn-normal">S'inscrire</Button>
</Link>
</Navbar.Brand>
)}
</Container>
</Navbar>
</header>
)}
</AuthConsumer>
)
}
So how can I re render my header after login part ? Thank's !
You need to change the isAuth state in a lifecycle component so that means whenever your app is loaded it will be initiated .
Put this function in a lifecycle method :
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
componentDidMount(){
checkAuth()
}
In a functional component :
checkAuth = () => {
const user = new VerificationUserAuth().getUserConnected()
if (user) {
this.setState({ isAuth: true })
}
}
React.useEffect(()=>{
checkAuth()
} , [])
I am having trouble with the Route path <Route path="customers/:id" render={(props) => <CusDataForm {...props}/>}/> in the code below:
import CusDataCtrl from './cusdata/CusDataCtrl'
import CusDataForm from './cusdata/CusDataForm'
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/customers" component={CusDataCtrl} />
<Route path="customers/:id" render={(props) => <CusDataForm {...props}/>}/>
</Switch>
</BrowserRouter>
);
}
}
export default App;
if I use <Route exact path="/customers/:id" component={CusDataForm} /> the component does render correctly; however, I need to pass some props down to this component.
My calling component is defined like so:
class CusDataGrid extends Component {
constructor(props) {
super(props)
this.state = {data: []}
}
componentDidMount() {
let me = this;
dbFetch("customers",data => me.setState({data:data}));
}
callEdit = e => {
let recid = e.target.getAttribute("data")
this.props.history.push("/customers/"+recid);
}
render() {
const rows = this.state.data.map((row, ndx) => {
return (
<div key={ndx}><button data={row.recordid} className="waves-effect waves-light btn-small" onClick={this.callEdit}>Edit</button></div>
);
});
return (
<div id="cusdata"><div className="data-scrollable">{rows}</div></div>
);
}
};
export default CusDataGrid;
and my target component is:
class CusDataForm extends Component{
componentDidMount = () =>{
this.setState({id: this.props.id ? this.props.id : ""});
}
render(){
return(<div>HELLO</div>)
}
}
export default CusDataForm;
Please let me know what I am doing incorrectly. Thanks!
you can use hook useParams for it
<Switch>
<Route path="/:id" children={<Child />} />
</Switch>
function Child() {
// We can use the `useParams` hook here to access
// the dynamic pieces of the URL.
let { id } = useParams();
return (
<div>
<h3>ID: {id}</h3>
</div>
);
}
official documentation
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 })} />
I am trying to create some protected in routes in React, using Create React App 2 and React Router 4. I used Tyler McGinnis's Protected Routes article as an example. Here is the my basic app component.
class App extends Component {
constructor(props) {
super(props);
this.state = { loggedIn: false };
}
componentDidMount() {
console.log('did mount');
this.setState({ loggedIn: true });
}
render() {
fakeAuth.authenticate(this.state.loggedIn);
console.log('render');
return (
<Router>
<Fragment>
<Login />
<PrivateRoute path="/register" component={Register} />
<Chordsheets />
<Chordsheet />
</Fragment>
</Router>
);
}
}
export default App;
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to="/" />
)}
/>
);
const fakeAuth = {
isAuthenticated: false,
authenticate(state) {
this.isAuthenticated = state;
console.log('isAuthenticated', this.isAuthenticated);
}
};
const Login = () => (
<div>
<Route exact path="/" component={LoginForm} />
</div>
);
const Chordsheets = () => (
<Fragment>
<Route path="/chordsheets" component={Header} />
<Route path="/chordsheets" component={AllChordSheets} />
</Fragment>
);
const Chordsheet = () => (
<Fragment>
<Route path="/chordsheet/:id" component={Header} />
<Route path="/chordsheet/:id" component={ChordSheet} />
</Fragment>
);
const Header = () => {
return (
<header>
<nav className="links">
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/chordsheets/0">My Chordsheets</Link>
</li>
</ul>
</nav>
</header>
);
};
After the component mounts loggedIn is set to true. When going to a new route render is not called again, so I cannot get to the Register route.
Does anyone have any thoughts on how to structure this? Thanks!
I know that i'm late but i'm happy to help anyone he wants this functionality by taking a look to this protected-react-routes-generator
All you're going to do is to provide the routes as an array.
I am trying to apply protected route using private route method in my graphql react app. I am using apollo client 2.
The private route works as intended, that is it protects / prevents someone from manually entering url (in this case /surveys). However, when the page is reloaded or refreshed manually, graphql query will initially return an empty object data (when user is logged in) or undefined data. Because of this, the condition for redirect inside the private route is applied hence the client is redirected to the "/" path. Is there anyway around this? Here is my app and my private route code:
/* app.js */
....imports
const WHOAMI = gql`
query whoAmI {
whoAmI {
_id
email
}
}
`;
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Query query={WHOAMI}>
{({ loading, error, data }) => {
// console.log(data);
return (
<div className="container">
<div className="col s12 center-align">
<Switch>
<Route path="/surveys/new" component={SurveyNew} />
<PrivateRoute
path="/surveys"
userId={data.whoAmI}
component={SurveyList}
/>
<Route path="/" exact component={LandingPage} />
</Switch>
</div>
</div>
);
}}
</Query>
</div>
</BrowserRouter>
</div>
);
}
}
export default App;
and my PrivateRouter file
...imports
const PrivateRoute = ({ component: Component, userId, ...rest }) => (
<Route {...rest} render={props => (
!userId || userId === 'undefined' ? (
<Redirect to={{
pathname: '/'
}}
/>
) : (
<Component {...props} />
)
)} />
);
export default PrivateRoute
I have probably the same architecture and I do
class App extends Component {
constructor (...args) {
super(...args)
this.state = {
user: {},
loading: true
}
}
componentDidMount() {
fetchUser()
.then(user => {
this.setState({
user,
loading: false
})
})
}
render () {
if (this.state.loading) {
return <LoadingPage />
} else {
return (
<ApolloProvider client={client}>
<BrowserRouter>
...
<BrowserRouter />
</ApolloProvider>
}
}