aboutsummaryrefslogtreecommitdiff
path: root/vnext
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2019-11-14 13:44:52 +0300
committerGravatar Vitaly Takmazov2023-01-13 10:37:55 +0300
commit841ec978bae3297357c3157a3adf846648771770 (patch)
tree7163d3a814fd68193f04415e5f1aeaf8a2cfd0a4 /vnext
parent3900358ca6eeac546cbe0eb0bd36572ddc404634 (diff)
react-router-dom hooks
Diffstat (limited to 'vnext')
-rw-r--r--vnext/src/App.js80
-rw-r--r--vnext/src/ui/Chat.js15
-rw-r--r--vnext/src/ui/Feeds.js111
-rw-r--r--vnext/src/ui/Header.js20
-rw-r--r--vnext/src/ui/Login.js12
-rw-r--r--vnext/src/ui/Post.js7
-rw-r--r--vnext/src/ui/SearchBox.js10
-rw-r--r--vnext/src/ui/Thread.js16
-rw-r--r--vnext/src/ui/UserInfo.js28
-rw-r--r--vnext/src/ui/Users.js25
10 files changed, 175 insertions, 149 deletions
diff --git a/vnext/src/App.js b/vnext/src/App.js
index 4c1e72d1..4251d52c 100644
--- a/vnext/src/App.js
+++ b/vnext/src/App.js
@@ -30,15 +30,14 @@ export default function App() {
useEffect(() => {
svg4everybody();
+ let params = qs.parse(window.location.search.substring(1));
+ if (params.hash) {
+ cookie.save('hash', params.hash, { path: '/' });
+ let retpath = params.retpath || `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
+ window.history.replaceState({}, document.title, retpath);
+ }
}, []);
- let params = qs.parse(window.location.search.substring(1));
- if (params.hash) {
- cookie.save('hash', params.hash, { path: '/' });
- let retpath = params.retpath || `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
- window.history.replaceState({}, document.title, retpath);
- }
-
/**
* @type {import('./api').SecureUser}
*/
@@ -135,17 +134,6 @@ export default function App() {
}, [hash]);
/**
- * @param {{ pathname: any; search: string; }[]} history
- * @param {any} pathname
- * @param {any} searchString
- */
- let search = (history, pathname, searchString) => {
- let location = {};
- location.pathname = pathname;
- location.search = `?search=${searchString}`;
- history.push(location);
- };
- /**
* @param {import("./api").SecureUser} visitor
*/
let auth = (visitor) => {
@@ -163,23 +151,45 @@ export default function App() {
<div id="wrapper">
<section id="content" ref={contentRef} className={scrollState.top ? elClassTop : ''}>
<Switch>
- <Route exact path="/" render={(props) => <Discussions visitor={visitor} {...props} />} />
- <Route exact path="/home" render={(props) => <Home visitor={visitor} {...props} />} />
- <Route exact path="/discover" render={(props) =>
- <Discover visitor={visitor} {...props} />
- } />
- <Route exact path="/settings" render={(props) =>
- <Settings visitor={visitor} {...props} onChange={auth} />
- } />
- <Route exact path="/login" render={(props) => <Login visitor={visitor} {...props} onAuth={auth} />} />
- <Route exact path="/post" render={(props) => <Post visitor={visitor} {...props} />} />
- <Route exact path="/pm" render={(props) => <Contacts visitor={visitor} {...props} />} />
- <Route exact path="/pm/:user" render={(props) => <Chat connection={eventSource} visitor={visitor} {...props} />} />
- <Route exact path="/:user/friends" render={(props) => <Friends visitor={visitor} {...props} />} />
- <Route exact path="/:user/readers" render={(props) => <Readers visitor={visitor} {...props} />} />
- <Route exact path="/:user" render={(props) => <Blog visitor={visitor} {...props} />} />
- <Route exact path="/tag/:tag" render={(props) => <Tag visitor={visitor} {...props} />} />
- <Route exact path="/:user/:mid" render={(props) => <Thread connection={eventSource} visitor={visitor} {...props} />} />
+ <Route exact path="/">
+ <Discussions visitor={visitor} />
+ </Route>
+ <Route exact path="/home">
+ <Home visitor={visitor} />
+ </Route>
+ <Route exact path="/discover">
+ <Discover visitor={visitor} />
+ </Route>
+ <Route exact path="/settings">
+ <Settings visitor={visitor} onChange={auth} />
+ </Route>
+ <Route exact path="/login">
+ <Login visitor={visitor} onAuth={auth} />
+ </Route>
+ <Route exact path="/post">
+ <Post visitor={visitor} />
+ </Route>
+ <Route exact path="/pm">
+ <Contacts visitor={visitor} />
+ </Route>
+ <Route exact path="/pm/:user">
+ <Chat connection={eventSource} visitor={visitor} />
+ </Route>
+ <Route exact path="/:user/friends">
+ <Friends visitor={visitor} />
+ </Route>
+ <Route exact path="/:user/readers">
+ <Readers visitor={visitor} />
+ </Route>
+ <Route exact path="/:user">
+ <Blog visitor={visitor} />
+ </Route>
+ <Route exact path="/tag/:tag">
+ <Tag visitor={visitor} />
+ </Route>
+ <Route exact path="/:user/:mid">
+ <Thread connection={eventSource} visitor={visitor} />
+ </Route>
</Switch>
</section>
{
diff --git a/vnext/src/ui/Chat.js b/vnext/src/ui/Chat.js
index 00e8eb3c..90c99c27 100644
--- a/vnext/src/ui/Chat.js
+++ b/vnext/src/ui/Chat.js
@@ -1,4 +1,5 @@
import React, { useEffect, useState, useCallback } from 'react';
+import { useParams } from 'react-router-dom';
import moment from 'moment';
import PM from './PM';
@@ -13,7 +14,6 @@ import './Chat.css';
* @typedef {Object} ChatProps
* @property {import('../api').SecureUser} visitor
* @property {EventSource} connection
- * @property {import('react-router').match} match
*/
/**
@@ -22,6 +22,7 @@ import './Chat.css';
*/
export default function Chat(props) {
const [chats, setChats] = useState([]);
+ const params = useParams();
let loadChat = useCallback((uname) => {
const { hash } = props.visitor;
@@ -36,32 +37,32 @@ export default function Chat(props) {
let onMessage = useCallback((json) => {
const msg = JSON.parse(json.data);
- if (msg.user.uname === props.match.params.user) {
+ if (msg.user.uname === params.user) {
setChats((oldChat) => {
return [msg, ...oldChat];
});
}
- }, [props.match.params.user]);
+ }, [params.user]);
let onSend = (template) => {
pm(template.to.uname, template.body)
.then(res => {
- loadChat(props.match.params.user);
+ loadChat(params.user);
}).catch(console.log);
};
useEffect(() => {
if (props.connection.addEventListener) {
props.connection.addEventListener('msg', onMessage);
}
- loadChat(props.match.params.user);
+ loadChat(params.user);
console.log(props.connection);
return () => {
if (props.connection.removeEventListener) {
props.connection.removeEventListener('msg', onMessage);
}
};
- }, [props.connection, onMessage, loadChat, props.match.params.user]);
- const uname = props.match.params.user;
+ }, [props.connection, onMessage, loadChat, params.user]);
+ const uname = params.user;
return (
<div className="msg-cont">
<UserInfo user={uname} />
diff --git a/vnext/src/ui/Feeds.js b/vnext/src/ui/Feeds.js
index e687f1e2..27a8376f 100644
--- a/vnext/src/ui/Feeds.js
+++ b/vnext/src/ui/Feeds.js
@@ -1,6 +1,6 @@
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useEffect } from 'react';
+import { Link, useLocation, useHistory, useParams } from 'react-router-dom';
-import { Link } from 'react-router-dom';
import qs from 'qs';
import moment from 'moment';
@@ -20,9 +20,6 @@ import { getMessages } from '../api';
/**
* @typedef {Object} PageProps
- * @property {import('history').History} history
- * @property {import('history').Location} location
- * @property {import('react-router').match} match
* @property {string=} search
* @property {import('../api').SecureUser} visitor
* @property {import('../api').Message[]=} msgs
@@ -31,34 +28,38 @@ import { getMessages } from '../api';
/**
* @param {PageProps} props
*/
-export function Discover(props) {
- let search = qs.parse(props.location.search.substring(1));
+export function Discover({ visitor }) {
+ const location = useLocation();
+ let search = qs.parse(location.search.substring(1));
const query = {
baseUrl: '/api/messages',
search: search,
pageParam: search.search ? 'page' : 'before_mid'
};
- return (<Feed authRequired={false} query={query} {...props} />);
+ return (<Feed authRequired={false} query={query} visitor={visitor} />);
}
/**
* @param {PageProps} props
*/
-export function Discussions(props) {
+export function Discussions({ visitor }) {
const query = {
baseUrl: '/api/messages/discussions',
pageParam: 'to'
};
- return (<Feed authRequired={false} query={query} {...props} />);
+ return (<Feed authRequired={false} query={query} visitor={visitor} />);
}
/**
* @param {PageProps} props
*/
-export function Blog(props) {
- const { user } = props.match.params;
- let search = qs.parse(props.location.search.substring(1));
- search.uname = user;
+export function Blog({ visitor }) {
+ const { user } = useParams();
+ const location = useLocation();
+ const search = {
+ ...qs.parse(location.search.substring(1)),
+ uname: user
+ };
const query = {
baseUrl: '/api/messages',
search: search,
@@ -67,9 +68,9 @@ export function Blog(props) {
return (
<>
<div className="msg-cont">
- <UserInfo user={user} />
+ <UserInfo uname={user} />
</div>
- <Feed authRequired={false} query={query} {...props} />
+ <Feed authRequired={false} query={query} visitor={visitor} />
</>
);
}
@@ -77,8 +78,9 @@ export function Blog(props) {
/**
* @param {PageProps} props
*/
-export function Tag(props) {
- const { tag } = props.match.params;
+export function Tag({ visitor }) {
+ const params = useParams();
+ const { tag } = params;
const query = {
baseUrl: '/api/messages',
search: {
@@ -86,50 +88,47 @@ export function Tag(props) {
},
pageParam: 'before_mid'
};
- return (<Feed authRequired={false} query={query} {...props} />);
+ return (<Feed authRequired={false} query={query} visitor={visitor} />);
}
/**
* @param {PageProps} props
*/
-export function Home(props) {
+export function Home({ visitor }) {
const query = {
baseUrl: '/api/home',
pageParam: 'before_mid'
};
- return (<Feed authRequired={true} query={query} {...props} />);
+ return (<Feed authRequired={true} query={query} visitor={visitor} />);
}
/**
* @typedef {Object} FeedState
* @property authRequired?: boolean
* @property visitor: import('../api').SecureUser
- * @property history: import('history').History
- * @property location: import('history').Location
* @property msgs: import('../api').Message[]
- * @property query: Query
+ * @property { Query} query
*/
/**
* @param {FeedState} props
*/
-function Feed(props) {
+function Feed({ visitor, query, authRequired }) {
+ const location = useLocation();
+ const history = useHistory();
const [state, setState] = useState({
- history: props.history,
- authRequired: props.authRequired,
- query: props.query,
- hash: props.visitor.hash,
- filter: props.location.search.substring(1),
+ authRequired: authRequired,
+ hash: visitor.hash,
msgs: [],
- loading: true,
nextpage: null,
error: false,
tag: ''
});
-
- const stateRef = useRef(state);
+ const [loading, setLoading] = useState(false);
useEffect(() => {
+ setLoading(true);
+ const filter = location.search.substring(1);
let getPageParam = (pageParam, lastMessage, filterParams) => {
const pageValue = pageParam === 'before_mid' ? lastMessage.mid : pageParam === 'page' ? (Number(filterParams.page) || 0) + 1 : moment.utc(lastMessage.updated).valueOf();
let newFilter = { ...filterParams };
@@ -138,35 +137,39 @@ function Feed(props) {
};
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
- const filterParams = qs.parse(stateRef.current.filter);
- let params = Object.assign({}, filterParams || {}, stateRef.current.query.search || {});
- let url = stateRef.current.query.baseUrl;
- if (stateRef.current.hash) {
- params.hash = stateRef.current.hash;
+ const filterParams = qs.parse(filter);
+ let params = Object.assign({}, filterParams || {}, query.search || {});
+ let url = query.baseUrl;
+ if (state.hash) {
+ params.hash = state.hash;
}
- if (!params.hash && stateRef.current.authRequired) {
- stateRef.current.history.push('/');
+ if (!params.hash && state.authRequired) {
+ history.push('/');
}
getMessages(url, params)
.then(response => {
const { data } = response;
- const { pageParam } = stateRef.current.query;
+ const { pageParam } = query;
const lastMessage = data.slice(-1)[0] || {};
const nextpage = getPageParam(pageParam, lastMessage, filterParams);
- setState({
- ...stateRef.current,
- msgs: data,
- loading: false,
- nextpage: nextpage,
- tag: qs.parse(location.search.substring(1))['tag'] || ''
+ setState((prevState) => {
+ return {
+ ...prevState,
+ msgs: data,
+ nextpage: nextpage,
+ tag: qs.parse(location.search.substring(1))['tag'] || ''
+ };
});
+ setLoading(false);
}).catch(ex => {
- setState({
- ...stateRef.current,
- error: true
+ setState((prevState) => {
+ return {
+ ...prevState,
+ error: true
+ };
});
});
- }, []);
+ }, [location.search, state.hash, state.authRequired, history, query]);
return (state.msgs.length > 0 ? (
<div className="msgs">
{
@@ -180,16 +183,16 @@ function Feed(props) {
}
{
state.msgs.map(msg =>
- <Message key={msg.mid} data={msg} visitor={props.visitor} />)
+ <Message key={msg.mid} data={msg} visitor={visitor} />)
}
{
state.msgs.length >= 20 && (
<p className="page">
- <Link to={{ pathname: props.location.pathname, search: state.nextpage }} rel="prev">Next →</Link>
+ <Link to={{ pathname: location.pathname, search: state.nextpage }} rel="prev">Next →</Link>
</p>
)
}
</div>
- ) : state.error ? <div>error</div> : state.loading ? <div className="msgs"><Spinner /><Spinner /><Spinner /><Spinner /></div> : <div>No more messages</div>
+ ) : state.error ? <div>error</div> : loading ? <div className="msgs"><Spinner /><Spinner /><Spinner /><Spinner /></div> : <div>No more messages</div>
);
}
diff --git a/vnext/src/ui/Header.js b/vnext/src/ui/Header.js
index a7663dd3..d8fe23e0 100644
--- a/vnext/src/ui/Header.js
+++ b/vnext/src/ui/Header.js
@@ -1,11 +1,21 @@
-import React, { memo } from 'react';
-import { Link, withRouter } from 'react-router-dom';
+import React, { memo, useCallback } from 'react';
+import { Link, useHistory } from 'react-router-dom';
import Icon from './Icon';
import { UserLink } from './UserInfo';
import SearchBox from './SearchBox';
-function Header({ visitor, search, className }) {
+function Header({ visitor, className }) {
+ const history = useHistory();
+ /**
+ * @param {string} searchString
+ */
+ let searchAll = useCallback((searchString) => {
+ let location = {};
+ location.pathname = '/discover';
+ location.search = `?search=${searchString}`;
+ history.push(location);
+ }, [history]);
return (
<div id="header" className={className}>
<div id="header_wrapper">
@@ -27,7 +37,7 @@ function Header({ visitor, search, className }) {
visitor.uid >= 0 &&
<>
<div id="search" className="desktop">
- <SearchBox pathname="/discover" onSearch={search} />
+ <SearchBox onSearch={searchAll} />
</div>
<nav id="global">
{visitor.uid > 0 ?
@@ -68,4 +78,4 @@ function Header({ visitor, search, className }) {
);
}
-export default memo(withRouter(Header));
+export default memo(Header);
diff --git a/vnext/src/ui/Login.js b/vnext/src/ui/Login.js
index 883a4eab..0c6f5d0c 100644
--- a/vnext/src/ui/Login.js
+++ b/vnext/src/ui/Login.js
@@ -1,6 +1,5 @@
import React, { useEffect } from 'react';
-
-import { withRouter } from 'react-router-dom';
+import { useLocation, useHistory } from 'react-router-dom';
import Icon from './Icon';
import Button from './Button';
@@ -14,8 +13,6 @@ import './Login.css';
/**
* @typedef {Object} LoginProps
* @property {import('../api').SecureUser} visitor
- * @property {import('history').History} history
- * @property {import('history').Location} location
* @property {any} onAuth
*/
@@ -23,7 +20,10 @@ import './Login.css';
* Login page
* @param {LoginProps} props
*/
-function Login({ visitor, history, location, onAuth }) {
+function Login({ visitor, onAuth }) {
+
+ const location = useLocation();
+ const history = useHistory();
useEffect(() => {
if (visitor.hash) {
@@ -76,7 +76,7 @@ function Login({ visitor, history, location, onAuth }) {
);
}
-export default withRouter(Login);
+export default Login;
const socialButtonsStyle = {
display: 'flex',
diff --git a/vnext/src/ui/Post.js b/vnext/src/ui/Post.js
index 35704f11..407f3b62 100644
--- a/vnext/src/ui/Post.js
+++ b/vnext/src/ui/Post.js
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
+import { useLocation, useHistory } from 'react-router-dom';
import qs from 'qs';
@@ -8,9 +9,11 @@ import MessageInput from './MessageInput';
import { post, update } from '../api';
/**
- * @param {{location: import('history').Location, visitor: import('../api').User, history: import('history').History}} props
+ * @param {{visitor: import('../api').User}} props
*/
-export default function Post({ location, visitor, history }) {
+export default function Post({ visitor }) {
+ const location = useLocation();
+ const history = useHistory();
let draftMessage = (location.state || {}).draft || {};
let [draft, setDraft] = useState(draftMessage.body);
let params = qs.parse(window.location.search.substring(1));
diff --git a/vnext/src/ui/SearchBox.js b/vnext/src/ui/SearchBox.js
index aab49757..dda97989 100644
--- a/vnext/src/ui/SearchBox.js
+++ b/vnext/src/ui/SearchBox.js
@@ -1,27 +1,25 @@
import React from 'react';
-import { withRouter } from 'react-router-dom';
import { useFormState } from 'react-use-form-state';
/**
* @typedef {Object} SearchBoxPropsFields
- * @property {string} pathname
* @property {function} onSearch
*/
/**
- * @typedef {import('react-router-dom').RouteComponentProps & SearchBoxPropsFields} SearchBoxProps
+ * @typedef {SearchBoxPropsFields} SearchBoxProps
*/
/**
* @param {SearchBoxProps} props
*/
-function SearchBox({ onSearch, history, pathname }) {
+function SearchBox({ onSearch }) {
/**
* @type {(React.FormEvent<HTMLFormElement>)}
*/
let onSubmit = (event) => {
event.preventDefault();
- onSearch(history, pathname, formState.values.search);
+ onSearch(formState.values.search);
};
const [formState, { text }] = useFormState();
return (
@@ -32,4 +30,4 @@ function SearchBox({ onSearch, history, pathname }) {
);
}
-export default withRouter(SearchBox);
+export default SearchBox;
diff --git a/vnext/src/ui/Thread.js b/vnext/src/ui/Thread.js
index 63bb2dd6..4f53c4af 100644
--- a/vnext/src/ui/Thread.js
+++ b/vnext/src/ui/Thread.js
@@ -1,4 +1,5 @@
import React, { useEffect, useState, useRef, useCallback } from 'react';
+import { useLocation, useParams } from 'react-router-dom';
import Message from './Message';
import MessageInput from './MessageInput';
@@ -11,7 +12,7 @@ import { format, embedUrls } from '../utils/embed';
import { getMessages, comment, update, markReadTracker, fetchUserUri, updateAvatar } from '../api';
-import { bubbleStyle, chatItemStyle } from './helpers/BubbleStyle';
+import { chatItemStyle } from './helpers/BubbleStyle';
import './Thread.css';
@@ -118,21 +119,21 @@ function Comment({ msg, draft, visitor, active, setActive, onStartEditing, postC
/**
* @param {{
- match: import('react-router').match,
- location: import('history').Location,
visitor: import('../api').SecureUser
connection: EventSource
}} props
*/
export default function Thread(props) {
- const [message, setMessage] = useState((props.location.state || {}).msg || {});
+ const location = useLocation();
+ const params = useParams();
+ const [message, setMessage] = useState((location.state || {}).msg || {});
const [replies, setReplies] = useState([]);
const [loading, setLoading] = useState(false);
const [active, setActive] = useState(0);
const [editing, setEditing] = useState(emptyMessage);
const [hash, setHash] = useState(props.visitor.hash);
- const { mid } = props.match.params;
+ const { mid } = params;
let loadReplies = useCallback(() => {
document.body.scrollTop = 0;
@@ -217,7 +218,10 @@ export default function Thread(props) {
</li>
)) : (
<>
- {Array(loaders).fill().map((it, i) => <Spinner key={i} />)}
+ {
+ // @ts-ignore
+ Array(loaders).fill().map((it, i) => <Spinner key={i} />)
+ }
</>
)
}
diff --git a/vnext/src/ui/UserInfo.js b/vnext/src/ui/UserInfo.js
index fe9cee19..00465252 100644
--- a/vnext/src/ui/UserInfo.js
+++ b/vnext/src/ui/UserInfo.js
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
-import { info, fetchUserUri } from '../api';
+import { info, fetchUserUri, update } from '../api';
import Avatar from './Avatar';
import Icon from './Icon';
@@ -13,29 +13,25 @@ let isMounted;
/**
* User info component
- * @param {{user: string, onUpdate?: function, children?: Element}} props
+ * @param {{uname: string, onUpdate?: function, children?: React.ReactHTMLElement}} props
*/
-export default function UserInfo(props) {
+export default function UserInfo({ uname, onUpdate, children }) {
const [user, setUser] = useState({
- uname: props.user,
+ uname: uname,
uid: 0
});
- const { onUpdate } = props;
- const userRef = useRef(user);
useEffect(() => {
isMounted = true;
- if (!userRef.current.avatar) {
- info(userRef.current.uname).then(response => {
- if (isMounted) {
- onUpdate && onUpdate(response.data);
- setUser(response.data);
- }
- });
- }
+ info(uname).then(response => {
+ if (isMounted) {
+ onUpdate && onUpdate(response.data);
+ setUser(response.data);
+ }
+ });
return () => {
isMounted = false;
};
- }, [onUpdate, props.user]);
+ }, [onUpdate, uname]);
return (
<>
<div className="userinfo">
@@ -63,7 +59,7 @@ export default function UserInfo(props) {
</>
}
</div>
- {props.children}
+ {children}
</>
);
}
diff --git a/vnext/src/ui/Users.js b/vnext/src/ui/Users.js
index 4c09318f..4dd929fb 100644
--- a/vnext/src/ui/Users.js
+++ b/vnext/src/ui/Users.js
@@ -1,36 +1,37 @@
import React, { useState } from 'react';
+import { useParams } from 'react-router-dom';
import UserInfo from './UserInfo';
import Avatar from './Avatar';
/**
* Friends feed
- * @param {{match: import('react-router').match }} match
*/
-export function Friends({ match }) {
- return <Users user={match.params.user} prop='read' />;
+export function Friends() {
+ const params = useParams();
+ return <Users uname={params.user} prop='read' />;
}
/**
* Readers feed
- * @param {{match: import('react-router').match }} match
*/
-export function Readers({ match }) {
- return <Users user={match.params.user} prop='readers' />;
+export function Readers() {
+ const params = useParams();
+ return <Users uname={params.user} prop='readers' />;
}
/**
* UserInfo list component
- * @param {{user: import('../api').User, prop: string}} props
+ * @param {{uname: string, prop: string}} props
*/
-function Users(props) {
- const [user, setUser] = useState({ uid: 0, uname: props.user });
+function Users({ uname, prop }) {
+ const [user, setUser] = useState({ uid: 0, uname: uname });
return (
- <UserInfo user={user.uname} onUpdate={setUser}>
+ <UserInfo uname={uname} onUpdate={setUser}>
<div style={{ display: 'flex', flexWrap: 'wrap', flexDirection: 'row' }}>
{
- user[props.prop] &&
- user[props.prop].map(user =>
+ user[prop] &&
+ user[prop].map(user =>
<Avatar key={user.uid} user={user} />
)
}