React <SecuredRoute .../> not returning this.props - reactjs

My components are displayed with data from the server if the route isn't secured. As soon as I secure the route, this.props returns undefined.
App.js
import React, { Component } from 'react';
import {Route, withRouter} from 'react-router-dom';
import auth0Client from './Auth/Auth';
import NavBar from './NavBar/NavBar';
import Callback from './Callback/Callback';
import Customers from './Customers/Customers';
import Customer from './Customer/Customer';
import SecuredRoute from './SecuredRoute/SecuredRoute';
class App extends Component {
constructor(props) {
super(props);
this.state = {
checkingSession: true,
}
}
async componentDidMount() {
if (this.props.location.pathname === '/callback') {
this.setState({checkingSession:false});
return;
}
try {
await auth0Client.silentAuth();
this.forceUpdate();
} catch (err) {
if (err.error !== 'login_required') console.log(err.error);
}
this.setState({checkingSession:false});
}
render() {
return (
<div>
<NavBar/>
<Route exact path='/callback' component={Callback}/>
<SecuredRoute exact path='/customers'
component={CustomersComponent}
checkingSession={this.state.checkingSession}
/>
<SecuredRoute exact path='/customers/:customerId'
component={CustomerComponent}
checkingSession={this.state.checkingSession}
/>
{/*<Route exact path='/customers/:customerId' component={CustomerComponent}/>*/} // this works
</div>
);
}
}
const CustomersComponent = (props) => {
return (
<Customers {...props}
/>
);
}
const CustomerComponent = (props) => {
return (
<Customer {...props}
/>
);
}
export default withRouter(App);
Auth.js
import auth0 from 'auth0-js';
class Auth {
constructor() {
this.auth0 = new auth0.WebAuth({
domain: 'domain.eu.auth0.com',
audience: 'https://domain.eu.auth0.com/userinfo',
clientID: 'clientID',
redirectUri: 'http://localhost:3000/callback',
responseType: 'id_token',
scope: 'openid profile'
});
this.getProfile = this.getProfile.bind(this);
this.handleAuthentication = this.handleAuthentication.bind(this);
this.isAuthenticated = this.isAuthenticated.bind(this);
this.signIn = this.signIn.bind(this);
this.signOut = this.signOut.bind(this);
}
getProfile() {
return this.profile;
}
getIdToken() {
return this.idToken;
}
isAuthenticated() {
return new Date().getTime() < this.expiresAt;
}
signIn() {
this.auth0.authorize();
}
handleAuthentication() {
return new Promise((resolve, reject) => {
this.auth0.parseHash((err, authResult) => {
if (err) return reject(err);
if (!authResult || !authResult.idToken) {
return reject(err);
}
this.setSession(authResult);
resolve();
});
})
}
setSession(authResult) {
this.idToken = authResult.idToken;
this.profile = authResult.idTokenPayload;
// set the time that the id token will expire at
this.expiresAt = authResult.idTokenPayload.exp * 1000;
}
signOut() {
this.auth0.logout({
returnTo: 'http://localhost:3000',
clientID: 'clientID',
});
}
silentAuth() {
return new Promise((resolve, reject) => {
this.auth0.checkSession({}, (err, authResult) => {
if (err) return reject(err);
this.setSession(authResult);
resolve();
});
});
}
}
const auth0Client = new Auth();
export default auth0Client;
Customers.js
import React, {Component} from 'react';
import {Link} from 'react-router-dom';
import axios from 'axios';
class Customers extends Component {
constructor(props) {
super(props);
this.state = {
accounts: null,
customers: null
};
}
async componentDidMount() {
const customers = (await axios.get('http://localhost:8081/customers/')).data;
const accounts = (await axios.get('http://localhost:8081/accounts/')).data;
this.setState({
accounts,
customers
});
}
countAccounts(custid) {
const accounts = this.state.accounts;
let count = 0;
accounts.forEach(function(element) {
if (element.CustomerId === custid) {
count = count + 1;
}
});
return count;
}
countOverdueAccounts(custid) {
const accounts = this.state.accounts;
let count = 0;
accounts.forEach(function(element) {
if (element.CustomerId === custid && element.CurrentStatus !== "Active") {
count = count + 1;
}
});
return count;
}
listOfAccounts(custid) {
const accounts = this.state.accounts;
let arr = [];
accounts.forEach(function(element) {
if (element.CustomerId === custid) {
arr.push(<p key={element.id}>{element.AccountNumber}</p>)
}
});
return arr;
}
overdueCard(custid) {
const accounts = this.state.accounts;
let cardTheme = "card text-white bg-primary mb-3";
accounts.forEach(function(element) {
if (element.CustomerId === custid && element.CurrentStatus !== "Active") {
cardTheme = "card text-white bg-danger mb-3";
}
});
return cardTheme;
}
render() {
return (
<div className="container">
<div className="row">
{this.state.customers === null && <p>Loading customer records...</p>}
{
this.state.customers && this.state.customers.map(customer => (
<div key={customer.id} className="col-sm-12 col-md-4 col-lg-3">
<Link to={`/customers/${customer.id}`}>
<div className={this.overdueCard(customer.id)}>
<div className="card-header">
<p>Accounts: {this.countAccounts(customer.id)}</p>
<p>Overdue accounts: {this.countOverdueAccounts(customer.id)}</p>
</div>
<div className="card-body">
<h4 className="card-title">Current status: {customer.CurrentStatus}</h4>
<p className="card-text">{customer.FirstName} {customer.Surname}</p>
<p className="card-text">ID number: {customer.NationalIDNumber}</p>
<p className="card-text">Contact number: {customer.ContactNumber}</p>
<p className="card-text">Email: {customer.EmailAddress}</p>
<div className="card-text">List of accounts:{this.listOfAccounts(customer.id)}</div>
</div>
</div>
</Link>
</div>
))
}
</div>
</div>
)
}
}
export default Customers;
Customer.js
import React, {Component} from 'react';
import axios from 'axios';
import SubmitUpdate from './SubmitUpdate';
import auth0Client from '../Auth/Auth';
class Customer extends Component {
constructor(props) {
super(props);
this.state = {
account: null,
customer: null
};
this.submitUpdate = this.submitUpdate.bind(this);
}
async componentDidMount() {
await this.refreshCollection();
}
async refreshCollection() {
//console.log('this.props: ', this.props);
const { match: { params } } = this.props; // <----- fails here as this.props is not returned
//console.log(`http://localhost:8081/customers/${params.customerId}`);
const customer = (await axios.get(`http://localhost:8081/customers/${params.customerId}`)).data;
const account = (await axios.get(`http://localhost:8081/accounts/${params.customerId}`)).data;
this.setState({
account: account,
customer: customer
});
}
async submitUpdate(update){
await axios.post(`http://localhost:8081/update/${this.state.customer.id}`, {
update,
}, {
headers: { 'Authorization': `Bearer ${auth0Client.getIdToken()}` }
});
await this.refreshCollection();
}
render() {
const {customer} = this.state;
const {account} = this.state;
//console.log('customer: ', customer);
//console.log('account: ', account);
if (customer === null) return <p>Loading... </p>;
return (
<div className="container">
<div className="row">
<div className="jumbotron col-12">
<p className="card-text">{customer[0].FirstName} {customer.Surname}</p>
<p className="card-text">ID number: {customer[0].NationalIDNumber}</p>
<p className="card-text">Contact number: {customer[0].ContactNumber}</p>
<p className="card-text">Email: {customer[0].EmailAddress}</p>
<hr className="my-4" />
<SubmitUpdate accountId={customer.id} submitUpdate={this.submitUpdate} />
<p>Notes</p>
</div>
</div>
</div>
);
}
}
export default Customer;
SecuredRoute.js
import React from 'react';
import {Route} from 'react-router-dom';
import auth0Client from '../Auth/Auth';
function SecuredRoute(props) {
const {component: Component, path, checkingSession} = props;
return (
<Route path={path} render={() => {
if (checkingSession) return <h3 className="text-center">Validating session...</h3>;
if (!auth0Client.isAuthenticated()) {
auth0Client.signIn();
return <div></div>;
}
return <Component />
}} />
);
}
export default SecuredRoute;
Please help me understand what I'm doing wrong. I'm sure it's something simple, I just can't figure it out.

Make sure to pass on the component props as well as your Router props.
Like this
import React from 'react';
import {Route} from 'react-router-dom';
import auth0Client from '../Auth/Auth';
function SecuredRoute(props) {
const {component: Component, path, checkingSession, ...rest} = props;
return (
<Route path={path} render={(routerProps) => { // <---- get the props
if (checkingSession) return <h3 className="text-center">Validating session...</h3>;
if (!auth0Client.isAuthenticated()) {
auth0Client.signIn();
return <div></div>;
}
return <Component {...rest} {...routerProps}/> // <---- include the routerProps
}} />
);
}
export default SecuredRoute;

Related

Am I awaiting incorrectly or is there another issue?

I'm new to React, but not development. I'm in my first independent project since training. I'm using redux.
I've created a simple list app that requires login. Login is working. BUT. If I login, then refresh, the redux store loses everything. So I'm setting the user's token in session storage to overcome this issue, so that I can get the user's data from the database with the token if they refresh.
The problem seems to be that when I use axios to get the user's data from the database based on the token, it's not awaiting properly and then continues to process the page and bombs because it doesn't have the user_id yet. It's bombing because I'm trying to access user_id from this.props.auth.userId.user_id and it doesn't have it for some reason.
App.js
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import { connect } from 'react-redux';
import ListItemCreate from './listitems/listItemCreate';
import ListItemEdit from './listitems/listItemEdit';
import ListItemDelete from './listitems/listItemDelete';
import ListItemList from './listitems/listItemList';
import ListItemShow from './listitems/listItemShow';
import Header from './Header';
import history from '../history';
import Login from './Login/Login';
import Register from './Login/Register';
import { signInWithToken } from '../actions';
const setToken = (userToken) => {
sessionStorage.setItem('token', JSON.stringify(userToken));
}
const getToken = () => {
//sessionStorage.removeItem('token');
const tokenString = sessionStorage.getItem('token');
if(tokenString){
const userToken = JSON.parse(tokenString);
console.log('The Token for you, madam: ' +userToken.token);
return userToken?.token;
}
return null;
}
class App extends React.Component {
//state = { isSignedIn: null };
render(){
const isSignedIn = this.props.auth.isSignedIn;
const curToken = getToken();
console.log('curToken: '+curToken);
if(curToken && !isSignedIn){
console.log('need signInWithToken');
this.props.signInWithToken(curToken);
}
if(isSignedIn){
console.log('because isSignedIn is TRUE');
//console.log(this.props.auth);
if(!curToken){setToken({ 'token': this.props.auth.userId.token });}
const user_id = this.props.auth.userId.user_id;
const email = this.props.auth.userId.email;
const token = this.props.auth.userId.token;
console.log('isSignedId:' + isSignedIn);
console.log('user_id:'+user_id);
console.log('email:' + email);
console.log('token:' + token);
console.log('getting isSigned in');
if (isSignedIn) {
console.log('it has VALUE');
console.log(this.props.isSignedIn);
} else {
console.log('no isSignedIn');
}
}
if(curToken){console.log('curToken:' + curToken);}
//const token = getToken();
//console.log();
if(isSignedIn || curToken){
console.log('i HAVE a token');
return(
<div className="ui container">
<Router history={history}>
<div>
<Header />
<Switch>
<Route path="/" exact component={ListItemList} />
<Route path="/listitems/new" exact component={ListItemCreate} />
<Route path="/listitems/edit/:list_item_id" exact component={ListItemEdit} />
<Route path="/listitems/delete/:list_item_id" exact component={ListItemDelete} />
<Route path="/listitems/:list_item_id" exact component={ListItemShow} />
</Switch>
</div>
</Router>
</div>
);
}else{
console.log('NO token');
return(
<div className="ui container">
<Router history={history}>
<div>
<Header />
<Switch>
<Route path="/" exact component={Login} setToken={setToken} />
<Route path="/register" exact component={Register} setToken={setToken} />
</Switch>
</div>
</Router>
</div>
);
}
}
}
const mapStateToProps = (state) => {
return { auth: state.auth };
};
export default connect(mapStateToProps, { signInWithToken })(App);
From the action creator (but not the whole file):
export const signInWithToken = (token) => async (dispatch) => {
console.log('signInWithToken');
let data = new FormData();
data.append('grant_type', 'signinwithtoken');
data.append('token', token);
const response = await user.post(`theurl/token`, data)
.then(response => {
console.log('and the response is'); // << It's showing me this in the console just AFTER the error where it needs to access the user_id
console.log(response.data);
dispatch({ type: SIGN_IN_WITH_TOKEN, payload: response.data });
history.push('/');
return {
type: SIGN_IN_WITH_TOKEN,
payload: response.data
};
});
};
The Reducer:
import { SIGN_IN, SIGN_OUT, LOGIN, REGISTER, SIGN_IN_WITH_TOKEN } from '../actions/types';
const INITIAL_STATE = {
isSignedIn: null,
userId: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case REGISTER:
return { ...state, isSignedIn: true, userId: action.payload };
case LOGIN:
return { ...state, isSignedIn: true, userId: action.payload };
case SIGN_IN:
return { ...state, isSignedIn: true, userId: action.payload };
case SIGN_OUT:
return { ...state, isSignedIn: false, userId: null };
case SIGN_IN_WITH_TOKEN:
return { ...state, isSignedIn: true, userId: action.payload };
default:
return state;
}
};
Here is ListItemList that is rendered as long as there's a user_id:
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchListItems, markListItemCompleted } from '../../actions';
import MultiSort from '../MultiSort';
class ListItemList extends React.Component {
componentDidMount(){
console.log('in ListItemList');
console.log(this.props);
const id = this.props.currentUserId;
if(id){
this.props.fetchListItems(id);
}else{
}
}
markCompleted(listItem){
console.log('in markCompleted');
console.log(listItem);
// you need to pass the data, but completion datetime needs to be set
let currentdate = new Date();
let datetime = currentdate.getDate() + "/"
+ (currentdate.getMonth()+1) + "/"
+ currentdate.getFullYear() + " "
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds();
console.log(datetime);
this.props.markListItemCompleted(listItem.list_item_id);
}
renderAdmin(listItem){
// if(listItem.userId === this.props.currentUserId){
return (
<div className="right floated content">
<button className="ui button secondary" onClick={() => this.markCompleted(listItem)} >Mark {listItem.completion_label}</button>
<Link to={`/listItems/edit/${listItem.list_item_id}`} className="ui button primary">Edit</Link>
<Link to={`/listItems/delete/${listItem.list_item_id}`} className="ui button negative">Delete</Link>
</div>
);
//}
}
formatCompletedDT(dt = null){
if(dt === "0000-00-00 00:00:00"){return "Never";}
let date = new Date(dt);
let day = date.getDate();
let month = date.getMonth() + 1;
let year = date.getFullYear();
let hour = date.getHours();
let minute = date.getMinutes();
let second = date.getSeconds();
return month + '/' + day + '/' + year + ' ' + hour + ':' + minute + ':' + second;
}
renderList(){
const noNulls = this.props.listItems.map(listItem => {
if(!listItem.last_completed_dt){
listItem.last_completed_dt = "0000-00-00 00:00:00";
}
return listItem;
});
const sortedList = MultiSort(noNulls, { last_completed_dt: 'asc', list_item: 'asc'});
return sortedList.map(listItem => {
const lastCompleted = this.formatCompletedDT(listItem.last_completed_dt);
// if(listItem.userId === this.props.currentUserId){
return (
<div className="item" key={listItem.list_item_id}>
{this.renderAdmin(listItem)}
<i className="large middle aligned icon check circle" />
<div className="content">
<Link to={`/listItems/${listItem.list_item_id}`} className="header">
{listItem.list_item}
</Link>
<div className="description" style={{ color: 'grey', fontSize: '9pt' }} >Last {listItem.completion_label}: {lastCompleted}</div>
</div>
</div>
);
// }
});
}
renderCreateButton(){
//if(this.props.isSignedIn){
return(
<div style={{ textAlign: 'right' }}>
<Link to="/listItems/new" className="ui button primary">
Create List Item
</Link>
</div>
);
//}
}
render(){
return (
<div>
<h2>List Items</h2>
<div className="ui celled list">{this.renderList()}</div>
{this.renderCreateButton()}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
listItems: Object.values(state.listItems),
currentUserData: state.auth.userId,
isSignedIn: state.auth.isSignedIn,
auth: state.auth
};
};
export default connect(mapStateToProps, { fetchListItems, markListItemCompleted })(ListItemList);
And the action creator for ListItemList:
export const fetchListItems = (userId) => async dispatch => {
const response = await listItems.get(`/listItemsMaintenance?user_id=${userId}`);
dispatch({ type: FETCH_LIST_ITEMS, payload: response.data });
};
I trust that if my plan is horrendously flawed, you'll tell me.
Your problem is this if statement: if(isSignedIn || curToken){ when it renders with token but not yet signed in then you try to render as if you already have the user information. Implement setting a loading value in your reducer and set it to true in your signInWithToken action before doing async and set it to false when the promise is done (fail or success).

What's wrong with my method call I try to learn React and must pass component and Props to child

I learn Reactjs and javascript and wanted to call this withFetching Component but don't understand how to set the arguments correctly. I understand overall logic but still learning the details
Here is the switch where I call the withFetching
render() {
const theFile = encodeURI(`./images/${fileData}`);
switch (mediaType) {
case 'xlsx': {
const newProps = { ...this.props, responseType: 'arraybuffer' };
return (
<div className="pg-viewer-wrapper">
<div className="pg-viewer" id="pg-viewer">
<{withFetching(XlsxViewer, newProps, fileType="xlsx", filePath={theFile} )}/>
</div>
</div>
);
}
.........
I try like this also:(making WithFetching camel-case even it's a function)
return (
<div className="pg-viewer-wrapper">
<div className="pg-viewer" id="pg-viewer">
<WithFetching XlsxViewer={XlsxViewer} newProps={newProps} />
</div>
</div>
);
But the WithFetching constructor never firers!
I try like this:
case 'xlsx': {
const newProps = { ...this.props, responseType: 'arraybuffer', fileType: 'xlsx', filePath: { theFile } };
// return withFetching(XlsxViewer, newProps);
return (
<div className="pg-viewer-wrapper">
<div className="pg-viewer" id="pg-viewer">
{WithFetching(XlsxViewer, newProps)};
</div>
</div>
);
}
But still the WithFetching constructor never firers!
Error: (yea I know the way I use brackets are my mistake it's hard to learn)
And this is the withFetching that is in its own file called fetch-wrapper.jsx. The WrappedComponent argument is the above XlsxViewer that is the final "On-screen" Component!
import React, { Component } from 'react';
import Error from './error';
import Loading from './loading';
function withFetching(WrappedComponent, props) {
return class FetchComponent extends Component {
constructor(props) {
// eslint-disable-line no-shadow
super(props);
this.state = {};
this.xhr = this.createRequest(props.filePath);
}
componentDidMount() {
try {
this.fetch();
} catch (e) {
if (this.props.onError) {
this.props.onError(e);
}
this.setState({ error: 'fetch error' });
}
}
componentWillUnmount() {
this.abort();
}
createRequest(path) {
let xhr = new XMLHttpRequest();
if ('withCredentials' in xhr) {
// XHR for Chrome/Firefox/Opera/Safari.
xhr.open('GET', path, true);
// } else if (typeof XDomainRequest !== 'undefined') {
// // XDomainRequest for IE.
// xhr = new XDomainRequest();
// xhr.open('GET', path);
} else {
// CORS not supported.
xhr = null;
return null;
}
if (props.responseType) {
xhr.responseType = props.responseType;
}
xhr.onload = () => {
if (xhr.status >= 400) {
this.setState({ error: `fetch error with status ${xhr.status}` });
return;
}
const resp = props.responseType ? xhr.response : xhr.responseText;
this.setState({ data: resp });
};
return xhr;
}
fetch() {
this.xhr.send();
}
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
render() {
if (!this.xhr) {
return <h1>CORS not supported..</h1>;
}
if (this.state.error) {
return <Error {...this.props} error={this.state.error} />;
}
if (this.state.data) {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
return <Loading />;
}
};
}
export default withFetching;
And this the final XlxsViewer Component that will be visible.
Thanks to Copyright (c) 2017 PlanGrid, Inc.
import React, { Component } from 'react';
import XLSX from 'xlsx';
import CsvViewer from './csv-viewer';
class XlxsViewer extends Component {
constructor(props) {
super(props);
this.state = this.parse();
}
parse() {
const dataArr = new Uint8Array(this.props.data);
const arr = [];
for (let i = 0; i !== dataArr.length; i += 1) {
arr.push(String.fromCharCode(dataArr[i]));
}
const workbook = XLSX.read(arr.join(''), { type: 'binary' });
const names = Object.keys(workbook.Sheets);
const sheets = names.map(name => XLSX.utils.sheet_to_csv(workbook.Sheets[name]));
return { sheets, names, curSheetIndex: 0 };
}
renderSheetNames(names) {
const sheets = names.map((name, index) => (
<input
key={name}
type="button"
value={name}
onClick={() => {
this.setState({ curSheetIndex: index });
}}
/>
));
return <div className="sheet-names">{sheets}</div>;
}
renderSheetData(sheet) {
const csvProps = Object.assign({}, this.props, { data: sheet });
return <CsvViewer {...csvProps} />;
}
render() {
const { sheets, names, curSheetIndex } = this.state;
return (
<div className="spreadsheet-viewer">
{this.renderSheetNames(names)}
{this.renderSheetData(sheets[curSheetIndex || 0])}
</div>
);
}
}
export default XlxsViewer;

How to prevent a react parent component from loading

I am using react to build my app and on the post component there are 3 other child components that are been called in a map function while passing the post props.
On the list component when a user clicks on the like button the post tends to reload, and that is not what I want.
This is my post parent component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
class Posts extends Component {
constructor(props) {
super(props)
this.state = {
sidebaropen: false,
test: '',
posts: []
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.post.posts.length > 0) {
this.setState({ posts: nextProps.post.posts })
console.log('updated')
}
}
componentDidMount() {
this.props.getPosts();
socket.on('posts', data => {
console.log("resiving :" + JSON.stringify(data))
if (Object.keys(this.state.posts).length > 0 && Object.keys(data).length >
0) {
this.setState({ posts: [data[0], ...this.state.posts] })
} else {
this.setState({ posts: data })
}
})
socket.on('id', data => {
console.log(data)
})
console.log('mounted post')
}
}
render(){
const { loading } = this.props.post;
const { posts, } = this.state;
let closebtn
let postContent;
if (posts === null || loading) {
postContent = <Spinner />;
} else {
postContent = <PostFeeds posts={ posts } />;
}
if (this.state.sidebaropen) {
closebtn = <button className='close-toggle-btn' onClick =
{ this.closetoggleclickhandle } />
}
return (
<div className= "post_wrapper" >
<Postsidebar show={ this.state.sidebaropen } />
< div className = "" >
{ postContent }
< /div>
{ closebtn }
<TiPlus className='creat-post-toggle-btn icons' onClick =
{ this.toggleclickhandle } />
</div>
)
}
}
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
post: state.post
})
export default connect(mapStateToProps, { getPosts })(Posts);
and this is the first child component
import React, { Component } from 'react';
import PostItem from './PostItem';
class PostFeeds extends Component {
componentDidMount() {
//this.setState({ test : 'mounted'})
console.log('mounted feed')
}
render() {
const { posts } = this.props;
//console.log(posts)
return posts.map(post => <PostItem key={ post._id } post = { post } />);
}
}
postItem.js its i little kind of rough
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { deletePost, addLike, removeLike, bookmark } from "../../actions/postsActions";
import Postimg from './Postimg';
import { MdBookmarkBorder ,/**MdBookmark */} from "react-icons/md";
import {AiFillDislike, AiOutlineLike, AiFillDownSquare} from 'react-icons/ai'
import { TiHeartOutline, TiHeartFullOutline, TiMessage, TiDelete } from "react-icons/ti";
class PostItem extends Component {
onDeleteClick(id){
this.props.deletePost(id);
}
componentWillMount() {
console.log( 'mounted item')
//console.log(window.scrollTo(0, localStorage.getItem('scrollpossition')))
}
onLikeClick(id){
this.props.addLike(id);
// window.scrollTo(0, localStorage.getItem('scrollpossition'))
window.location.href = '/feed'
}
onUnlikeClick(id){
this.props.removeLike(id);
// window.scrollTo(0, localStorage.getItem('scrollpossition'))
window.location.href = '/feed'
}
findUserLike(likes) {
const { auth } = this.props;
if(likes.length > 0){
if(likes.filter(like => like.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
}
}
findUserDislike(dislikes) {
const { auth } = this.props;
if(dislikes.length > 0){
if(dislikes.filter(dislike => dislike.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
}
}
onBookmark (id){
this.props.bookmark(id)
}
render() {
const { post, auth, showActions } = this.props;
let ifAlreadyliked;
let ifAlreadydisliked;
let postimg;
let postText;
let profileimg
let topic = ''
if(post.user)
{
if(post.user.profileImageData){
profileimg = <Link to={`/profile/${post.profile.handle}`}><img src={post.profile.profileImageData} alt='' /></Link>
}else{
profileimg = <img src='/assets/images/user-4.png' alt='pip' />
}
if(this.findUserLike(post.likes)){
ifAlreadyliked = <TiHeartFullOutline className= 'icons like-color'/>
}else{
ifAlreadyliked = <TiHeartOutline className= 'icons'/>
}
if(this.findUserDislike(post.dislikes)){
ifAlreadydisliked = <AiFillDislike className= 'icons yellow'/>
}else{
ifAlreadydisliked = <AiOutlineLike className= 'icons' onClick={this.onUnlikeClick.bind(this, post._id)}/>
}
}
if(post.Topic){
topic = <div className= ''><small><b style={{color:'#ff8d00'}}>< AiFillDownSquare />{post.Topic}</b></small></div>
}
if(post.postImageData !== '' && post.postImageData !== null && post.postImageData !== undefined)
{
postimg = <Postimg imageSrc = {post.postImageData} imgAlt = {''}/>
}
if(post.text !== '' || post.text === null)
{
postText = <div className='feed_text'>{post.text}</div>
}
return (
<div className="feed_card">
<div className="feed_header">
<div className="feed-profile-img">{profileimg}</div>
<div className="feed-handle-text">{post.name}</div>
<div className='v-spacer'/>
<div className="time-stamp"><small>{new Date (post.date).toLocaleString ('en-US', {hour: 'numeric', hour12: true, minute: 'numeric', month: 'long', day: 'numeric' } )} </small></div>
</div>
<div className="feed-body-container">
<div>
{topic}
{postimg}
{postText}
<div className='mini_feed_footer'>
<small>{post.likes.length} likes. {post.comments.length} comments. {post.dislikes.length} dislikes.</small>
</div>
{ showActions ? (
<div className='feed_footer'>
<div onClick={this.onLikeClick.bind(this, post._id)} type="button" className="btn btn-light mr-1">
<div className='feed-icon-mini-container'>
{ifAlreadyliked}
</div>
</div>
<div className='spacer'/>
{ifAlreadydisliked}
<div className='spacer'/>
<Link to={`/post/${post._id}`} className='header-brand'>
<TiMessage className='icons'/>
</Link>
<div className='spacer'/>
{ post.user === auth.user.id ? (
<TiDelete
onClick={this.onDeleteClick.bind(this, post._id)}
className="icons red"
/>
) : <MdBookmarkBorder
onClick={this.onBookmark.bind(this, post._id)}
className="icons blue"
/> }
</div>) : null}
</div>
</div>
</div>
);
}
}
PostItem.defaultProps = {
showActions: true
}
PostItem.propTypes = {
post: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
deletePost: PropTypes.func.isRequired,
addLike:PropTypes.func.isRequired,
removeLike:PropTypes.func.isRequired,
bookmark:PropTypes.func.isRequired
};
const mapStateToProps = state => ({
auth: state.auth
})
export default connect(mapStateToProps, {deletePost, addLike,bookmark, removeLike})(PostItem);
Don't use window object for navigation (Major) as this causes reload. Make your handlers arrow functions (minor).
onLikeClick = id => {
this.props.addLike(id);
// window.scrollTo(0, localStorage.getItem('scrollpossition'))
this.props.history.push("/feed");
};
onUnlikeClick = id => {
this.props.removeLike(id);
// window.scrollTo(0, localStorage.getItem('scrollpossition'))
this.props.history.push("/feed");
};
Also if /feed is the same page then remove it all together, no need for it.

React componentDid mount get Async image. if not done before changing page, how to cancel the request?

I am kinda new to react coming from an Angular background. recently I am building a page where I display cards that all need to fetch an image from the microsoft graph api. The return type of the call is a base64 string. I don't want to put this into redux because it will make my devtools unreadable.
Therefore I decided to make a async call in the componentDidMount lifecycle hook with the this.isMounted pattern (Cancel All Subscriptions and Asyncs in the componentWillUnmount Method, how?). The problem however is that for some reason this doesn't unsubscribe the async call that I made. I am not sure whether I made a mistake or whether it needs to be unsubscribed instead of checking whether the component is mounted. but I cannot find any information on how to deal with this.
Any help would be appreciated.
My teamCard code:
import React from "react";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import TeamCardLogo from "./teamCardLogo/teamCardLogo";
import TeamCardPersona from "./teamCardPersona/teamCardPersona";
import { GetGroupMembers } from "../../HttpRepositories/graphRepository";
import { FormattedMessage } from "react-intl";
import Fade from "react-reveal/Fade";
import { Modal } from "office-ui-fabric-react/lib/Modal";
import reactAppInsights from "react-appinsights";
import TeamModal from "./teamModal/teamModal";
class TeamCard extends React.Component {
state = {
members: "",
modelIsOpen: false
};
async componentDidMount() {
let members = await GetGroupMembers(this.props.id);
if (this.state.member !== "error") {
this.setState({ members });
}
}
_openModal = id => {
this.setState({ modelIsOpen: true });
};
_closeModal = () => {
this.setState({ modelIsOpen: false });
};
render() {
let members = "";
if (
typeof this.state.members !== "undefined" &&
this.state.members.length > 0 &&
this.state.members !== "error"
) {
members = this.state.members.map((member, i) => {
if (i < 5) {
return (
<div className="team-card-body__personas-wrapper-person" key={i}>
<TeamCardPersona
className="team-card-body__personas-wrapper-person"
member={member}
key={i}
/>
</div>
);
}
});
} else {
members = <div className="no-members-spacer" />;
}
let favouriteIcon = "";
this.props.isFavorite === true
? (favouriteIcon = <Icon iconName="FavoriteStarFill" />)
: (favouriteIcon = <Icon iconName="FavoriteStar" />);
return (
<React.Fragment>
{/* <Fade bottom delay={this.props.delay} appear={true}> */}
<article
className="team-card-wrapper"
onClick={() => this._openModal(this.props.id)}
>
<header className="team-card-wrapper__header">
<TeamCardLogo
injectClass="team-card-wrapper__header-photo"
teamId={this.props.id}
/>
<div className="team-card-wrapper__header-options-wrapper">
<div className="header-options__icon-group">
<div className="header-options__group-type">
<Icon iconName="LockSolid" />
</div>
</div>
<div className="header-options__icon-group">
<div className="header-options__favourite">{favouriteIcon}</div>
</div>
</div>
</header>
<section className="team-card-body">
<h1>{this.props.title}</h1>
<h2>
{" "}
<FormattedMessage
id="teamcard.memberCount"
defaultMessage="Leden"
/>
:{this.state.members.length}
</h2>
<div className="team-card-body__personas-wrapper">{members}</div>
<p className="description">{this.props.description}</p>
{/* <div className="team-card-body__join-button-wrapper">
<PrimaryButton text="Lid worden" />
</div> */}
</section>
</article>
{/* </Fade> */}
<Modal
titleAriaId={this._titleId}
subtitleAriaId={this._subtitleId}
isOpen={this.state.modelIsOpen}
onDismiss={this._closeModal}
isBlocking={false}
containerClassName="team-modal-wrapper"
>
<TeamModal
teamId={this.props.id}
title={this.props.title}
description={this.props.description}
favorite={this.props.isFavorite}
members={this.state.members}
closeModal={this._closeModal}
/>
</Modal>
</React.Fragment>
);
}
}
export default TeamCard;
my TeamCardLogo code (makes the async call)
import React from "react";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import { getImage } from "../../../HttpRepositories/graphRepository";
class TeamCardImage extends React.Component {
constructor(props) {
super(props);
this._isMounted = false;
}
state = {
groupImage: ""
};
getLogo = () => {};
async componentDidMount() {
this._isMounted = true;
if (this._isMounted) {
let logo = await getImage(
`https://graph.microsoft.com/v1.0/groups/${
this.props.teamId
}/photo/$value`
);
if (logo !== "error") {
this.setState({ groupImage: logo });
}
}
}
render() {
let injectedClassName =
this.props.injectClass != "" ? this.props.injectClass : "";
let headerPhoto = "";
const groupIcon = (
<div className="team-card-wrapper__header-photo-alt">
<Icon iconName="Group" />
</div>
);
if (this.state.groupImage === "") {
headerPhoto = groupIcon;
} else {
headerPhoto = <img src={this.state.groupImage} alt=" " />;
}
return (
<React.Fragment>
<div className={injectedClassName}>{headerPhoto}</div>
</React.Fragment>
);
}
componentWillUnmount() {
this._isMounted = false;
}
}
export default TeamCardImage;
my httpRepos code
import { getGraphToken } from "../adalConfig";
import axios from "axios";
export const GetGroupMembers = async groupId => {
// we initiate a new token, to be sure that it didn't expire.
let graphToken = getGraphToken();
try {
let response = await axios({
url: `https://graph.microsoft.com/v1.0/groups/${groupId}/members?$select=id,displayName`,
method: "GET",
headers: { Authorization: "Bearer " + graphToken }
});
if (response.status != 200 && response.status != 204) {
return "error";
}
return await response.data.value;
} catch (error) {
return "error";
}
};
export const getImage = async url => {
// we initiate a new token, to be sure that it didn't expire.
let graphToken = getGraphToken();
try {
let response = await axios({
url: url,
method: "get",
responseType: "blob",
headers: { Authorization: "Bearer " + graphToken }
});
if (response.status != 200 && response.status != 204) {
return "error";
}
var urlCreator = window.URL || window.webkitURL;
var imageUrl = urlCreator.createObjectURL(response.data);
return await imageUrl;
} catch (error) {
return "error";
}
};
You want to check that _isMounted is still true before you call setState, not before you start the request.
async componentDidMount() {
this._isMounted = true;
let logo = await getImage(
`https://graph.microsoft.com/v1.0/groups/${this.props.teamId}/photo/$value`
);
if (this._isMounted && logo !== "error") {
this.setState({ groupImage: logo });
}
}

React Router Redirecting on Logout

I am trying to redirect to the index route from "mypolls" page.
The problem is when I click the logout button in the header it redirects to the path of the first Link component in my list
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
class PollsList extends Component {
static contextTypes = {
router: PropTypes.object
};
componentWillMount() {
this.props.fetchPolls();
}
componentWillReceiveProps(nextProps) {
const { authenticatedUser, type } = nextProps;
if (!authenticatedUser && type === 'mypolls') {
this.context.router.push('/');
}
}
componentWillUnmount() {
//Important! If your component is navigating based on some global state(from say componentWillReceiveProps)
//always reset that global state back to null when you REMOUNT
this.props.resetMe();
}
renderOptions(options) {
return options.map(o => {
o = o.trim();
return (
<span className="list-group-item-text">{" " + o + " "}</span>
);
});
}
renderPolls(polls) {
const { type, authenticatedUser, user } = this.props;
if (authenticatedUser) {
if (type === 'mypolls') {
return polls.filter(poll => user.user._id === poll.authorId)
.map((poll) => {
return (
<li className="list-group-item" key={poll._id}>
<Link style={{color:'black'}} to={"polls/" + poll._id}>
// example redirect: http://localhost:8080/polls/586e0d06eedc57071566b1f2
<h3 className="list-group-item-heading">{poll.title}</h3>
</Link>
{this.renderOptions(poll.options)}
</li>
);
});
}
}
return polls.map((poll) => {
return (
<li className="list-group-item" key={poll._id}>
<Link style={{color:'black'}} to={"polls/" + poll._id}>
<h3 className="list-group-item-heading">{poll.title}</h3>
</Link>
{this.renderOptions(poll.options)}
</li>
);
});
}
render() {
const { polls, loading, error } = this.props.pollsList;
if (loading) {
return <div className="container"><h1>Polls</h1><h3>Loading...</h3></div>
} else if (error) {
return <div className="alert alert-danger">Error: {error.message}</div>
}
return (
<div className="container">
<div className="col-md-6 col-md-offset-3">
<h1>Polls</h1>
<ul className="list-group">
{this.renderPolls(polls)}
</ul>
</div>
</div>
);
}
}
export default PollsList;
The Header.js component also is set to redirect if the user is logged out
componentWillReceiveProps(nextProps) {
if (nextProps.deletedPoll.error && nextProps.deletedPoll.error.message) {//delete failure
alert(nextProps.deletedPoll.error.message || 'Could not delete. Please try again.');
} else if (nextProps.deletedPoll.poll && !nextProps.deletedPoll.error) {//delete success
this.context.router.push('/');
} else if (this.props.user.user && !nextProps.user.user) {//logout (had user(this.props.user.user) but no loger the case (!nextProps.user.user))
this.context.router.push('/');
}
}
Here is the container component
import { connect } from 'react-redux'
import { fetchPolls, fetchPollsSuccess, fetchPollsFailure, resetPolls } from '../actions/polls';
import PollsList from '../components/PollsList';
const mapStateToProps = (state) => {
return {
pollsList: state.polls.pollsList,
user: state.user,
status: state.user.status,
authenticatedUser: state.user.status === 'authenticated' ? state.user.user : null
};
}
const mapDispatchToProps = (dispatch) => {
return {
fetchPolls: () => {
dispatch(fetchPolls()).then((response) => {
!response.error ? dispatch(fetchPollsSuccess(response.payload.data)) : dispatch(fetchPollsFailure(response.payload.data));
});
},
resetMe: () => {
dispatch(resetPolls());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PollsList);

Resources