I'm trying to make an SPA with routing (ideally with React hooks) in React, but all the examples, descriptions i find are about displaying different components based on the URL. What i want is something like Youtube or Google docs, where the page structure/components are (mostly) the same and only the content changes.
(Edit: adding a bit more context.)
This is going to be a document editor/presenter.
Page structure: after login, there is always a toolbar(blue color) on the top, for menus, notifications, etc. The rest of the screen will be mostly like the two examples below:
Example1:
Example2:

The search pane(orange) could be switched on/off by a button on the toolbar or by a user session variable. The document will be presented in the document section(grey) based on either a user session variable, doc ID provided in URL or selecting a document in the search pane.
Planned URLs
(Added in edit.)
Landing page: /login , login page.
Landing page: / , here the toolbar and a preconfigured, user session based default doc would be presented.
Document page: /doc?id=oys2OPkfOwQ , same as landing page but document section would contain the document with ID provided as query param.
Anything else: /something , toolbar and something under it.
(Added in edit.)
The layout is defined by CSS grid and page structure changes based on a variable. So this is going to be a prop for the App component coming from default value and user session configured variable and could change later.
This is the functionality i imagine for the App component (pseudo code-like thing):
<Router>
<Route path='/login'>
<Login/>
// Components: Toolbar and something under it
</Route>
<Route path='/'>
<DocApp/>
// Components: Toolbar, Document or Toolbar, Search and Document
// Default document loaded for default, not logged in user
// Default document loaded from stored user session
</Route>
<Route path='/doc'>
<DocApp/>
// Components: Toolbar, Document or Toolbar, Search and Document
// Same as for '/' except document with ID set as query param is displayed
// This could be called/triggered from search and document component as well
</Route>
<Route path='/somethingelse'>
<SomethingElse/>
</Route>
</Router>
(Edit: rephrased, original question was how to implement a solution where different documents loaded based on URL query parameter.)
What i'm mostly interested in if there is a simpler way to draw the landing layout '/' and specific doc presenter /doc?id=oys2OPkfOwQ layout? In both cases the same components get displayed, only the provided parameter(doc to present) is different.
(Added in edit.)
By reading the answers and feedback and re-thinking my problem i realized that i have a multiple URLs same content problem.
First of all, edit your routes to render DocumentLoader component under the route /doc
// file: app.js
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
import DocumentLoader from "./DocumentLoader";
const App = (props) => {
return <BrowserRouter>
<Routes>
<Route path="/doc" element={<DocumentLoader />}>
</Routes>
</BrowserRouter>
}
You need two custom hooks, one for loading new document by changing the docId query parameter, and another hook to listen to docId changes to reload new document from your backend.
NOTE: Edit loadDocumentData to load from your backend
// file: hooks.js
import { useState, useEffect, useCallback } from 'react';
import { useSearchParams } from 'react-router-dom';
/**
* TODO:// Refactor this function to call your backend to get
* Document data by docId
*/
const loadDocumentData = (docId) =>
new Promise((resolve, reject) => {
// this setTimeout for demonstration porpuse only
setTimeout(() => {
resolve({ id: docId, name: `Document name for ${docId}` });
}, 3000);
});
export const useDocument = () => {
const [loading, setLoading] = useState(true);
const { docId, loadDocument } = useDocumentParam();
const [document, setDocument] = useState(null);
useEffect(() => {
setLoading(true);
// Load your document data based on docID
loadDocumentData(docId)
.then((doc) => {
setDocument(doc);
setLoading(false);
})
.catch((e) => {
console.error('Failed to load doc', docId);
});
}, [docId, setLoading]);
return { document, loading, loadDocument };
};
export const useDocumentParam = () => {
const [searchParams, setSearchParams] = useSearchParams();
const docId = searchParams.get('d');
const loadDocument = useCallback(
(newDocId) => {
setSearchParams({ d: newDocId });
},
[setSearchParams]
);
return { docId, loadDocument };
};
To listen on query param changes, load document from server-side, display loading indicator and render the "DocPresenter" component.
// file: DocumentLoader.js
import * as React from 'react';
import DocPresenter from './DocPresenter';
import { useDocument } from './hooks';
const DocumentLoader = (props) => {
const { loading, document, loadDocument } = useDocument();
if (loading) {
return <div>Display loading indicator while loading the document</div>;
}
return (
<div className="document-container">
<div className="toolbar">NavBar</div>
<div className="searchbox">search component</div>
<div className="editor">
<DocPresenter document={document} setParentstate={loadDocument} />
</div>
</div>
);
};
export default DocumentLoader;
Helper Links:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With