My project file structure
App.jsx
import React, { Component } from 'react';
import createBrowserHistory from 'history/lib/createBrowserHistory';
import { Router, Route ,browserHistory} from 'react-router';
import Header from './components/header';
import PreLoginHeader from './components/pre_login_header';
import BrandManagerForm from './components/brand_manager_form';
import Home from './pages/home';
import Internships from './pages/internships';
import PostNewInternship from './pages/post_new_internship';
import Applications from './pages/applications';
import Support from './pages/support';
require('./util/config-reflux')();
class App extends Component {
componentDidMount(){
}
render() {
const history = new createBrowserHistory();
return (
<div style={{height:'100%',width:'100%'}}>
{
this.checkLoggedIn() ? <Header history={history} /> : <PreLoginHeader />
}
<Router history={history}>
<Route path='/' component={Home} />
<Route path='/internships' component={Internships} />
<Route path='/register' component={BrandManagerForm} />
<Route path='/register/complete' component={BrandManagerForm} />
<Route path='/post-new-internship' component={PostNewInternship} />
<Route path='/applications' component={Applications} />
<Route path='/messages' component={Internships} />
<Route path='/messages/:id' component={Support} />
<Route path='/messages/:id/edit' component={PostNewInternship} />
{/* IT'S THE SAME VIEW AS Internships.NOT TO BE CONFUSED WITH NAME*/}
</Router>
</div>
)
}
checkLoggedIn = () => {
if (localStorage.getItem('loggedIn'))
return true
return false
}
}
export default App;
config-reflux.jsx
var Reflux = require('reflux');
var RefluxPromise = require('reflux-promise');
module.exports = function() {
// Uses the user agent's Promise implementation
Reflux.use(RefluxPromise(window.Promise)); // eslint-disable-line new-cap
};
Support.jsx
/* eslint-disable */
import React,{Component} from 'react'
import '../assets/styles/support.css'
import request from 'request'
import Breadcrumb from 'react-breadcrumb'
import moment from 'moment'
import Loader from '../components/loader'
import supportAction from '../actions/supportAction'
class Support extends Component {
constructor(props){
super(props)
this.base_url = process.env.REACT_APP_API_URL;
this.state = {
message : '',
messages:[]
}
}
componentDidMount(){
if (!this.checkLoggedIn()){
window.location = '/'
}
const that = this;
var buffer_id = this.props.params.id;
this.setState({buffer_id:buffer_id},function(){
this.fetchData()
})
var data = null
request.get({
url:`${this.base_url}/internshipbuffer/${buffer_id}`,
headers:{
'Authorization':`Bearer ${localStorage.getItem('auth-token')}`
}
},function(err,resp,body){
data = JSON.parse(body)
data.benefits = data.benefits.split('\n')
that.setState({data:data})
})
}
updateData = (result) => {
console.log(result)
}
fetchData = () => {
var params = {
'internshipbuffer':this.state.buffer_id
}
supportAction.getMessage(params).then(this.updateData)
}
getMessage = (next) => {
const that = this;
var url = `${this.base_url}/internshipbuffer/getChat?internshipbuffer=${this.state.buffer_id}`;
if (next){
url = `${this.base_url}/internshipbuffer/getChat?internshipbuffer=${this.state.buffer_id}&sort={"createdAt":-1}`
}
request.get({
url:url,
headers : {
"Authorization":`Bearer ${localStorage.getItem('auth-token')}`
}
},function(err,resp,body){
var body = JSON.parse(body)
console.log(body);
if (body){
var messages = that.state.messages
if (next){
messages = messages.concat(body.data.reverse())
}
else{
messages = body.data.reverse()
}
that.setState({messages:messages,messages_next:body.next})
}
})
}
render(){
return (
<div className="support-main">
{this.state.data ? this.renderContent() : this.renderLoading()}
</div>
)
}
checkLoggedIn = () => {
if (localStorage.getItem('loggedIn') ){
return true ;
}
return false;
}
renderLoading = () => {
return (
<Loader />
)
}
renderContent = () => {
const that = this;
var data = this.state.data;
return (
<div className="support-main-container">
<Breadcrumb
path={
[
{
path: '/messages',
label: 'Messages '
},
{
path: `/pending/${this.props.params.id}`,
label: ` ${data.title}`
}
]
}
separatorChar={' > '}
/>
<div className="card">
<div className="img-container">
<img alt={null} src={data.asseturl} id="support-brand-logo" width="50"/>
</div>
<ul className="card-brand-info-container">
<li className="bold">{localStorage.getItem('brand_name')}</li>
<li>Brand manager : {localStorage.getItem('brandmanager_name')}</li>
</ul>
<button onClick={this.editInternshipAction} className='support-edit-btn'>EDIT</button>
<div className="support-main-info">
<div> <span className="bold">Title</span> : {data.title}</div>
<div><span className="bold"> Description</span> : {data.description}</div>
<div><span className="bold"> Positions</span> : {data.positions}</div>
<div><span className="bold">Intenship Period</span> : {data.period} </div>
<div>
<span className="bold"> Benefits </span>:
<ul className="benefits-list">
{data.benefits[0]}
</ul>
</div>
<div>
<span className="bold"> Skills & Requirements</span> :
<ul className="skills-list">
{data.requirements}
</ul>
</div>
<div>
<span className="bold"> City : </span>
<ul className="city-list">
{
data.citiesvalid.map(function(item,i){
if (i !== (data.citiesvalid.length)-1)
return <li key={i} >{item},</li>
else
return <li key={i} >{item}</li>
})
}
</ul>
</div>
</div>
</div>
<div className="messages-container">
{
this.state.messages ?
this.state.messages.map(function(obj,i){
var messenger;
obj.brandmanager ? messenger = localStorage.getItem('brand_name') : messenger = 'Support'
return (that.renderMessage(obj.message,messenger,i,obj.createdAt))
}) : null
}
</div>
<div className="message-input-container">
<input className="prefix-messenger" placeholder={`${localStorage.getItem('brand_name')} :`} />
<input className="main-message" value={this.state.message} onChange={this.setMessage} />
</div>
<button onClick={this.sendMessage} className="messages-send-btn"><i style={{marginRight:'5px'}} className="fa fa-paper-plane" aria-hidden="true"></i>Send</button>
</div>
)
}
editInternshipAction = () => {
this.props.router.push(this.props.location.pathname+'/edit')
}
renderMessage = (message,messenger,i,message_date) => {
var data = this.state.data;
return (
<div key={i} className="support-message-box">
<hr />
<div className="img-container-message">
<img alt={null} src={data.asseturl} id="support-brand-logo" width="50"/>
</div>
<div className="main">
<div>
<span style={{color:'#c67763'}} className={`bold ${messenger}`}>{messenger}</span>
<span> ({moment(new Date(message_date)).format('LT')}) : {message} </span>
</div>
</div>
</div>
)
}
sendMessage = () => {
const that = this;
if (this.state.message){
request.put({
url:`${this.base_url}/internshipbuffer/putChat?internshipbuffer=${this.props.params.id}&message=${this.state.message}`,
headers:{
"Authorization":`Bearer ${localStorage.getItem('auth-token')}`
}
},function(err,resp,body){
that.setState({message:''})
that.getMessage();
})
}
else{
this.setState({message_error:'error',error_message:'Required'})
}
that.scrollToBottom(document.getElementsByClassName('messages-container')[0])
}
scrollToBottom = (element) => {
console.log(element.scrollHeight)
element.scrollTop = element.scrollHeight;
}
setMessage = (e) => {
this.setState({message:e.target.value})
if (e.target.value === ''){
this.setState({message_error:'error',error_message:'Required'})
}
else{
this.setState({message_error:'',error_message:''})
}
}
}
export default Support
supportAction.jsx
import Reflux from 'reflux'
import supportResource from '../resources/supportResource'
var actions = Reflux.createActions({
'getMessage': {
children: ['completed', 'failed']
}
});
console.log(actions)
actions.getMessage.listenAndPromise(supportResource.getMessage);
export default actions;
I have used reflux-promise module in my react app.
I have defined an action to fetch resource which fetches the data from the server.
I have used listenAndPromise in my action .
I have configured my app to use promise in config-reflux.jsx file.
Then too its showing listenAndPromise is not a function.
Any help ? Thanks !!
I think the order of what you import is wrong. Try to import the config before using this function.
import './util/config-reflux'; // <-- make sure you import it before any component or action using it
import Support from './pages/support';
config-reflux.jsx:
var Reflux = require('reflux');
var RefluxPromise = require('reflux-promise');
// Uses the user agent's Promise implementation
Reflux.use(RefluxPromise(window.Promise)); // eslint-disable-line new-cap
Related
I am trying to implement a simple e-commerce application where I have a home component and a cart component whenever I call handle increment or handle decrement, quantity variable is increased or decreased twice in both cart array and items array.so {item.quantity} changes 0,2,4 and so on and this is happening on both cart page and home page. I feel like this has something to do with not using spread operator properly while updating an object in an array.
please help me understand why is this happening and how to resolve it.
Here is my code
shop.jsx
import React, { Component } from 'react';
import Navbar from './nav';
import Cart from './cart';
import Home from './home';
import { Switch, Route } from 'react-router-dom';
class Shop extends Component {
state = {
items: [],
cart: []
}
componentDidMount() {
let items = [...this.state.items];
items.push({ id: 1, name: "product1", price: 100, quantity: 0 });
items.push({ id: 2, name: "product2", price: 200, quantity: 0 });
this.setState({ items });
}
handleIncrement = (item) => {
console.log('handle increment called');
let items = [...this.state.items];
let cart = [...this.state.cart];
let productIndex = items.indexOf(item);
let cartIndex = cart.indexOf(item);
items[productIndex].quantity += 1;
if (cartIndex === -1) {
item.quantity += 1;
cart.push(item);
}
else {
cart[cartIndex].quantity += 1;
}
this.setState({ cart, items });
}
handleDecrement = (item) => {
console.log('handle decrement called');
let items = [...this.state.items];
let cart = [...this.state.cart];
let productIndex = items.indexOf(item);
let cartIndex = cart.indexOf(item);
items[productIndex].quantity -= 1;
cart[cartIndex].quantity -= 1;
this.setState({ cart, items });
}
render() {
return (
<div>
<Navbar />
<Switch>
<Route path="/home" render={(props) => <Home
items={this.state.items}
handleAdd={this.handleAdd}
handleIncrement={this.handleIncrement}
handleDecrement={this.handleDecrement}
{...props} />} />
<Route path="/cart" render={(props) => <Cart
cart={this.state.cart}
handleIncrement={this.handleIncrement}
handleDecrement={this.handleDecrement}
{...props} />} />
</Switch>
</div>
);
}
}
export default Shop;
Home.jsx
import React from 'react';
const Home = (props) => {
return (
<div>
Home
<ul>
{props.items.map((item) => {
return (
<li key={item.id}>
{item.name} <br />
{item.price} <br />
{
item.quantity === 0 ?
<button onClick={() => props.handleIncrement(item)}>add</button> :
<div>
<button onClick={() => props.handleDecrement(item)}>-</button>
<button>{item.quantity}</button>
<button onClick={() => props.handleIncrement(item)}>+</button>
</div>
}
</li>
)
})
}
</ul>
</div>
);
}
export default Home;
cart.jsx
import React from 'react';
const Cart = (props) => {
return (
<div>
cart
<ul>
{props.cart.map((item) => {
return (
<li key={item.id}>
{item.name} <br />
{item.price} <br />
{item.quantity} <br />
{item.price * item.quantity}
</li>
)
})}
</ul>
</div>
);
}
export default Cart;
nav.jsx
import React from 'react';
import { Link } from 'react-router-dom';
const Navbar = () => {
return (
<div>
Navbar
<Link to="/home">Home</Link>
<Link to="/cart">Cart</Link>
</div>
);
}
export default Navbar;
Remove items[productIndex].quantity += 1; from handleIncrement and items[productIndex].quantity -= 1; from handleDecrement method.
This will work.
I have small problem with React Router and redirecting. After i have created a admin protected route, I want the user to be redirected to "user dashboard" after login. But my issue is that redirect is not working.
All is happening like this:
My Navigation componente, but this one i think is okay:
import React from 'react';
import { Link, withRouter } from 'react-router-dom';
import { signOut, isAuthUser } from '../../../utils/utils';
const isActive = (history, path) => {
if (history.location.pathname === path) {
return { color: '#ff9900' };
} else {
return { color: '#ffffff' };
}
};
const Navigation = ({ history }) => {
return (
<nav>
<ul className='nav nav-tabs bg-primary'>
<li className='nav-item'>
<Link className='nav-link' style={isActive(history, '/')} to='/'>
Home
</Link>
<Link
className='nav-link'
style={isActive(history, '/user/dashboard')}
to='/user/dashboard'
>
Dashboard
</Link>
{!isAuthUser() && (
<div>
<Link
className='nav-link'
style={isActive(history, '/signup')}
to='/signup'
>
Signup
</Link>
<Link
className='nav-link'
style={isActive(history, '/signin')}
to='/signin'
>
Signin
</Link>
</div>
)}
{isAuthUser() && (
<Link
className='nav-link'
style={isActive(history, '/signout')}
onClick={() => signOut()}
to='/'
>
Sign Out
</Link>
)}
</li>
</ul>
</nav>
);
};
export default withRouter(Navigation);
My app.js with Routes
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import MainLayout from '../src/components/layout/MainLayout/MainLayout';
import Signup from './components/views/Signup/Signup';
import Signin from './components/views/Signin/Signin';
import Home from './components/views/Home/Home';
import PrivateRoute from './components/common/ProvateRoute/PrivateRoute';
import UserDashboard from './components/views/UserDashboard/UserDashboard';
function App() {
return (
<BrowserRouter>
<MainLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/signin' component={Signin} />
<Route exact path='/signup' component={Signup} />
<PrivateRoute exact path='/user/dashboard' component={UserDashboard} />
</Switch>
</MainLayout>
</BrowserRouter>
);
}
export default App;
My utils.js with some helper functions
export const signOut = () => {
if (typeof window !== 'undefined') {
localStorage.removeItem('jwt');
return fetch('http://localhost:8000/api/signout', { method: 'GET' }).then(res => {
console.log('signout', res);
});
}
};
export const authenticateUser = data => {
if (typeof window !== 'undefined') {
localStorage.setItem('jwt', JSON.stringify(data));
}
};
//check if user is auth and there is jwt item in localstorage. menu render
export const isAuthUser = () => {
if (typeof window == 'undefined') {
return false;
}
if (localStorage.getItem('jwt')) {
return JSON.parse(localStorage.getItem('jwt'));
} else {
return false;
}
};
So those looks okay in my opinion, but still i decided to post those here.
As all things that are most related are in my tow files: UserDashboard and Signin
My Signin.js looks like this:
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import axios from 'axios';
import Layout from '../../layout/Layout/Layout';
import { authenticateUser, isAuthUser } from '../../../utils/utils';
class Signin extends Component {
state = {
formData: {
email: '',
password: '',
},
userRedirect: false,
};
onChange = e => {
const { formData } = this.state;
//assign form data to new variable
let newFormData = { ...formData };
newFormData[e.target.name] = e.target.value;
this.setState({
formData: newFormData,
});
};
signIn = user => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
axios
.post('http://localhost:8000/api/signin', user, config)
.then(res => authenticateUser(res.data));
this.setState({
formData: { email: '', password: '' },
userRedirect: true,
});
};
onSubmit = e => {
const { password, email } = this.state.formData;
e.preventDefault();
this.signIn({ email, password });
};
signInForm = (email, password) => (
<form onSubmit={this.onSubmit}>
<div className='form-group'>
<label className='text-muted'>Email</label>
<input
type='email'
name='email'
value={email}
onChange={this.onChange}
className='form-control'
></input>
</div>
<div className='form-group'>
<label className='text-muted'>Password</label>
<input
type='password'
name='password'
minLength='6'
value={password}
onChange={this.onChange}
className='form-control'
></input>
</div>
<button className='btn btn-primary'>Submit</button>
</form>
);
redirecUser = () => {
const { userRedirect } = this.state;
const { user } = isAuthUser();
if (userRedirect === true) {
if (user && user.role === 1) {
return <Redirect to='/admin/dashboard' />;
} else {
return <Redirect to='/user/dashboard' />;
}
}
};
render() {
const { email, password } = this.state.formData;
return (
<Layout
title='Signin'
description='Login to your account'
className='container col-md-8 offset-md-2'
>
{this.signInForm(email, password)}
{this.redirecUser()}
</Layout>
);
}
}
export default Signin;
Here I am rendering all with signInForm and passing all data that i want with signIn(). From this i get user data: _id, email, password, role and token. This is sent to local storage.
Based on that what i get i want admin dashboard or user dashboard.
I have now olny user Dashboard
import React from 'react';
import { isAuthUser } from '../../../utils/utils';
import Layout from '../../layout/Layout/Layout';
const UserDashboard = () => {
const {
payload: {
user: { name, email, role },
},
} = isAuthUser();
return (
<Layout
title='User Dashboard'
description={`Wlecome ${name}`}
className='container col-md-8 offset-md-2'
>
<div className='card mb-5'>
<h3 className='card-header'>User information</h3>
<ul className='list-group'>
<li className='list-group-item'>{name}</li>
<li className='list-group-item'>{email}</li>
<li className='list-group-item'>
{role === 1 ? 'Admin' : 'Registered User'}
</li>
</ul>
</div>
<div className='card'>
<h3 className='card-header'>Purchase history</h3>
<ul className='list-group'>
<li className='list-group-item'>History</li>
</ul>
</div>
</Layout>
);
};
export default UserDashboard;
I have created PrivateRoute component based on documentation
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuthUser } from '../../../utils/utils';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
isAuthUser() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/signin', state: { from: props.location } }} />
)
}
/>
);
export default PrivateRoute;
I do get all data in local storage, but after signin user is not redirected
Thanks for any help
Since you are trying to redirect from a function in your Signin.js try using history api.
this.props.history.push('/admin/dashboard')
instead of
<Redirect to='/admin/dashboard' />;
Description of problem:
Changing the id (numbers only) of this url via the link tag does not update the page (but does change the url in the adress bar). Hitting refresh afterward will show the updated page.
http://localhost:8080/video/id/7564
Right clicking to open the link in a new tab, or changing the link path to a completely different page works as expected.
My app.js file
import React from 'react'
import { Router, Route, Switch } from 'react-router-dom'
import RenderHomepage from '../components/homePage/RenderHomepage'
import RenderChannelPage from '../components/channelPage/RenderChannelPage'
import RenderVideoPage from '../components/videoPage/RenderVideoPage'
import RenderSearchPage from '../components/searchPage/RenderSearchPage'
import PageNotFound from '../components/PageNotFound'
import history from '../history'
const App = () => {
return (
<div>
<Router history={history}>
<Switch>
<Route path="/" exact component={RenderHomepage} />
<Route path="/channel" component={RenderChannelPage} />
<Route path="/video/id" component={RenderVideoPage} />
<Route path="/search" component={RenderSearchPage} />
<Route path="/404" exact component={PageNotFound} />
<Route component={PageNotFound} />
</Switch>
</Router>
</div>
)
}
export default App
Link tag in UpNextVideos component:
import React from 'react'
import { Link } from 'react-router-dom'
...
<Link to={{pathname: vid.id}}>
<h3 className={`${p}-sidebar-grid-video-title`}>{capitalizeFirstLetter(vid.tags)}</h3>
</Link>
...
How the components in question are nested:
<RenderVideoPage>
<VideoPage>
<UpNextVideos>
RenderVideoPage component:
import React from 'react'
import VideoPage from './VideoPage'
import Header from '../Header'
import HeaderMobile from '../HeaderMobile'
import FooterMobile from '../FooterMobile'
import ActivityFeed from '../ActivityFeed'
const RenderVideoPage = () => {
return (
<div className="videoPage-body">
<HeaderMobile />
<Header />
<ActivityFeed page={'home'} />
<VideoPage />
<FooterMobile page={'video'} />
</div>
)
}
export default RenderVideoPage
VideoPage component:
import React, { useEffect, useState } from 'react'
import axios from 'axios'
import history from '../../history'
import handleMediaQueries from './containers/handleMediaQueries'
import setDislikes from './containers/setDislikes'
import NewSubscribers from './NewSubscribers'
import CommentSection from './CommentSection'
import UpNextVideos from './UpNextVideos'
import DescriptionBox from './DescriptionBox'
import VideoNotFound from './VideoNotFound'
import { fetchVideoFromID, fetchPictureFromID } from '../../containers/api'
import { thumbsUp, thumbsDown } from '../svgs'
import {
abbreviateNumber,
capitalizeFirstLetter,
randomDate } from '../../containers/helperFunctions'
const VideoPage = () => {
const [p, setPrefix] = useState("videoPage")
const [state, setState] = useState({
loading: true,
error: false
})
useEffect(() => {
if (state.loading) extractDataFromUrl()
else handleMediaQueries()
}, [state.loading])
const fetchVideo = async (id, picAuthorID) => {
let response = await fetchVideoFromID(id)
if (!response) setState(prevState => ({...prevState, error: true}))
else mapVideoResponseToHTML(response.data.hits, picAuthorID)
}
const mapVideoResponseToHTML = (response, picAuthorID) => {
let responseAsHtml = response.map(vid => {
return {
video:
<div className={`${p}-video-wrapper posRelative`} key={vid.id}>
<a className={`${p}-pixabay-src`} href={vid.pageURL}>?</a>
<video
poster="https://i.imgur.com/Us5ckqm.jpg"
className={`${p}-video clickable`}
src={vid.videos.large.url}
controls autoPlay>
</video>
<div className={`${p}-video-info-wrapper`}>
<div className={`${p}-video-title-box`}>
<h1 className={`${p}-video-title`}>{capitalizeFirstLetter(vid.tags)}</h1>
<span className={`${p}-video-views`}>{abbreviateNumber(Number(vid.downloads).toLocaleString())} views</span>
<span className={`${p}-video-date`}>{randomDate()}</span>
</div>
<div className={`${p}-video-options`}>
<div className="thumbs">
<div className={`${p}-video-options-thumbsUp`}>{thumbsUp(20)}
<span className={`${p}-video-options-thumbsUp-text`}>{abbreviateNumber(vid.likes)}</span>
</div>
<div className={`${p}-video-options-thumbsDown`}>{thumbsDown(20)}
<span className={`${p}-video-options-thumbsDown-text`}>{setDislikes(vid.likes)}</span>
</div>
<div className={`${p}-video-options-likebar`}></div>
</div>
<span className={`${p}-video-options-share`}>Share</span>
<span className={`${p}-video-options-save`}>Save</span>
<span className={`${p}-video-options-ellipses`}>...</span>
</div>
</div>
</div>,
authorFollowers: vid.views,
vidAuthorID: vid.id,
author: picAuthorID ? 'Loading' : vid.user,
authorAvatar: picAuthorID ? null : vid.userImageURL,
views: vid.downloads
}
})
responseAsHtml = responseAsHtml[0]
setState(prevState => ({...prevState, ...responseAsHtml, loading: false}))
if (picAuthorID) fetchAuthorAvatar(picAuthorID)
}
const extractDataFromUrl = () => {
const currentURL = window.location.href
const urlAsArray = currentURL.split('/')
const urlID = urlAsArray[5].split('-')
const videoID = urlID[0]
const picAuthorID = urlID[1]
// Author avatars are random except on the home page.
// if url isnt from homepage, then use videoID
// if url is from homepage, send that avatarID
if (urlID.includes('000')) {
fetchVideo(videoID)
} else {
setState(prevState => ({...prevState, picAuthorID: picAuthorID}))
fetchVideo(videoID, picAuthorID)
}
}
const fetchAuthorAvatar = async (id) => {
const response = await fetchPictureFromID(id)
const authorName = response.data.hits[0].user
const authorAvatar = response.data.hits[0].previewURL
setState(prevState => ({
...prevState,
authorAvatar: authorAvatar,
author: capitalizeFirstLetter(authorName)
}))
}
return (
<div>
{ state.error ? <VideoNotFound /> : null}
{ state.loading === true ? null
:
<div className={`${p}-page-wrapper`}>
<main className={`${p}-main`}>
{state.video}
<DescriptionBox props={state} />
<div className={`${p}-suggested-videos-mobile`}></div>
<div className={`${p}-new-subscribers-wrapper`}>
<h2 className={`${p}-new-subscribers-text`}>{`New Subscribers to ${state.author}`}</h2>
<NewSubscribers />
</div>
<div className={`${p}-comment-section`}>
<CommentSection views={state.views}/>
</div>
</main>
<aside className={`${p}-sidebar`}>
<UpNextVideos />
</aside>
</div>
}
</div>
)
}
export default VideoPage
UpNextVideos component:
import React, { useEffect, useState, useRef, useCallback } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import { videoQuery } from '../../words'
import { fetchVideos } from '../../containers/api'
import {
capitalizeFirstLetter,
uuid,
getRandom,
abbreviateNumber
} from '../../containers/helperFunctions'
const UpNextVideos = () => {
const [p, setPrefix] = useState("videoPage")
const [nextVideos, setNextVideos] = useState([])
useEffect(() => {
fetchUpNextVideos(15, getRandom(videoQuery))
}, [])
// INFINITE SCROLL
const observer = useRef()
const lastUpNextVideo = useCallback(lastVideoNode => {
// Re-hookup observer to last post, to include fetch data callback
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver(entries => {
const lastVideo = entries[0]
if (lastVideo.isIntersecting && window.innerWidth <= 1000) {
document.querySelector('.videoPage-show-more-button').classList.add('show')
}
else if (lastVideo.isIntersecting && window.innerWidth > 1000) {
document.querySelector('.videoPage-show-more-button').classList.remove('show')
fetchUpNextVideos(20, getRandom(videoQuery))
}
})
if (lastVideoNode) observer.current.observe(lastVideoNode)
})
const fetchUpNextVideos = async (amount, query) => {
let response = await fetchVideos(amount, ...Array(2), query)
response = response.data.hits
const responseAsHtml = response.map((vid, index) => {
return (
<div className={`${p}-sidebar-grid-video-wrapper`} key={uuid()} ref={response.length === index + 1 ? lastUpNextVideo : null}>
<div className={`${p}-sidebar-grid-video`}>
<a href={`/video/id/${vid.id}-000`}>
<video
className={`${p}-upnext-video`}
onMouseOver={event => event.target.play()}
onMouseOut={event => event.target.pause()}
src={`${vid.videos.tiny.url}#t=1`}
muted >
</video>
</a>
</div>
<a href={`/video/id/${vid.id}`}>
<h3 className={`${p}-sidebar-grid-video-title`}>{capitalizeFirstLetter(vid.tags)}</h3>
</a>
<a href={`/channel/000${vid.id}`}>
<p className={`${p}-sidebar-grid-video-author`}>{vid.user}</p>
</a>
<p className={`${p}-sidebar-grid-video-views-text`}>{abbreviateNumber(vid.downloads)} views</p>
</div>
)
})
setNextVideos(prevState => ([...prevState, ...responseAsHtml]))
}
return (
<div>
<div className={`${p}-sidebar-text-top`}>
<span className={`${p}-sidebar-text-upnext`}>Up next</span>
<span className={`${p}-sidebar-text-autoplay`}>Autoplay</span>
</div>
<div className={`${p}-sidebar-grid-wrapper`}>
{nextVideos}
</div>
<button
className={`${p}-show-more-button`}
onMouseDown={() => fetchUpNextVideos(15, getRandom(videoQuery))}>
Show More
</button>
</div>
)
}
export default UpNextVideos
What I've tried:
Wrapping the <Link> tag with <Router history={history} />
Wrapping the <Link> tag with <BrowserRouter>
Wrapping the export statement withRouter(UpNextVideos)
Using a plain string instead of an object, as described in react-router-docs
Ok, I believe this issue lies in your VideoPage component.
useEffect(() => {
if (state.loading) extractDataFromUrl()
else handleMediaQueries()
}, [state.loading]);
You only ever have state.loading true once, when the component mounts. This only processes your URL once, so when the URL changes this component isn't aware of it.
This is your route currently
<Route path="/video/id" component={RenderVideoPage} />
now assuming your URLs are shaped "/video/id/" then you can define your route to have a parameter
<Route path="/video/id/:videoId" component={RenderVideoPage} />
If you wrap this component with react-router-dom's withRouter HOC you can easily get the id path param and add it to an effect to recompute all the video data.
export default withRouter(VideoPage)
withRouter injects the location, match, and history props from the closest Route ancestor. Here's an example of getting the id param and triggering an effect when its value updates.
const VideoPage = ({ match }) => {
const { params } = match;
useEffect(() => { /* do something with new id */ }, [params.videoId]);
}
G'day
Following the tutorials in the older versions of React I have this in
my routes
<Route path="/people" component={People} />
<Route path="/people_new" component={CreatePeople} />
<Route path="/people/:email" component={ShowPeople} />
<Route path="/people_edit/:email" component={EditPeople} />
I have been implementing an upgrade to 16. The ShowPeople is not being called. Is this a change in the routing?
More code
people.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { apiGetData } from '../actions/api'; // our home spun API
import { FETCH_PEOPLE } from '../actions/api';
const API_KEY = ''; // not needed at the moment. reminder.
const URL = 'people';
//----------------------------
class People extends Component {
//--------------------
componentWillMount() {
const url = `${URL}${API_KEY}`; // target URI will fetch ALL entries
console.log('In people.js URL == ');
console.log(url);
this.props.apiGetData(FETCH_PEOPLE, url); // call the API
}
//-------------
renderPeople() {
console.log('In renderPeople :', this.props.people);
return this.props.people.map((person) => {
return(
<li className="list-group-item" key={person.id}>
<Link to={"/people/" + person.email}>
<strong>{person.name}</strong>
<span className="pull-right">{person.surname}</span>
</Link>
</li>
);
});
}
//--------
render() {
console.log('made it into People');
return(
<div>
<div className="jumbotron">
<h2>Asset-IQ - Live Build - May 2017</h2>
<h2>List of People</h2>
</div>
<div className="text-right">
<Link to="/people_new" className="btn btn-primary">
New Person
</Link>
</div>
<div>
<ul className="list-group">
{this.renderPeople()}
</ul>
</div>
</div>
);
}
}
//-------------------------------
function mapStateToProps(state) {
return { people: state.people.all };
}
//------------------------------------------------------------------------
export default connect(mapStateToProps, {apiGetData: apiGetData })(People);
//--------------------- EOF ---------------------------------------------
I was running a VERY early versionof React that came with a boilerplate
I got from a Udemy course. I didn't realise until a few weeks ago I was
running 0.9x!
The app is only half written so now is a good time to join this century.
Here is the component that USED to be rendered
// vim: set expandtab tabstop=4 shiftwidth=4 autoindent:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { apiGetData } from '../actions/api';
import { apiDeleteData } from '../actions/api';
import { FETCH_PERSON } from '../actions/api';
import { DELETE_PERSON } from '../actions/api';
//--------------------------------
class ShowPeople extends Component {
//--------------------
componentDidMount() {
const target = `people/${this.props.match.params.email}`; // email is the id passed in as a prop
console.log(target); // quick look at the value
this.props.apiGetData(FETCH_PERSON, target); // get the record from Python
}
//---------------
onDeleteClick() {
const target = `${this.props.match.params.email}`;
let ok = confirm("Sure you want to ZAP this mofo?");
if (ok) {
this.props.apiDeleteData(DELETE_PERSON, target).then(() =>
alert("They gone...."));
}
//browserHistory.push('/people'); HOW do we do this in React 16
}
//--------
render() {
const { person } = this.props;
console.log("In person");
console.log(person);
if (!person) {
return (
<div>
SPINNER....
</div>
);
}
//------
return (
<div>
<div className="jumbotron">
<h2>Asset-IQ - Live Build - May 2017</h2>
<h2>Person Detail</h2>
</div>
<h2>{person.name} {person.surname}</h2>
<Link to="/people" className="btn btn-primary">Back</Link>
<button className="btn btn-warning pull-right" onClick={this.onDeleteClick.bind(this)}>
Delete
</button>
</div>
);
}
}
//--------------------------------
function mapStateToProps(state) {
return { person: state.people.person };
}
//-----------------------------------------------------------------------------------
export default connect(mapStateToProps, { apiGetData, apiDeleteData })(ShowPeople);
//--------------------- EOF ---------------------------------------------------
Cheers
You need to use the exact attribute in your routes to make it work correctly.
<Route path="/people" exact component={People} />
<Route path="/people_new" component={CreatePeople} />
<Route path="/people/:email" component={ShowPeople} />
<Route path="/people_edit/:email" component={EditPeople}
Here you have more detailed information about this attribute.
And here you have a live demo.
I am trying to implement the following: https://www.npmjs.com/package/react-sticky
in my code as follow:
import React from 'react';
import Video from './../video.jsx';
import Overview from './overview.jsx';
import Photography from './photography.jsx';
import Details from './details.jsx';
import Cast from './cast.jsx';
import porgectsCollection from './../../data/projectInfo.js';
import { StickyContainer, Sticky } from 'react-sticky';
class Nav extends React.Component {
constructor(props) {
super(props);
this.state = {
mobileMenu: false
};
}
showMobileMenu () {
this.setState({ mobileMenu: !this.state.mobileMenu });
}
render () {
let links = this.props.project.links.map(function(el, i){
return <li key={i}>{el}</li>;
});
const open = this.state.mobileMenu ? ' open' : '';
return (
<StickyContainer>
<span onClick={this.showMobileMenu.bind(this)} className="mobile-trigger">X</span>
<Sticky topOffset={100} stickyClassName="sticky-nav">
<nav className={"secondary-nav" + open}>
<ul>
{links}
</ul>
</nav>
</Sticky>
</StickyContainer>
);
}
}
class SingleProject extends React.Component {
getProjectDataFromUrl() {
return porgectsCollection.filter(el => el.title === this.props.params.id);
}
render () {
let data = this.getProjectDataFromUrl(),
project = data[0];
console.log(project);
return (
<section className="project-page">
<Video project={project} />
<Nav project={project} />
<Overview project={project} />
<Photography project={project} />
<Details project={project} />
<Cast project={project} />
</section>
);
}
}
export default SingleProject;
I would hope that when "Sticky" reached 100px from the top it would get a custom class "sticky-nav" applied to it. However the nav keeps on scrolling without getting stuck at all. I can see the divs applied around my markup with the extra padding but no more then that.
URL project: https://github.com/WebTerminator/aldemar,
file in question is singleProject.jsx
import React from 'react';
import Video from './../video.jsx';
import Overview from './overview.jsx';
import Photography from './photography.jsx';
import Details from './details.jsx';
import Cast from './cast.jsx';
import porgectsCollection from './../../data/projectInfo.js';
import { StickyContainer, Sticky } from 'react-sticky';
class Nav extends React.Component {
constructor(props) {
super(props);
this.state = {
mobileMenu: false
};
}
showMobileMenu () {
this.setState({ mobileMenu: !this.state.mobileMenu });
}
render () {
let links = this.props.project.links.map(function(el, i){
return <li key={i}>{el}</li>;
});
const open = this.state.mobileMenu ? ' open' : '';
return (
<Sticky stickyClassName="sticky-nav" topOffset={-100}>
<span onClick={this.showMobileMenu.bind(this)} className="mobile-trigger">X</span>
<nav className={"secondary-nav" + open}>
<ul>
{links}
</ul>
</nav>
</Sticky>
);
}
}
class SingleProject extends React.Component {
getProjectDataFromUrl() {
return porgectsCollection.filter(el => el.title === this.props.params.id);
}
render () {
let data = this.getProjectDataFromUrl(),
project = data[0];
return (
<section className="project-page">
<StickyContainer>
<Video project={project} />
<Nav project={project} />
<Overview project={project} />
<Photography project={project} />
<Details project={project} />
<Cast project={project} />
</StickyContainer>
</section>
);
}
}
export default SingleProject;