How can I use shared components between routes in Next.js - reactjs

I'm thinking of migrating from create-react-app to Next.js
I need to share component between routes in Next.js
I tried the Layout file example below and it worked pretty well for me but I have a special case where I need the shared component to be above the router itself.
For example, if I have a video element and want the video to still playing if I changed the route
const Layout = (props) => (
<div>
{props.children}
<video width="400" controls>
<source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4"/>
Your browser does not support HTML5 video.
</video>
</div>
)
const Home = () => (
<Layout>
<Link href="/about"><a>About</a></Link>
<h1 className='title'>Welcome to Home!</h1>
</Layout>
)
const About = () => (
<Layout>
<Link href="/"><a>Home</a></Link>
<h1 className='title'>Welcome to About!</h1>
</Layout>
)
Example from another project using create-react-app and reach-router
the player component is above the router
<Layout>
<Player/>
<Router className={'router'}>
<Login path={Routes.login}/>
<NotFound404 default/>
<Home path={Routes.root}/>
<Test path={Routes.test}/>
</Router>
<Layout/>
The shared element will be the video tag, the problem is on every route change the video is rendered and replayed

You might wanna check the docs on using pages/_app.js for that.
Example usage is shown below:
import Head from "next/head";
type Props = {
pageProps: any;
Component: React.FC;
};
const App = ({ Component, pageProps }: Props) => {
return (
<>
<Head>
// You can put whatever you want to put in your document head
</Head>
<YourSharedComponent />
<Component {...pageProps} />
<style>{`
// styles...
`}</style>
</>
);
};
App.getInitialProps = async ({ Component }) => {
const pageProps = Component.getInitialProps && await Component.getInitialProps(ctx, loggedUser);
return {
pageProps,
}
};
export default App;

Related

rendering a component inside a react page using Menu

I have a menu and the main body of the page. I have created different pages as components with some text. All I want is that when I click on the side bar menu, the components are displayed in the main page. How can I make this work?
const items2 = [{
label: 'Rice',
key: 'rice',
},
{
label: 'AB Test',
key: 'ab',
}]
const MainLayout = () => {
const {
token: { colorBgContainer },
} = theme.useToken();
const navigate = useNavigate();
const onClick = (e)=>{navigate(`/${e.key}`)}
return (
<Layout>
<Sider >
<Menu
mode="inline"
items={items2}
onClick = {onClick}
/>
</Sider>
<Content >
//I Want to open the pages here
</Content>
</Layout>
</Content>
To render a component inside other component, React provides a special props name children.
To achieve your requirement, you can do like this:
MainLayout.js:
export const MainLayout = ({children}) => {
const {
token: { colorBgContainer },
} = theme.useToken();
const navigate = useNavigate();
const onClick = (e)=>{navigate(`/${e.key}`)}
return (
<Layout>
<Sider>
<Menu
mode="inline"
items={items2}
onClick={onClick}
/>
</Sider>
<Content>
{children}
</Content>
</Layout>
)
}
In MainLayout.js, you only need to write {children} inside component <Content>, React will do the rest for you to pass the content of Rice or AB or whatever for you. In each page, just place <MainLayout> at the top of the outer of rest of your code.
Please see 2 example files below.
Rice.js:
import MainLayout from './MainLayout';
export const Rice = () => {
// Do some stuffs here...
return (
<MainLayout>
<div>
<h2>Best rated rice in the World</h2>
<ol>
<li>Pinipig</li>
<li>Riz de Camargue</li>
...
</ol>
<div>
</MainLayout>
)
}
Corn.js:
import MainLayout from './MainLayout';
export const Corn = () => {
// Do some stuffs here...
return (
<MainLayout>
<div>
<h2>Best Corn Side Dish Recipes</h2>
<ol>
<li>Whipped-Cream Corn Salad</li>
<li>Classic Grilled Corn on the Cob</li>
...
</ol>
<div>
</MainLayout>
)
}
You can read more and play around with the example code from React's official documents.
It is the basic concept of React, so before you start to build something big, I suggest to follow this docs first or find some series of React tutorials for beginner, they will explain key concepts of React so you would not save more time.
You need to use react-router-dom to navigate when you click other MenuItem. Create your own RouterProvider and put it in the Content.
<Content>
<RouterProvider router={router}>
</Content>
EDIT
Now you have two way to navigate to your Component. First is using Link and set it to your label.
{
label: <Link title="Rice" to="/rice">Rice</Link>,
key: 'rice',
}
Second way is hook useNavigate
const navigate = useNavigate();
const onClick = (e)=>{navigate(`/${e.key}`)}
//Add to Menu
<Menu
onClick={onClick}
//other props
/>

How to call a state from a component to a page?

I am creating an example dApp which carries the "Header" component at the top of the page for every page. So, I have created a header component and I make people connect to their MetaMask wallet in Header.tsx, which they do successfully and I keep their wallet ID with currentAccount state.
Header.tsx:
const Header: FunctionComponent<{}> = (props) => {
const [currentAccount, setCurrentAccount] = useState("");
async function checkAccount() {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' })
setCurrentAccount(accounts[0]);
}
return (
<header>
<div className="col-xl-3 col-lg-3 col-md-3 col-sm-3">
<ul>
<li>{!connectHidden && <button className="buy connect-wallet" onClick={connectWallet}><b>Connect Wallet</b></button>}</li>
</ul>{currentAccount}
<ul>
<li>{!disconnectHidden && <button className="buy connect-wallet" onClick={disconnectWallet}><b>Disconnect Wallet</b></button>}</li>
</ul>
</div>
</header>
);
};
export default Header;
But at my homepage, there are no codes for anything about getting user's wallet ID, I don't want to rewrite the code here as it is not the right way. As a newbie in react, I couldn't make the codes I have tried work like trying to import a function or variables. How do I call the currentAccount state in my home page?
Home.tsx:
const HomePage: FunctionComponent<{}> = () => {
useEffect(() => {
onInit()
return () => { }
}, [])
async function onInit() {
}
async function onClickMint() {
alert("mint");
}
return (
<>
<div>xx
</div>
</>
);
};
export default HomePage;
Here is my app.tsx and as you can see, I am seeing all of the components at once. But I want to use the state I have got at Header component in my Home component.
App.tsx:
import Header from './components/Header';
const App: FunctionComponent<{}> = () => {
return (
<Router>
<Header />
<Route exact path="/" component={HomePage} />
<Route exact path="/wallet" component={Wallet} />
<Footer />
</Router>
);
};
export default App;
Quick answer:
simply create your state at the top level (App.tsx) and give currentAccount, setCurrentAccount as props for the other components
App.tsx:
import Header from './components/Header';
const App: FunctionComponent<{}> = () => {
const [currentAccount, setCurrentAccount] = useState("");
return (
<Router>
<Header />
<Route exact path="/">
<HomePage currentAccount={currentAccount} setCurrentAccount={setCurrentAccount}/>
</Route>
<Route exact path="/wallet">
<Wallet currentAccount={currentAccount} setCurrentAccount={setCurrentAccount}/>
</Route>
<Footer />
</Router>
);
};
export default App;
Longer answer:
You need to inform yourself about redux or simply the useContext hook
For instance with the useContext hook you can create a context that will contain your state and that you will be able to access in any child component without using props which can be redundant when you have multiple children and grandchildren ...
Here you can find the documentation about how to use the useContext Hook :
https://reactjs.org/docs/hooks-reference.html#usecontext

How do I route between pages in Embedded React App?

Background:
I am trying to create some links in my embedded Shopify app.
I understand that I cannot use the simple <a> tag due to the fact that Shopify embedded apps are rendered as iframes.
I made some headway with this tutorial, but I am stuck: https://theunlikelydeveloper.com/shopify-app-bridge-react-router/
What I am trying to do:
I have 3 pages (index.js, dashboard.js, and support.js). I would like to allow the user to navigate from one page to another (with links and/or buttons).
My Code:
By following the tutorial above, I've gotten this far:
// index.js
import { Page, Frame } from "#shopify/polaris";
const Index = () => {
return (
<Page>
<Frame>
{/* LINK TO DASHBOARD PAGE*/}
{/* LINK TO SUPPORT PAGE */}
</Frame>
</Page>
);
};
export default Index;
// routes.js
import React from "react";
import { Switch, Route, withRouter } from "react-router";
import { ClientRouter, RoutePropagator } from "#shopify/app-bridge-react";
function Routes(props) {
const { history, location } = props;
return (
<>
<ClientRouter history={history} />
<RoutePropagator location={location} />
<Switch>
<Route path="/dashboard">
<Dashboard />
</Route>
<Route path="/support">
<Support />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</>
);
}
export default withRouter(Routes);
// link.js
import React from "react";
import { Link as ReactRouterLink } from "react-router";
const IS_EXTERNAL_LINK_REGEX = /^(?:[a-z][a-z\d+.-]*:|\/\/)/;
function Link({ children, url = "", external, ref, ...rest }) {
if (external || IS_EXTERNAL_LINK_REGEX.test(url)) {
rest.target = "_blank";
rest.rel = "noopener noreferrer";
return (
<a href={url} {...rest}>
{children}
</a>
);
}
return (
<ReactRouterLink to={url} {...rest}>
{children}
</ReactRouterLink>
);
}
export default Link;
Additional:
I believe I'm supposed to implement the following code somewhere, but I don't see how it fits into the picture of navigating between pages with a link or button.
<AppProvider linkComponent={Link}>
{/* App content including your <Route> components */}
</AppProvider>
Link to Shopify Docs: https://polaris.shopify.com/components/structure/app-provider#section-using-linkcomponent
At this time of building embedded app you can make client-side navigation using app-bridge utilities as referred to in this answer
You just need to edit _app file and consider making client-side navigation from your components(can't use a normal Link)
import {useEffect} from 'react';
import Router, { useRouter } from "next/router";
import { RoutePropagator as ShopifyRoutePropagator } from "#shopify/app-bridge-react";
function RoutePropagator () {
const router = useRouter();
const { route } = router;
const app= useAppBridge();
useEffect(() => {
app.subscribe(Redirect.Action.APP, ({ path }) => {
Router.push(path);
});
}, []);
return app && route ? (
<ShopifyRoutePropagator location={route} />
) : null;
}
Then use this component in your _app file
_app.tsx
class MyApp extends App {
render() {
const { Component, pageProps, host } = this.props as any;
return (
<PolarisProvider i18n={translations}>
<ShopifyBridgeProvider
config={{
apiKey: API_KEY,
host,
forceRedirect: true,
}}
>
<RoutePropagator />
<ApolloClientProvider Component={Component} {...pageProps} />
</ShopifyBridgeProvider>
</PolarisProvider>
);
}
}
Now you've subscribed for routing events in _app file, we just require to make client-side navigation right in your pages
import {useAppBridge} from '#shopify/app-bridge-react';
import { Redirect } from '#shopify/app-bridge/actions';
function IndexPage(props) {
const app = useAppBridge();
return (
<>
<div>{'you are in main page'}</div>
<div onClick={() => {
app.dispatch(Redirect.toApp({
path: '/dashboard'
}));
}}>
{'to dashboard'}
</div>
</>
);
}
And for going back to the main page / route, I've found that it trigger an oauth again if not provided with the shop name, so we will use the shop query params for that
<div onClick={() => {
app.dispatch(Redirect.toApp({
path: '/?shop=<shop-name>.myshopify.com'
}));
}}>
{'to main'}
</div>

How can I change an image dynamically in Ionic React?

I'm pretty new to React and TypeScript, and I ran into this problem:
In my UI I'm using several decorative graphics as follows:
import Kitten from './img/Kitten.png';
<img className="Image" src={Kitten} />
Now, I have a dark-mode toggle. When it fires, I want to replace all images with their appropriate dark-mode version. I was thinking about something like this:
import Kitten from './img/Kitten.png';
import DarkKitten from './img/DarkKitten.png';
//gets called when dark mode is toggled on or off
const darkModeToggleFunc = () => {
document.querySelectorAll('.Image').forEach(element => {
if(element.src.includes("Dark")) {
element.src = element.src.replace("Dark", "");
} else{
element.src = "Dark" + element.src;
}
});
}
<img className="Image" src={Kitten} />
Now, in React I have two problems: the .src-attribute is unknown because element is not necessarily an image and the second problem is: I don't assign URIs as src but the variable from the import. So there isn't really a string I can change... If I'm informed correctly, React uses Base64 for images specified this way.
How could I achieve my goal in React?
Edit: App.tsx
//bunch of imports
const App: React.FC = () => {
return (
<IonApp>
<IonReactRouter>
<IonSplitPane contentId="main">
<Menu />
<IonRouterOutlet id="main">
<Route path="/page/:name" component={Page} exact />
<Redirect from="/" to="/page/Production" exact />
</IonRouterOutlet>
</IonSplitPane>
</IonReactRouter>
</IonApp>
);
};
export default App;
First things first when it comes to react you dont directly go and change things in the document level, you update the virtual DOM and let react take care of the rest.
You scenario is on changing the theme of the app, this answer is on using React context to change theme and use images appropriately.
First you create a Context which will hold the theme value
const AppContext = createContext({
theme: "light",
setTheme: (theme) => {}
});
Here we are going to use a state variable for simplicity, you can use anything you prefer.
Heres the app.js file
export default function App() {
const [theme, setTheme] = React.useState("light");
const themeState = { theme, setTheme };
return (
<AppContext.Provider value={themeState}>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<ImageViewer />
<DarkModeSwitch />
</div>
</AppContext.Provider>
);
}
Here we set the theme value in the state and set the context to that, the setTheme can be used to update the theme from any component that is in the tree. in your case the darkmodeswitch, here we toggle the value
const DarkModeSwitch = () => {
const { theme, setTheme } = useContext(AppContext);
const darkModeToggle = () => {
setTheme(theme === "light" ? "dark" : "light");
};
return (
<div>
<input
type="checkbox"
checked={theme === "light"}
onChange={() => darkModeToggle()}
/>
</div>
);
};
Coming to your main requirement, the images, lets use a common files for images with the contents
export const Kitten ="image source 1";
export const KittenDark ="image source 2";
You simply set the image based on the theme like below
import { Kitten, KittenDark } from "./images";
export default function ImageViewer() {
const { theme } = useContext(AppContext);
return (
<img
alt="kitten"
style={{ height: 50, width: 100 }}
src={theme === "light" ? Kitten : KittenDark}
/>
);
}
as you can see everything is connected via the context and once you update the context you can see the images change.
You can see a working version here
https://codesandbox.io/s/react-theme-switch-3hvbg
This is not 'THE' way, this is one way of handling the requirement, you can use things like redux etc

Adding a Link as a child of a Router with ReactDOM.render yields "You should not use <Link> outside a <Router>"

I am looking for a way to use ReactDOM.render to create a Link within a react router. The setup more or less looks like this:
const router = (
<div>
<Router>
<Route path="/map" component={Map}/>
</Router>
</div>
);
The relevant parts of Map.jsx look like this:
const MapPopup = () => {
return (
<Link to={`/map/add`} />
)
}
class Map extends React.Component {
componentDidMount() {
this.map = L.map('map')
//...stuff...
this.map.on('contextmenu', event => {
popup
.setLatLng(event.latlng)
.addTo(this.map)
.setContent(
ReactDOM.render(
MapPopup(),
document.querySelector('.leaflet-popup-content')
)[0]
)
.openOn(this.map)
})
}
render() {
return (
<React.Fragment>
<div id="map" />
</React.Fragment>
)
}
}
I am basically trying to add a Link to the map popup provided by leaflet (I can't use react-leaflet for this project). If I however return the MapPopup directly in the render function it works (obviously not in the popup but the Link does work this way).
<React.Fragment>
<div id="map" />
<MapPopup />
</React.Fragment>
Does anyone have an idea how I can tackle this rather unusual problem?
I am using "react-router-dom": "4.3.1".
This is the expected error since <Link> component expects ancestor component to be of router type (<BrowserRouter>, <MemoryRouter>, <Router> ... ), refer this thread for a more details.
For your scenario to circumvent this limitation ReactDOM.createPortal could be utilized instead of ReactDOM.render:
<Route
path="/popup"
render={() => (
<Popup>
<div>
Some content goes here
<Link to="/map"> Back to map</Link>
</div>
</Popup>
)}
/>
where
class Popup extends React.Component {
render() {
return ReactDOM.createPortal(
this.props.children,
document.querySelector("#link-render-div")
);
}
}
and
Here is a demo for your reference

Resources