Props only showing under inline if statement in react redux - reactjs

Been fooling around with some code and came into something tricky. Currently I am showing user data if the user isAuthenticated. However, I have to put this.props.user ? in the inline statement. Otherwise, this.props.user comes back as undefined, although, with this.props.user ? it works.
Here is the code
// import React from "react";
// import { connect } from "react-redux";
// import { getUser } from "../store/actions/userActions";
// import { withRouter } from 'react-router-dom';
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom'
import * as actions from '../store/actions/auth'
class UserDetailView extends React.Component {
componentDidMount(){
this.props.onTryAutoSignup()
this.props.getfetchUser(this.props.username)
console.log(this.props)
const user = this.props.user
}
render(){
return(
<div>
{
this.props.user//need this or the below become undefined && this.props.isAuthenticated ?
<div>
Welcome {this.props.user.username} {this.props.user.email}
</div>
:
<div><Link to='/login/'>Login</Link></div>
}
</div>
)
}
}
const mapStateToProps = state => {
return{
isAuthenticated: state.token !== null,
user: state.user,
username: state.username
}
}
const mapStateToDispatch = (dispatch) => ({
logout: () => dispatch(actions.logout()),
onTryAutoSignup: () => dispatch(actions.authCheckState()),
getfetchUser: id => dispatch( actions.fetchUser( id ) )
})
export default connect(mapStateToProps, mapStateToDispatch)(UserDetailView);

That is simply because on the "first load" of the component the user data is not valid (I assume, I can't see your reducers)
So let's say your reducer sets the default user as null
now this component is loaded
componentDidMount dispatches and calls getfetchUser
getfetchUser will take some time...
the component render is not waiting so it will call render method
but user is not set yet as you didn't get the response from the server
so on the first render, you see user as null
but as soon as ajax call returns it sets the user and you are good
so that's why you see this "weird" behavior, but that's how it is in the current implementation.
You can try different tricks to make sure you are good, like:
stick to what you did
add a loading flag, so you could know you are waiting for a response from the server. This is the better option because you could handle errors from the server.

You need a check because you are accessing the username and email attributes of the user object.
Assuming on initial render,
user = {} // this wont have properties initially
isAuthenticated = true
You try to access the email and username, which don't exist at that point of time.
To avoid this,
Maybe you can pass the isAuthenticated inside your user object itself. This will maintain integrity.

Related

Is there any onAuthStateChanged equivalent for Firestore Data? i.e onDataFieldChanged

Im updating the App context when the user auth changes
However, I need to change the context also when the user data on the firestore DB is changed
For instance:
The context in my app store the User Doc.
Each user document has a count field. When the user is finished to do some action the count is changed and I need to change the context to the updated user doc.
How can I do it?
Here is my Context file:
import React, { Component, createContext } from 'react'
import { auth, getUserDocument } from './firebase/firebase.utils'
export const UserContext = createContext({ user: null })
class UserProvider extends Component {
state = {
user: null,
}
componentDidMount = async () => {
auth.onAuthStateChanged(async (userAuth) => {
const user = await getUserDocument(userAuth)
this.setState({ user })
})
}
render() {
return (
<UserContext.Provider value={this.state.user}>
{this.props.children}
</UserContext.Provider>
)
}
}
export default UserProvider
Firestore offers you Snapshot Listeners, which are nothing but real-time event listeners fot your data.
In your case, you can simply add a Snapshot Listener on your document and you will get an event whenever the document changes. The snapshot gives you a DocumentSnapshot which is the updated document. Read this for all the details.

How to pass state to React JS High Order Component

I am using OIDC redux connector for user state. I have a few components that require authentication. I would like to use something like export default connect(mapStateToProps, mapDispatchToProps)(withAuth(Component)); and request data from state inside my authentication service.
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { push } from 'connected-react-router'
export const withAuth = (Component) => {
return props => {
return <Component {...props} />
}
}
Is it possible to get state in the render function? So I can check the user beinig logged in and redirect to the sign-in page if there is no user signed in?
BTW: How would I redirect? I have tried using redirect-router-dom and <Redirect /> But then it complains about set state being changed too often ... But that might be my mistake. I get this error when I render a Redirect: Error: Maximum update depth exceeded.
If I understand correctly you want to "decorate" your component with additional logic to handle an authentication redirect?
I suggest using a "decorator" pattern here e.g.:
export const withAuth = (Component) => {
const mapStateToProps = (state, ownProps) => ({
authenticated: state.authenticated // Or what you need to do to determine this
});
return connect(mapStateToProps)(class extends React.Component {
render() {
const { authenticated, ...componentProps } = props;
if (authenticated) {
return <Component {...componentProps }>;
}
return <Redirect to="/login" />;
}
});
}
Then when you need this you can do things like:
export default withAuth(connect(yourOwnMapStateToProps)(YourComponent))
Just figured it out, I changed the store so instead of returning a function, it returns the object. So I can load in all js files. It might not be the best solution. If there is a better way to get the store in code, I would love to hear about how to do that. The configurestore function is what I found in quite a lot of examples.
import { store } from '../configureStore';
Using store.getState() I can get the current state.
The redirect issue I am having is similar to: How to use react-transition-group with react-router-dom

How to get access to state with higher order component

I want to create a higher order component that checks if a user has been logged in. If they have, I show the component if not, I want to redirect them to the login page.
can someone explain what I'm doing wrong here?
Here's the HOC:
import React from 'react';
import { connect } from 'react-redux';
const withAuthentication = (Component) => {
class WithAuthentication extends React.Component {
componentDidMount() {
console.log(this.props.sessionId);
}
render() {
return this.props.sessionId ? <Component {...this.props} /> : null;
}
}
const mapStateToProps = (state) => ({
sessionId: state.auth.userInfo.authUserInfo.sessionId
})
return connect(mapStateToProps, null)(WithAuthentication);
}
export default withAuthentication;
then I call it like this:
....
import withAuthentication from './containers/withAuthentication';
const Hello = () => <h1> Hello</h1>;
....
<Route path="/" component={ withAuthentication(Hello) }/>
I stripped more code that I think is unrelated to this...
TIA for you help!
Update: The code that is causing the propblem seems to be this:
const mapStateToProps = (state) => ({
sessionId: state.auth.userInfo.authUserInfo.sessionId
})
The error: TypeError: Cannot read property 'sessionId' of undefined
So basically, what you are doing in nesting one component into another i.e. a Functional component returning another component after some logical level verification.
What you are doing seems good. You should be able to access the props(not State), in a functional component like this
let aComponent(props)=>{
return <p>{props.name}'s logged in status is {props.loggedIn}</p>
}
if you are using the component like this
<aComponent title="Hello" loggedIn={this.props.isLoggedIn}/> //isLoggedIn from redux store
Also, If the Logical/Authentication verification fails in withAuthentication, you should call the router API to navigate to your desired page.
i.e.
you should call this, if you are using react-router
this.props.router.push('/login')

Prevent routing in React when user manually changes url in browser tab

I am stuck in a issue that happens when user manually changes the route in browser tab and presses enter. This forces my react router to navigate to the state entered by user. I want to prevent this and allow routing only through the flow I have implemented by button clicks in my website.
Some of my screens need data that will be available only if the user navigates the site using the flow expected. If user directly tries to navigate to a particular route by manually changing the route in url then he may skip the desired flow and hence the app will break.
Other scenario, in case I want to restrict some users from accessing some routes but the user knows the path and manually enters that in browser url then he will be presented with that screen but should not be.
What I do is use a prop from previous page, if that prop is undefined(meaning user did not follow due process :) hehe ) I simply send the user back to the landing page or wherever.
You can create a route guard using HOC. For example, you don't want unauthorized user to pass route /profile, then you can do the following:
// requireAuthorized.js (HOC)
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {Redirect} from 'react-router-dom'
const connector = connect(
state => ({
isAuthorized: state.profile !== null // say, you keep user profile in redux
})
)
export default (WrappedComponent) => {
return (
connector(
class extends Component {
static propTypes = {
isAuthorized: PropTypes.bool.isRequired
}
render () {
const {isAuthorized, ...clearedProps} = this.props
if (isAuthorized) {
return <WrappedComponent {...clearedProps} />
} else {
return <Redirect to={{pathname: '/login'}} />
}
}
}
)
)
}
// ProfilePage.jsx
import React from 'react'
...
import requireAdmin from '../hocs/requireAdmin' // adjust path
class ProfilePage extends React.Component {
...
render () {
return (
<div>
...
</div>
)
}
}
export default requireAdmin(ProfilePage)
Pay attention to the export statement in my ProfilePage.js
I'd suggest using this library for cleanest solution (or at least make personal similar implementation of it).
Then you'd create authentication check HOC:
export const withAuth = connectedReduxRedirect({
redirectPath: '/login',
authenticatedSelector: state => state.user.isAuthenticated, // or whatever you use
authenticatingSelector: state => state.user.loading,
wrapperDisplayName: 'UserIsAuthenticated'
});
And you could easily create flow HOC:
export const withFlow = (step) = connectedReduxRedirect({
redirectPath: '/initial-flow-step',
authenticatedSelector: state => state.flow[step] === true,
wrapperDisplayName: 'FlowComponent'
});
Then initialize your component
const AuthenticatedComponent = withAuth(Dashboard)
const SecondStepComponent = withFlow("first-step-finished")(SecondStep)
const ThirdStepComponent = withFlow("second-step-finished")(ThirdStep)
You can easily create authenticated flow step by composing HOC:
const AuthSecondStepComponent = withAuth(withFlow("first-step-finished")(SecondStep))
Only thing that is important is that you update your redux state correctly as going through your step flow. When user finishes first step you'd set
state.flow["first-step-finished"] = true // or however you manage your state
so that when user navigates manually to specific page, he wouldn't have that redux state because its an in-memory state and would be redirected to redirectPath route.
Something like this is suitable. You make HOC Route with a wrap to function that deals with authentication/context props.
Note: this deals with direct access to the route, not to the menu items and such. That must be treated in a simmilar way on the menu / menuItem components.
import requireAuth from "../components/login/requireAuth";
class Routes extends React.Component<RoutesProps, {}> {
render() {
return (
<div>
<Switch>
<Route exact={true} path="/" component={requireAuth(Persons, ["UC52_003"])} />
<Route path="/jobs" component={requireAuth(Jobs, ["UC52_006"])} />
</Switch>
</div>
)
}
}
export default function (ComposedComponent, privileges) {
interface AuthenticateProps {
isAuthenticated: boolean
userPrivileges: string[]
}
class Authenticate extends React.Component<AuthenticateProps, {}> {
constructor(props: AuthenticateProps) {
super(props)
}
render() {
return (
isAuthorized(this.props.isAuthenticated, privileges, this.props.userPrivileges) &&
<ComposedComponent {...this.props} /> || <div>User is not authorised to access this page.</div>
);
}
}
function mapStateToProps(state) {
return {
isAuthenticated: state.userContext ? state.userContext.isAuthenticated : false,
userPrivileges: state.userContext ? state.userContext.user ? state.userContext.user.rights : [] : []
};
}
return connect(mapStateToProps, null)(Authenticate);
}
you can put the condition in useEffect of the given page/screen and push it back if it doesnt have the required values.. example below

How to dispatch actions from Child components three level down?

I am currently facing this issue designing a React application and I don't seem to be able to find an answer to it.
So my application has following heirarchy of Components in React Router
App
-> DynamicContainer
-> -> LoginComponent
Now, LoginComponents has form elements to take username and password.
I have userActionCreators where the login is handled, and it dispatches login successful when finished, but I don't seem to be able find the right way to connect my LoginComponent to dispatch actions or call actionCreators.
How do I do it? Any suggestion would be appreciated.
One option is to bind your single-purpose forms to their actions with connect. Since <LoginComponent /> is typically always doing the exact same thing, you can use it like this:
import React from 'react';
import * as userActionCreators from '../actions/users';
import { connect } from 'react-redux';
export class LoginComponent extends React.Component {
static propTypes = {
login: React.PropTypes.func.isRequired
}
render() {
const { login } = this.props;
const { username, password } = this.state;
return (
<form onSubmit={ () => login(username, password) }>
...
</form>
);
}
}
export default connect(null, userActionCreators)(LoginComponent);
connect automatically binds the action creator and separately provides dispatch to props, so if you want to be more explicit, the above example is the same as
import React from 'react';
import { login } from '../actions/users';
import { connect } from 'react-redux';
export class LoginComponent extends React.Component {
static propTypes = {
dispatch: React.PropTypes.func.isRequired
}
render() {
const { login, dispatch } = this.props;
const { username, password } = this.state;
return (
<form onSubmit={ () => dispatch(login(username, password)) }>
...
</form>
);
}
}
export default connect()(LoginComponent);
And for reference, userActionCreators:
const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
const LOGIN_FAILED = 'LOGIN_FAILED';
export function login(username, password) {
if (username === 'realUser' && password === 'secretPassword') {
return { type: LOGIN_SUCCESS, payload: { name: 'Joe', username: 'realUser' } };
} else {
return { type: LOGIN_FAILED, error: 'Invalid username or password };
}
}
if I understood you correctly, if you read Example: Todo List | Redux you'll find the example that you might be looking for.
There's the App component, connect()ed to Redux, and then there're the other components: AddTodo, TodoList, Todo and Footer.
App calls TodoList that calls Todo where user can click something. This click will surf back callback after callback, from Todo to TodoList to App as detailed below:
App calls TodoList with
<TodoList todos={visibleTodos} onTodoClick={ index => dispatch(completeTodo(index)) } />
TodoList calls Todo with
<Todo {...todo} key={index} onClick={ () => this.props.onTodoClick(index) } />
Todo component has a <li> with onClick={this.props.onClick} property.
So, backwards, when someones clicks inside the Todo compoment, that will call this.props.onClick which will call this.props.onTodoClick(index) from TodoList (notice the optional added parameter index), then, at last, this will invoke the function dispatch(completeTodo(index)) from App.
Two options:
Pass a bound actionCreator from your Container (which is connected to Redux) down to a child component (which is not connected to Redux) via the props object.
Consider adopting React Component Context in your project.

Resources