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.
Related
I'm trying to build a Movie Search web app with React. I query themoviedb.com for movie information and display the poster/movie title to the user.
I'm using Swiperjs to display the movie posters and titles to the users horizontally. Unfortunately, when I map() the movie information into a SwiperSlide component, the slider won't slide. It seems to stutter and refuse to move from the first movie poster. Can anyone send me in the right direction as to why it won't work?
Weirdly enough, on random refreshes, random Genres will work completely fine until a reload, but only one Genre component will do this.
Genre Component:
import React from "react";
import { Swiper, SwiperSlide } from 'swiper/react';
import MovieCard from '../components/movieCard';
class Genre extends React.Component {
constructor(props) {
super(props);
this.state = {
genre: this.props.genre,
genreName: "",
movies: []
};
}
componentDidMount() {
const genreNum = this.state.genre;
const url = `apiURlRequestHere`;
try {
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ movies: data.results }));
} catch (err) {
console.error(err);
}
this.getGenreName(genreNum);
}
getGenreName(genreNum) {
let title = "";
switch (genreNum) {
case "28":
title = "Action";
break;
case "12":
title = "Adventure";
break;
case "16":
title = "Animation";
break;
case "35":
title = "Comedy";
break;
case "80":
title = "Crime";
break;
case "99":
title = "Documentary";
break;
case "18":
title = "Drama";
break;
case "14":
title = "Fantasy";
break;
case "27":
title = "Horror";
break;
case "9648":
title = "Mystery";
break;
case "10749":
title = "Romance";
break;
case "878":
title = "Science Fiction";
break;
case "53":
title = "Thriller";
break;
default:
title = "";
break;
}
this.setState({ genreName: title });
}
render() {
const movies = this.state.movies;
let genreCat = this.state.genreName;
return (
<>
<h2 className="category-title">{genreCat}</h2>
<Swiper spaceBetween={0} slidesPerView={1}>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie} />
</SwiperSlide>
))}
</Swiper>
</>
);
}
}
export default Genre;
MovieCard Component:
import React from "react";
export default function MovieCard({movie}) {
return (
<div className="card">
<img
className="card--image"
src={`https://image.tmdb.org/t/p/w185_and_h278_bestv2/${movie.poster_path}`}
alt={movie.title + ' poster'}
/>
<div className="card--content">
<h3 className="card--title">{movie.title}</h3>
<p className="card--rating">{movie.vote_average * 10}%</p>
</div>
</div>
)
}
--- UPDATE ---
Here is the Swiper Component I am using from SwiperJs
import React, { useRef, useState, useEffect, forwardRef } from 'react';
import { getParams } from './get-params';
import { initSwiper } from './init-swiper';
import { needsScrollbar, needsNavigation, needsPagination, uniqueClasses } from './utils';
import { renderLoop, calcLoopedSlides } from './loop';
import { getChangedParams } from './get-changed-params';
import { getChildren } from './get-children';
import { updateSwiper } from './update-swiper';
import { renderVirtual, updateOnVirtualData } from './virtual';
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
const Swiper = forwardRef(
(
{
className,
tag: Tag = 'div',
wrapperTag: WrapperTag = 'div',
children,
onSwiper,
...rest
} = {},
externalElRef,
) => {
const [containerClasses, setContainerClasses] = useState('swiper-container');
const [virtualData, setVirtualData] = useState(null);
const [breakpointChanged, setBreakpointChanged] = useState(false);
const initializedRef = useRef(false);
const swiperElRef = useRef(null);
const swiperRef = useRef(null);
const oldPassedParamsRef = useRef(null);
const oldSlides = useRef(null);
const nextElRef = useRef(null);
const prevElRef = useRef(null);
const paginationElRef = useRef(null);
const scrollbarElRef = useRef(null);
const { params: swiperParams, passedParams, rest: restProps } = getParams(rest);
const { slides, slots } = getChildren(children);
const changedParams = getChangedParams(
passedParams,
oldPassedParamsRef.current,
slides,
oldSlides.current,
);
oldPassedParamsRef.current = passedParams;
oldSlides.current = slides;
const onBeforeBreakpoint = () => {
setBreakpointChanged(!breakpointChanged);
};
Object.assign(swiperParams.on, {
_containerClasses(swiper, classes) {
setContainerClasses(classes);
},
_swiper(swiper) {
swiper.loopCreate = () => {};
swiper.loopDestroy = () => {};
if (swiperParams.loop) {
swiper.loopedSlides = calcLoopedSlides(slides, swiperParams);
}
swiperRef.current = swiper;
if (swiper.virtual && swiper.params.virtual.enabled) {
swiper.virtual.slides = slides;
swiper.params.virtual.cache = false;
swiper.params.virtual.renderExternal = setVirtualData;
swiper.params.virtual.renderExternalUpdate = false;
}
},
});
if (swiperRef.current) {
swiperRef.current.on('_beforeBreakpoint', onBeforeBreakpoint);
}
useEffect(() => {
return () => {
if (swiperRef.current) swiperRef.current.off('_beforeBreakpoint', onBeforeBreakpoint);
};
});
// set initialized flag
useEffect(() => {
if (!initializedRef.current && swiperRef.current) {
swiperRef.current.emitSlidesClasses();
initializedRef.current = true;
}
});
// watch for params change
useIsomorphicLayoutEffect(() => {
if (changedParams.length && swiperRef.current && !swiperRef.current.destroyed) {
updateSwiper(swiperRef.current, slides, passedParams, changedParams);
}
});
// update on virtual update
useIsomorphicLayoutEffect(() => {
updateOnVirtualData(swiperRef.current);
}, [virtualData]);
// init swiper
useIsomorphicLayoutEffect(() => {
if (externalElRef) {
externalElRef.current = swiperElRef.current;
}
if (!swiperElRef.current) return;
initSwiper(
{
el: swiperElRef.current,
nextEl: nextElRef.current,
prevEl: prevElRef.current,
paginationEl: paginationElRef.current,
scrollbarEl: scrollbarElRef.current,
},
swiperParams,
);
if (onSwiper) onSwiper(swiperRef.current);
// eslint-disable-next-line
return () => {
if (swiperRef.current && !swiperRef.current.destroyed) {
swiperRef.current.destroy();
}
};
}, []);
// bypass swiper instance to slides
function renderSlides() {
if (swiperParams.virtual) {
return renderVirtual(swiperRef.current, slides, virtualData);
}
if (!swiperParams.loop || (swiperRef.current && swiperRef.current.destroyed)) {
return slides.map((child) => {
return React.cloneElement(child, { swiper: swiperRef.current });
});
}
return renderLoop(swiperRef.current, slides, swiperParams);
}
return (
<Tag
ref={swiperElRef}
className={uniqueClasses(`${containerClasses}${className ? ` ${className}` : ''}`)}
{...restProps}
>
{slots['container-start']}
{needsNavigation(swiperParams) && (
<>
<div ref={prevElRef} className="swiper-button-prev" />
<div ref={nextElRef} className="swiper-button-next" />
</>
)}
{needsScrollbar(swiperParams) && <div ref={scrollbarElRef} className="swiper-scrollbar" />}
{needsPagination(swiperParams) && (
<div ref={paginationElRef} className="swiper-pagination" />
)}
<WrapperTag className="swiper-wrapper">
{slots['wrapper-start']}
{renderSlides()}
{slots['wrapper-end']}
</WrapperTag>
{slots['container-end']}
</Tag>
);
},
);
Swiper.displayName = 'Swiper';
export { Swiper };
And here is the SwiperSlide Component:
import React, { useRef, useState, forwardRef } from 'react';
import { uniqueClasses } from './utils';
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
const SwiperSlide = forwardRef(
(
{ tag: Tag = 'div', children, className = '', swiper, zoom, virtualIndex, ...rest } = {},
externalRef,
) => {
const slideElRef = useRef(null);
const [slideClasses, setSlideClasses] = useState('swiper-slide');
function updateClasses(swiper, el, classNames) {
if (el === slideElRef.current) {
setSlideClasses(classNames);
}
}
useIsomorphicLayoutEffect(() => {
if (externalRef) {
externalRef.current = slideElRef.current;
}
if (!slideElRef.current || !swiper) return;
if (swiper.destroyed) {
if (slideClasses !== 'swiper-slide') {
setSlideClasses('swiper-slide');
}
return;
}
swiper.on('_slideClass', updateClasses);
// eslint-disable-next-line
return () => {
if (!swiper) return;
swiper.off('_slideClass', updateClasses);
};
});
let slideData;
if (typeof children === 'function') {
slideData = {
isActive:
slideClasses.indexOf('swiper-slide-active') >= 0 ||
slideClasses.indexOf('swiper-slide-duplicate-active') >= 0,
isVisible: slideClasses.indexOf('swiper-slide-visible') >= 0,
isDuplicate: slideClasses.indexOf('swiper-slide-duplicate') >= 0,
isPrev:
slideClasses.indexOf('swiper-slide-prev') >= 0 ||
slideClasses.indexOf('swiper-slide-duplicate-prev') >= 0,
isNext:
slideClasses.indexOf('swiper-slide-next') >= 0 ||
slideClasses.indexOf('swiper-slide-duplicate next') >= 0,
};
}
const renderChildren = () => {
return typeof children === 'function' ? children(slideData) : children;
};
return (
<Tag
ref={slideElRef}
className={uniqueClasses(`${slideClasses}${className ? ` ${className}` : ''}`)}
data-swiper-slide-index={virtualIndex}
{...rest}
>
{zoom ? (
<div
className="swiper-zoom-container"
data-swiper-zoom={typeof zoom === 'number' ? zoom : undefined}
>
{renderChildren()}
</div>
) : (
renderChildren()
)}
</Tag>
);
},
);
SwiperSlide.displayName = 'SwiperSlide';
export { SwiperSlide };
Also, I have a working example with the issue here... https://codesandbox.io/s/blue-mountain-0fjyc?file=/src/App.js
in this example, hit refresh on the browser and that is what I see.
--- Update ---
Still working on this project and I created a movie detail page that displays the cast with a Swiper(what a fool, I know). It works perfectly fine but if I replace the code in the GenreComponent code with the working code from the below MovieDetail component, it still will not work...
import React from "react";
import { Swiper, SwiperSlide } from 'swiper/react';
import MovieGenre from '../components/movieGenreBtn';
class MovieDetails extends React.Component {
constructor(props) {
super(props);
this.state = {
movie: [],
movieGenres: [],
credits: [],
director: [],
foundDirector: false
}
}
componentDidMount() {
const movieId = this.props.location.pathname.replace("/", "");
this.fetchMovie(movieId);
this.fetchCrew(movieId);
}
fetchMovie(movieId) {
const url = `https://api.themoviedb.org/3/movie/${movieId}?api_key=${process.env.REACT_APP_MOVIE_API_KEY}&language=en-US
`;
try {
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ movie: data }));
} catch (err) {
console.error(err);
}
}
fetchCrew(movieId) {
const url = `https://api.themoviedb.org/3/movie/${movieId}/credits?api_key=${process.env.REACT_APP_MOVIE_API_KEY}`
try {
fetch(url)
.then((response) => response.json())
.then((data) => this.setState({ credits: data }));
} catch (err) {
console.error(err);
}
}
getDirector() {
let director = [];
if (this.state.credits !== null){
const crew = this.state.credits.crew;
let i;
for(i=0; i < crew.length; i++) {
if (crew[i].job === 'Director') {
director = crew[i];
}
}
}
this.setState({ director: director, foundDirector: true });
}
getGenres() {
const movie = this.state.movie;
let genres = [];
let i;
for (i=0; i < movie.genres.length; i++) {
genres.push(movie.genres[i]);
}
this.setState({ movieGenres: genres });
}
render() {
const movie = this.state.movie;
const cast = this.state.credits.cast;
if (cast != null && !this.state.foundDirector) {
this.getDirector();
this.getGenres();
}
const currCast = this.state.credits.cast;
const movieGenres = this.state.movieGenres;
return (
<div className="movie-details-wrapper">
<div className="details-header">
<img
className="movie-backdrop"
src={`https://image.tmdb.org/t/p/w1000_and_h450_multi_faces/${movie.backdrop_path}`} />
<h3 className="details-title">
{movie.title}
</h3>
<h5 className="details-tagling">
{movie.tagline}
</h5>
</div>
<div className="details-content">
<div className="details-director-rating">
<p className="basic-details">
Director: {this.state.director.name}
</p>
<p className="details-rating">
{movie.vote_average * 10}%
</p>
</div>
<div className="details-genres">
{movieGenres.map(genre => (
<MovieGenre genre={genre.id} />
))}
</div>
<div className="details-cast-wrapper">
<h3>Cast</h3>
<div className="details-cast">
{currCast ?
<Swiper>
{currCast.map(person => (
<SwiperSlide>
<img className="cast-img"src={`https://image.tmdb.org/t/p/w220_and_h330_bestv2/${person.profile_path}`} />
// </SwiperSlide>
))}
</Swiper>
:
<h2>No cast</h2>
}
</div>
</div>
</div>
</div>
);
}
} export default MovieDetails
--- Last Update ---
Problem Solved!
I think the Swiper was initialized before anything was in it so the Swiper thought it had zero slides and wouldn't function. I fixed this by adding some conditional rendering to check the length of the movies variable. Initially, this did not work with the conditional rendering because I forgot to add the length check.
Before:
<h2 className="category-title">{genreCat}</h2>
{movies ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}
After:
<h2 className="category-title">{genreCat}</h2>
{movies.length > 0 ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}
Problem Solved! I think the Swiper was initialized before anything was in it so the Swiper thought it had zero slides and wouldn't function. I fixed this by adding some conditional rendering to check the length of the movies variable. Initially, this did not work with the conditional rendering because I forgot to add the length check.
Before:
<h2 className="category-title">{genreCat}</h2>
{movies ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}
After:
<h2 className="category-title">{genreCat}</h2>
{movies.length > 0 ?
<Swiper>
{movies
.filter((movie) => movie.poster_path)
.map((movie) => (
<SwiperSlide key={movie.id}>
<MovieCard movie={movie}/>
</SwiperSlide>
))}
</Swiper>
:
<h2>No Movies</h2>
}
You should add "return"
movies.map((movie) => {
return <SwiperSlide></SwiperSlide>
}
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;
I tried to bulid shopping cart in React I don't use redux so I think it can be a problem too. I have now alomost done application so I want to finish it in this way without using redux. Ok whats the problem. I made function to add items into the shopping cart in component counter but I don't want to display this products in this component but in main component App in header. In component counter I creat component ShoppingCart to display the products - but I want to only push products into the ShoppingCart but display them in component App.
I tried a lot of diffrent methods but it's not working. I can display products but really not in the place I want. I think the problem is how I can comunicate with my items between components.
This is my Counter
import React, { Component } from "react";
import "./Counter";
import "./Counter.css";
import ShoppingCart from "./ShoppingCart";
class Counter extends Component {
state = {
availableProducts: 20,
shoppingCart: 0,
cart: []
};
handleRemoveFromCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart - 1
});
};
handleAddToCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart + 1
});
};
handleAddProductsToCart = props => {
// console.log("clicked", this.props.name, this.state.shoppingCart)
let found = false;
const updateCart = this.state.cart.map(cartItem => {
if (cartItem.name === this.props.name) {
found = true;
cartItem.productsNumber = this.state.shoppingCart;
return cartItem;
} else {
return cartItem;
}
});
if (!found) {
updateCart.push({
name: this.props.name,
productsNumber: this.state.shoppingCart,
key: this.props.name
});
}
this.setState({
cart: updateCart
});
// return <ShoppingCart cart={updateCart} />;
// console.log(updateCart);
};
render() {
const cart = this.state.cart.map(cartItem => (
<ShoppingCart
name={cartItem.name}
productsNumber={cartItem.productsNumber}
key={cartItem.key}
/>
));
return (
<>
<div className="counter">
<button
className="buttonCount"
disabled={this.state.shoppingCart === 0 ? true : false}
onClick={this.handleRemoveFromCart}
>-</button>
<span> {this.state.shoppingCart} </span>
<button
className="buttonCount"
disabled={
this.state.shoppingCart === this.state.availableProducts
? true
: false
}
onClick={this.handleAddToCart}
>
+
</button>
<button
className="buy"
disabled={this.state.shoppingCart <= 0 ? true : false}
onClick={this.handleAddProductsToCart}
>Add to cart</button>
</div>
<div>{cart}</div>
</>
);
}
}
export default Counter;
and this is Shopping
import React, {Component} from "react"
import "./ShoppingCart";
import "./ShoppingCart.css";
class ShoppingCart extends Component {
render() {
return (
<>
<div>{this.props.name}</div>
<div>{this.props.productsNumber}</div>
</>
);
}
}
export default ShoppingCart;
If you have any suggestions it will be helpful. Thank you.
I think the following code can help you
import React, { Component } from "react";
import "./Counter";
import "./Counter.css";
import ShoppingCart from "./ShoppingCart";
class Counter extends Component {
state = {
availableProducts: 20,
shoppingCart: 0,
cart: []
};
handleRemoveFromCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart - 1
});
};
handleAddToCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart + 1
});
};
handleAddProductsToCart = props => {
// console.log("clicked", this.props.name, this.state.shoppingCart)
let found = false;
const updateCart = this.state.cart.map(cartItem => {
if (cartItem.name === this.props.name) {
found = true;
cartItem.productsNumber = this.state.shoppingCart;
return cartItem;
} else {
return cartItem;
}
});
if (!found) {
updateCart.push({
name: this.props.name,
productsNumber: this.state.shoppingCart,
key: this.props.name
});
}
this.setState({
cart: updateCart
});
// return <ShoppingCart cart={updateCart} />;
// console.log(updateCart);
};
CreateCard=(cartItem)=>{
console.log(cartItem)
return(
<ShoppingCart
name={cartItem.name}
productsNumber={cartItem.productsNumber}
key={cartItem.key}
/>
);
}
render() {
return (
<>
<div className="counter">
<button
className="buttonCount"
disabled={this.state.shoppingCart === 0 ? true : false}
onClick={this.handleRemoveFromCart}
>-</button>
<span> {this.state.shoppingCart} </span>
<button
className="buttonCount"
disabled={
this.state.shoppingCart === this.state.availableProducts
? true
: false
}
onClick={this.handleAddToCart}
>
+
</button>
<button
className="buy"
disabled={this.state.shoppingCart <= 0 ? true : false}
onClick={this.handleAddProductsToCart}
>Add to cart</button>
</div>
<div>{this.state.cart!=null?this.state.cart.map(cartItem => (this.CreateCard(cartItem))):""}</div>
</>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.3.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.3.0/umd/react-dom.production.min.js"></script>
I hope help you
I have a problem when trying to add my redux array to a component’s state:
componentDidMount() {
this.props.cardAction()
this.setState({ showCards: this.props.cardAction() })
}
hhhhhh undefined console log undefined
Here’s my dashboard code:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import Header from '../../common/Header/'
import Masonry from '../../common/Masonry/'
import { cardAction } from '../../store/actions/Cards'
import Arrow_Down from '../../assets/img/arrow-down.svg'
class Dashboard extends Component {
componentDidMount() {
this.props.cardAction()
this.setState({ showCards: this.props.cardAction() })
}
constructor(props) {
super(props)
this.state = {
collapsed: true,
class: 'collapsed',
showCards: {},
}
this.toggleCollapse = this.toggleCollapse.bind(this);
}
toggleCollapse(i, info) {
console.log('i', info, 'iiiii', i)
this.setState({
collapsed: !this.state.collapsed,
class: this.state.collapsed ? '' : 'collapsed',
showCards: info
}, () => {
// my state is updated here !
console.log('cardsss', this.state.showCards)
})
if (this.state.showCards === 'active') {
let carddd = this.state.showCards
this.setState({
showCards: {
...this.state.showCards,
open: 'inactive'
}
});
}
else {
this.setState({
showCards: {
...this.state.showCards,
open: 'active'
}
});
}
}
render() {
console.log('hhhhhh', this.state.showCards)
const cardList = this.props.Cards.map((info, i) => {
return (
<div className={(info.open === 'active') ? 'collapsed' : ''} key={i}>
<div className={(info.open === 'active') ? 'header flex space-between active' : 'header flex space-between'}>
<h2>{info.title}</h2>
<span onClick={() => { this.toggleCollapse(i, info) }}><img src={Arrow_Down} alt='Arrow' /></span>
</div>
<div className='content'>
<p>{info.description}</p>
</div>
</div>
)
})
return (
<div>
<Header />
<Masonry columns={3} gap={20}>
{cardList}
</Masonry>
</div>
)
}
}
Dashboard.defaultProps = {
columns: 2,
gap: 20,
Cards: []
}
Dashboard.propTypes = {
Cards: PropTypes.array.isRequired,
}
const mapStateToProps = state => {
return { Cards: state.cards.result }
}
const mapDispatchToProps = dispatch => ({
cardAction: () => dispatch(cardAction())
})
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
this.props.cardAction() is a redux action, it's not meant for you to directly assign to state, reason is redux action will return to reducer, not component. You should remove the setState in componentDidMount
componentDidMount() {
this.props.cardAction();
}
When you invoked this.props.cardAction(), it will call the function that defined in redux action file, and the result will be available at this.props.Cards as you mentioned above
const mapStateToProps = state => {
return { Cards: state.cards.result }
}
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);