From 841ec978bae3297357c3157a3adf846648771770 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Thu, 14 Nov 2019 13:44:52 +0300 Subject: react-router-dom hooks --- vnext/src/App.js | 80 ++++++++++++++++++--------------- vnext/src/ui/Chat.js | 15 ++++--- vnext/src/ui/Feeds.js | 111 ++++++++++++++++++++++++---------------------- vnext/src/ui/Header.js | 20 ++++++--- vnext/src/ui/Login.js | 12 ++--- vnext/src/ui/Post.js | 7 ++- vnext/src/ui/SearchBox.js | 10 ++--- vnext/src/ui/Thread.js | 16 ++++--- vnext/src/ui/UserInfo.js | 28 +++++------- vnext/src/ui/Users.js | 25 ++++++----- 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} */ @@ -134,17 +133,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 */ @@ -163,23 +151,45 @@ export default function App() {
- } /> - } /> - - - } /> - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{ 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 (
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 (); + return (); } /** * @param {PageProps} props */ -export function Discussions(props) { +export function Discussions({ visitor }) { const query = { baseUrl: '/api/messages/discussions', pageParam: 'to' }; - return (); + return (); } /** * @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 ( <>
- +
- + ); } @@ -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 (); + return (); } /** * @param {PageProps} props */ -export function Home(props) { +export function Home({ visitor }) { const query = { baseUrl: '/api/home', pageParam: 'before_mid' }; - return (); + return (); } /** * @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 ? (
{ @@ -180,16 +183,16 @@ function Feed(props) { } { state.msgs.map(msg => - ) + ) } { state.msgs.length >= 20 && (

- Next → + Next →

) }
- ) : state.error ?
error
: state.loading ?
:
No more messages
+ ) : state.error ?
error
: loading ?
:
No more messages
); } 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 (