React context value is not defined in ComponentDidMount - javascript

I'm using React and Context API to store the userId in a Context.
A component Authenticator is under my ContextProvider and store the userId in the context, it's working very well.
However, i want to use this userId from the context in another component MySpace in order to fetch some data about the current user. But in the ComponentDidMount() function of MySpace, this.context.userId is null. I think it's because my ContextProvider is doing a setState() to store userId and MySpace is mounted before the setState() has finished.
Here is my code, i don't know if i need to fetch data in another lifecycle method or so.
App.js
class App extends Component {
render() {
return (
<Router history={history}>
<UserContextProvider>
<Authenticator />
<Navbar />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/register' component={Register} />
<Route path='/login' component={Login} />
<PrivateRoute path='/my-space' component={MySpace} />
<Route component={NotFoundPage} />
</Switch>
</UserContextProvider>
</Router>
);
}
}
UserContext.js
export const UserContext = createContext();
class UserContextProvider extends React.Component {
state = {
userId: null
}
replaceUser = (userId) => {
this.setState({ userId: userId });
}
render() {
return (
<UserContext.Provider value={{...this.state, replaceUser: this.replaceUser}}>
{this.props.children}
</UserContext.Provider>
);
}
}
Authenticator.js
class Authenticator extends Component {
static contextType = UserContext;
componentDidMount() {
const { replaceUser } = this.context;
replaceUser(getUserId());
}
render() {
return (
<></>
);
}
}
MySpace.js
class MySpace extends Component {
static contextType = UserContext;
componentDidMount() {
document.title = "Mon espace - Todo";
this.getMyProjects();
}
getMyProjects = () => {
const { userId } = this.context
console.log(userId) // => null
_getMyProjects(userId)
.then(projects => {
console.log(projects)
})
.catch(err => alert(err))
}
render() {
return (
<p>Mon espace</p>
)
}
}

You could catch when the ContextProvider ahs the userId and is ready to fetch the data just need another prop in your state in my example ive used isReady
export const UserContext = createContext();
class UserContextProvider extends React.Component {
state = {
isReady: false,
userId: null
}
replaceUser = (userId) => {
this.setState({ userId: userId, isReady: true });
}
render() {
return (
<UserContext.Provider value={{...this.state, replaceUser: this.replaceUser}}>
{this.props.children}
</UserContext.Provider>
);
}
}
And in byour MySpace Component you need to check in componentDidUpdate if the isReady flag is true you can fetch the data with the userID:
class MySpace extends Component {
static contextType = UserContext;
componentDidMount() {
document.title = "Mon espace -
}
componentDidUpdate(){
if(this.context.isReady){
this.getMyProjects();
}
}
getMyProjects = () => {
const { userId } = this.context
console.log(userId) // => null
_getMyProjects(userId)
.then(projects => {
console.log(projects)
})
.catch(err => alert(err))
}
render() {
return (
<p>Mon espace</p>
)
}
}
I think that this could solve your issue, othe approach could be instead of creating a new prop just check if the context userId is different from null as null is your initial state

Related

Why is my component unable to access data from my reducer?

I am writing a React app in which somebody can sign up as a business or user, and a user is able to search for a business by name. I do not understand why I am getting an error when trying to render my search component, saying "TypeError: Cannot read properties of undefined (reading 'toLowerCase')". I do not understand why I am getting this error because I believe I am passing in the appropriate data via my reducers and the Redux store. This is my search component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import BusinessCard from '../Business/BusinessCard'
import { Card } from 'semantic-ui-react';
class Businesses extends Component {
state = {newSearch: ""}
handleInputChange = e => {
this.setState({newSearch: e.target.value})
}
render() {
const { businessesReducer} = this.props;
let businessesMatch = businessesReducer.businesses.filter( (business ) => business.name.toLowerCase().includes(this.state.newSearch.toLowerCase()))
return (
<div>
<input placeholder="Search Events and Services Near You" value={this.state.newSearch} name="businessName" type="text" onChange={this.handleInputChange} />
<Card.Group itemsPerRow={3}>
{ businessesMatch.map((business, id) => <BusinessCard key={id} business={business} />)}
</Card.Group>
</div>
)
}
}
const mapStateToProps = (state) => {
return ({
businessesReducer: state.businessesReducer
})
}
export default connect(mapStateToProps)(Businesses);
My businesses reducer:
const initialState =
{
businesses:[],
isLoading: false
}
export default (state = initialState, action) => {
switch (action.type) {
case 'LOADING':
return {
...state,
isLoading: true
}
case "GET_ALL_BUSINESSES_SUCCESS":
return { ...state,
businesses: action.businesses,
isLoading: false
}
default:
return state
}
}
BusinessCard.js (which I am trying to render per the user's search)
import React, { Component } from 'react';
import { Card } from 'semantic-ui-react';
import { connect } from 'react-redux';
class BusinessCard extends Component {
constructor(props) {
super(props);
}
render(){
const { business, businessesReducer } = this.props;
return(
<Card>
<div key={business.id} >
<Card.Content>
<Card.Header><strong>{business.name}</strong></Card.Header>
</Card.Content>
</div>
</Card>
)
}
}
const mapStateToProps = state => {
return {
businesses: state.businesses,
businessesReducer: state.businessesReducer
}
}
export default connect(mapStateToProps)(BusinessCard);
And App.js
import { getAllBusinesses } from './actions/business/business';
import { BrowserRouter as Router, Route, Switch} from 'react-router-dom';
import history from './history';
class App extends React.Component {
componentDidMount() {
this.props.getAllBusinesses();
}
render() {
return (
<Router history={history}>
<div className="App">
<NavBar />
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About} />
<Route path="/services" component={Services} />
<Route path="/shop" component={Shop}/>
<Route path="/login-signup" component={LoginContainer}/>
<Route path="/signup" component={Signup}/>
<Route path="/business-signup" component={BusinessSignup}/>
<Route path="/professional-signup" component={ProfessionalSignup}/>
<Route path="/search" component={Businesses}/>
</Switch>
</div>
</Router>
)
}
}
const mapStateToProps = (state) => {
return {
businessesReducer: state.businessesReducer
}
}
export default connect(mapStateToProps, {getAllBusinesses})(App);
Does anybody have any idea why my search component cannot access "business" and its properties? Everything looks correct to me.
1: It would be good if you could show getAllBusinesses.
2: Please make sure if data exists in your store, you can use redux-dev-tools for that.
3: The first time that your component renders there is no data in your store and it's just an empty array so please first check if name exists and has value then try to convert it to lower case.
It would be something like this:
let businessesMatch = businessesReducer.businesses.filter(
(business) =>
business.name &&
business.name
.toLowerCase()
.includes(this.state.newSearch.toLowerCase())
);
Or if with optional chaining:
let businessesMatch = businessesReducer.businesses.filter((business) =>
business?.name
.toLowerCase()
.includes(this.state.newSearch.toLowerCase())
);
If none of these help please provide more information like a code sandbox.

React: redux-axios: How to access the state of one component accessible to another component?

My question is a bit different from other ones on stack-overflow because I already am using Redux, and my problem is that the data is not loaded properly. Let me explain:
I have two components Bookings and Rooms, and it is present in my Routes:
class Routes extends React.Component {
render() {
return (
<Switch>
<Route path='/' exact component={Bookings} />
<Route path='/bookings' component={Bookings} />
<Route path='/rooms' component={Rooms} />
</Switch>
);
}
}
Simplified version of Bookings:
class Bookings extends React.Component {
componentDidMount(){
this.props.load(services['bookings']);
}
render() {
const bookingsList = this.props.bookings.map(booking => <p>Booking ID: {booking.id} Booking: {booking.name} Room ID: {booking.room_id}</p>)
return <>
<p>Bookings are</p>
{bookingsList}
</>
}
}
function mapStateToProps(storeState){
let bookings = storeState.bookingsState;
return { bookings : bookings };
}
function mapDispatchToProps(dispatch){
let actionDispatchers = bindActionCreators(actionCreators, dispatch);
return actionDispatchers;
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Bookings);
Rooms:
class Rooms extends React.Component {
componentDidMount(){
this.props.load(services['rooms']);
}
render() {
const roomsList = this.props.rooms.map(room => <p>Room ID: {room.id} Name: {room.name}</p>)
return <>
<p>Rooms are:</p>
{roomsList}
</>
}
}
function mapStateToProps(storeState){
let rooms = storeState.roomsState;
return { rooms : rooms };
}
function mapDispatchToProps(dispatch){
let actionDispatchers = bindActionCreators(actionCreators, dispatch);
return actionDispatchers;
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Rooms);
Each of the components were working very well independently. I wanted to use roomsState inside Bookings to resolve Room IDs to Room Names. So I added roomsState in mapStateToProps:
function mapStateToProps(storeState){
let bookings = storeState.bookingsState;
let rooms = storeState.roomsState;
return { bookings : bookings, rooms: rooms };
}
But the problem here, I am guessing is that data is not loaded into roomState till the user navigates to the Rooms component:
How should I solve it the proper react-redux way?
Call your load action in your Routes component and pass it down to your Bookings like this:
class Routes extends React.Component {
componentDidMount(){
this.props.load(services['rooms']);
}
render() {
return (
<Switch>
<Route
path='/'
render={(props) => <Bookings {...props} rooms={this.props.rooms} />}
/>
<Route
path='/bookings'
render={(props) => <Bookings {...props} rooms={this.props.rooms} />}
/>
<Route
path='/rooms'
render={(props) => <Rooms {...props} isAuthed={true} />}
/>
</Switch>
);
}
}
function mapStateToProps(storeState){
let rooms = storeState.roomsState;
let bookings = storeState.bookingsState;
return { rooms : rooms, bookings: bookings };
}
function mapDispatchToProps(dispatch){
let actionDispatchers = bindActionCreators(actionCreators, dispatch);
return actionDispatchers;
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Routes);

Unable to implement auth routes

I'm trying to learn react, and am setting up routes in my application which require you to log in. I'm trying to adapt the example given here
The code I've written should either redirect the user or display the protected route. But when I log in I'm still being redirected.
I believe the issue is in my PrivateRoute class below. I pass it a authenticated property which is set in the parent class, but it doesn't appear to update.
In app.js we declare the authenticator, where we perform async login with our backend.
I pass the checkLoggedIn function to the login component, where we set the parent's authenticated state property to true. I'm console.log()ing the state just to check it's occurring, which it is.
When I then click the Link to /protected route I'm still being redirected.
app.js
// imports ...
let authenticator = new Authenticator();
class ProtectedComponent extends Component {
render() {
return (
<h1>Protected!</h1>
);
}
}
class App extends Component {
constructor(props){
super(props);
this.state = {
authenticator: authenticator,
authenticated: authenticator.isLoggedIn(),
}
}
checkLoggedIn() {
this.setState({authenticated: true});
console.log(this.state);
}
render() {
let routes, links = null;
links = <div className="links">
<Link to="/login">Login</Link>
<Link to="/protected">Protected</Link>
</div>;
routes = <div className="routes">
<Route
path="/login"
render={() =>
<Login
authenticator={this.state.authenticator}
loginCallback={this.checkLoggedIn} />
}
/>
<PrivateRoute
path="/protected"
component={ProtectedComponent}
authenticated={this.state.authenticated}
/>
</div>;
return (
<Router className="App">
{links}
{routes}
</Router>
);
}
}
export default App;
PrivateRoute.js
// imports ....
const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
<Route {...rest} render={props =>
authenticated === true
? (<Component {...props} />)
: (<Redirect to={{
pathname: "/login",
state: { from: props.location }
}} />
)
}/>
);
export default PrivateRoute;
Login.js
// imports ...
class Login extends Component {
constructor(props) {
super(props);
this.authenticator = props.authenticator;
this.loginCallback = props.loginCallback;
this.state = {
identifier: "",
password: "",
}
}
updateState = (e, keyName = null) => {
this.setState({[keyName]: e.target.value})
}
attemptLogin = (e) => {
this.authenticator.loginPromise(this.state.identifier, this.state.password)
.then(resp => {
if(resp.data.success === true) {
this.authenticator.setToken(resp.data.api_token);
this.loginCallback();
} else {
this.authenticator.removeToken()
}
})
.catch(err => {
console.error(err);
});
}
render(){
<button onClick={this.attemptLogin}> Log In </button>
}
}
export default Login;
I'm setting the authenticated state to true in the callback method, but when I go to the protected route (and run it's render method) it appears to be evaluating to false.
If I'm misunderstanding the react props system, let me know. If you'd like to see any more of the code let me know and I'll amend the question.
You have to create a PrivateRoute HOC component first:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('bpm-user')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
and wrap your routes that most be protected:
<Switch>
<Route path="/login" component={Login} />
<PrivateRoute path="/new/index" component={NewIndex} />
<PrivateRoute path="/jobs/index" component={JobsIndex} />
<PrivateRoute path="/unions/index" component={UnionsIndex} />
<PrivateRoute exact path="/" component={ListIndex} />
<PrivateRoute exact path="/charges" component={MunicipalCharges} />
</Switch>
and use Link
<Link to="/jobs/index">Jobs</Link>
my login reducer
import axios from 'axios';
import * as actionTypes from './AuthActionTypes';
export const login = (user) => {
return dispatch => {
// for example => dispatch({type:actionTypes.REQUEST_LOGIN_USER});
axios({
method: 'post',
url: '/api/auth/login',
data: { 'username': user.username, 'password': user.password },
headers: { 'Content-Type': 'application/json;charset=utf-8' }
})
.then(res => {
localStorage.setItem('bpm-user', JSON.stringify(res.data));
dispatch({
type: actionTypes.LOGIN_USER,
payload: res.data
})
})
.catch(error => {
// TODO... for example => dispatch({type:actionTypes.FAILD_LOGIN_USER, payload:error});
})
}
}
export const logout = () => {
localStorage.removeItem('bpm-user');
}
like the example codes that i copied from my own project

Secure pages in sign in/up AWS Cognito React

I'm doing the sign in/up pages for an app, and I'm using AWS Cognito and React for the first time. I need to secure my pages until someone signs in/up. I couldn't figure out how to send anything from the userAuth() to the export default, or how to make this work.
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from 'react-router-dom';
import App from '../App';
import { Auth } from 'aws-amplify';
//last thing 333
async function userAuth() {
let something = Boolean;
Auth.currentSession()
.then(function(fulfilled) {
console.log('worked' + fulfilled);
something === true;
return something;
})
.catch(function(error) {
console.log('didnt work' + error);
window.location.href = '/';
return error;
});
}
export default ({ component: C, ...rest }) => (
alert('this is the user auth ' + userAuth()),
(
<Route
{...rest}
render={
props =>
userAuth() === 'something' ? (
<Redirect to="/" />
) : (
<C {...props} />
)
}
/>
)
);
Auth.currentAuthenticatedUser() is an asynchronous API, so you cannot return anything from this API that can be used in a Route (there is the possibility of returning a Promise, but that is not actually necessary.)
You can change the component to a class like:
class PrivateRoute extends React.Component {
constructor(props) {
super(props);
this.state = {
authStatus: false,
loading: false,
}
}
componentDidMount() {
Auth.currentAuthenticatedUser()
.then((user)=> {
this.setState({ loading: false, authStatus: true });
})
.catch(() => {
this.setState({ loading: false });
this.props.history.push('/login');
});
}
render() {
return <Route {...rest} render={(props) => (
this.state.authStatus
? <Component {...props} />
: <div>Loading ... </div>
)} />
}
}
Just fixed the problem, and thought it may help someone having the same issue !
import React from 'react';
import {
withRouter,
Switch,
Route,
Redirect,
BrowserRouter as Router
} from 'react-router-dom';
import { Auth } from 'aws-amplify';
class AppliedRoute extends React.Component {
state = {
loaded: false,
isAuthenticated: false
};
componentDidMount() {
this.authenticate();
this.unlisten = this.props.history.listen(() => {
Auth.currentAuthenticatedUser()
.then(user => console.log('user: ', user))
.catch(() => {
if (this.state.isAuthenticated)
this.setState({ isAuthenticated: false });
});
});
}
componentWillUnmount() {
this.unlisten();
}
authenticate() {
Auth.currentAuthenticatedUser()
.then(() => {
this.setState({ loaded: true, isAuthenticated: true });
})
.catch(() => this.props.history.push('/'));
}
render() {
const { component: Component, ...rest } = this.props;
const { loaded, isAuthenticated } = this.state;
if (!loaded) return null;
return (
<Route
{...rest}
render={props => {
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: '/'
}}
/>
);
}}
/>
);
}
}
AppliedRoute = withRouter(AppliedRoute);
export default AppliedRoute;

connect react redux HOC got error of `Cannot call a class as a function`

What's wrong with my HOC below? I got error of cannot call a class as function?
https://i.imgur.com/SirwcGZ.png
My HOC
const CheckPermission = (Component) => {
return class App extends Component {
componentDidMount() {
this.props.fetchUsers().then(resp => {
this.setState({user: true, loading: false});
})
}
render() {
const { user, loading } = this.props
loading && <div>Loading...</div>
!user && <Redirect to="/dashboard" />
return <Component {...this.props} />
}
}
}
export default connect(state=>state.global, {fetchUsers})(CheckPermission)
This is how I import and user CheckPermission:
<Route exact path='/dashboard' component={CheckPermission(Dashboard)} />
you can't wrap checkPermissions with a connect because it is also a HOC.
Instead you have to compose them.
import { compose } from 'redux';
...
export default compose(
connect(state=>state.global, {fetchUsers}),
CheckPermission
);

Categories

Resources