Props not being passed when using custom document for styled components - javascript

I'm trying to use styled components with next.js. I've added the babel plugin and added a custom _document.js. That all seems to be working fine.
However, when I try and use isomorphic-unfetch to getInitialProps into the page, the request returns data, but it doesn't make it into Props.
For _document.js I'm using the code from the next.js site here and have also tried a slightly different version from here which I also saw on the next.js docs site at some point.
My test page looks like this (also from the next.js docs):
import styled from 'styled-components';
import fetch from 'isomorphic-unfetch';
export default class MyPage extends React.Component {
static async getInitialProps({ req }) {
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
return { userAgent }
}
render() {
return (
<div>
Hello World {this.props.userAgent}
</div>
)
}
}
I also have an _app.js that looks like this:
import App, { Container } from 'next/app';
import Page from '../components/Page';
class MyApp extends App {
render() {
const { Component } = this.props;
return (
<Container>
<Page>
<Component />
</Page>
</Container>
);
}
}
export default MyApp;
And Page.js just has some styled components and a theme with a component that looks like this:
class Page extends Component {
render() {
return (
<ThemeProvider theme={theme}>
<StyledPage>
<GlobalStyle />
<Meta />
<Header />
{this.props.children}
</StyledPage>
</ThemeProvider>
);
}
}
export default Page;
I feel like it's something to do with the new _document.js or _app.js not passing the props down somehow.
Clearly I'm pretty new to next.js, so apologies if it's something stupid. In the meantime, I'll keep plugging away to get a better understanding of what's going on. I wanted to ask in parallel here since I'm under some time pressure for this project.
Many thanks for any thoughts you might have.

Not long after posting, I worked out the issue. I guess posting on SO helps to understand the issue.
Essentially it was _app.js which was the problem, not _document.js as I had initially expected.
I needed to add pageProps to _app.js so that they were propagated down to the page:
import App, { Container } from 'next/app';
import Page from '../components/Page';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Page>
<Component {...pageProps} />
</Page>
</Container>
);
}
}
export default MyApp;

Related

Adding a persistent component to _app.js in Nextjs

So I am playing around with my first Nextjs app and am having trouble with adding a persistent sidebar.
I found Adam Wathan's article on persistent layouts in nextjs, but it seems like there is a newer pattern that was added recently using the _app.js page. I went to the docs and a few of the github issues around it, but it doesn't looks like there's a lot of documentation around it yet.
So for my example, I have my _app.js file:
import '../css/tailwind.css'
import Head from 'next/head'
import Sidebar from "../components/Sidebar";
export default function App({Component, pageProps}){
return(
<>
<Head />
<Component {...pageProps} />
</>
)
}
import React, { useState } from "react";
import Transition from "../components/Transition";
import Link from 'next/link'
function Sidebar({ children }) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [hideSidebarMenu, setHideSidebarMenu] = useState(true);
const openSidebar = () => {
setIsSidebarOpen(true);
setHideSidebarMenu(false);
};
const closeSidebar = () => {
setIsSidebarOpen(false);
};
const hideSidebar = () => {
setHideSidebarMenu(true);
};
return(
<div>
/*sidebar links here*/
</div>
)
}
export default Sidebar;
How do I integrate my sidebar component into this? I've tried adding it next to component and wrapping component and a few other iterations with no luck. Hopefully I'm just missing something simple.
This is odd. I could have swore I tried this very simple solution before, but something like this was completely sufficient.
This solution will feed in the page that you are on using the children prop in the sidebar.
import '../css/tailwind.css'
import Head from 'next/head'
import Sidebar from "../components/Sidebar";
export default function App({Component, pageProps}){
return(
<>
<Head />
<Sidebar >
<Component {...pageProps} />
</Sidebar>
</>
)
}
this option will just render the sidebar along with the content
import '../css/tailwind.css'
import Head from 'next/head'
import Sidebar from "../components/Sidebar";
export default function App({Component, pageProps}){
return(
<>
<Head />
<Sidebar />
<Component {...pageProps} />
</>
)
}

How to get the react navigator state outside the root react navigator?

This is the thing:
I have a react-native application on mobile, and I'm trying to do some authority check action when the user has left my app and get back. I want to avoid doing an authority check on the login screen and considering the existing application component tree, I want to get the current route for me to use. Now, I'm using 5.x version react-navigation.
const App = () => {
useEffect(() => {
const handleAppStateChange = (): void => {
// Authority check action
};
AppState.addEventListener('change', handleAppStateChange);
}, []);
return (
<Provider store={store}>
<SafeAreaProvider>
<SafeAreaView>
<RootNavigator />
<OverlayActivityIndicator />
<ActionSheet />
</SafeAreaView>
</SafeAreaProvider>
</Provider>
);
}
Now I meet the problem:
I don't know how to do to get the current route outside the root navigator.
My last choice is integrating the current route into the redux store, and that will be making my application more complex.
Any other ideas?
You can refer this for getting navigation outside components
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
//whereas
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}

React + Nextjs - External scripts loading but not executing together

I have a next js application and I need to integrate it with an existing site by importing the header and footer from the parent site. It the markup along with supporting libs are being delivered through a JS file, one for each header and footer respectively. This is how my _apps.js, navigation.js and footer.js file looks like:
_app.js:
render() {
return (
<Provider store={reduxStore}>
<Head headerData={newStaticData} />
<Navigation />
<OtherComponents />
<Footer />
</Provider>
);
}
navigation.js:
class Navigation extends Component {
shouldComponentUpdate() {
return false;
}
componentDidMount() {
const script = document.createElement('script');
script.src = "https://mainsite.com/external/header.js";
script.async = true
document.body.appendChild(script);
}
render() {
return (
<div id="target_div_id"></div>
)
}
}
export default Navigation;
footer.js:
class Footer extends Component {
shouldComponentUpdate() {
return false;
}
componentDidMount() {
const script = document.createElement('script');
script.src = "https://mainsite.com/external/footer.js";
script.async = true
document.body.appendChild(script);
}
render() {
return (
<div id="footer_target_id"></div>
)
}
}
export default Footer;
When I run this code, just the main navigation will appear but not the footer. If it comment out the navigation, then the footer will appear. I am not sure why but it looks like only one loads at a time. I have tried using script.defer=true but hasn't helped either.
Can anyone advice what might be causing this and what's the resolution?
TIA.
you can easily do this with react-helmet even in child component
import React from "react";
import {Helmet} from "react-helmet";
class Navigation extends Component {
render() {
return (
<div id="target_div_id">
<Helmet>
<script type="text/javascript" href="https://mainsite.com/external/header.js" />
</Helmet></div>
)
}
}
export default Navigation;
try you use react hooks instead of react Component lifecycle
const Navigation = ()=> {
return (
<div id="target_div_id">
<Helmet>
<script type="text/javascript" href="https://mainsite.com/external/header.js" />
</Helmet></div>
)
}
export {Navigation} ;
// parent
import {Navigation} from "../Navigation.js";
I would suggest you not to use _app.js for this.
Try creating a Layout file like below:
// MainLayout.js
import NavBar from './Navigation.js'
import Footer from './Footer.js'
export default ({ children }) => (
<>
<Navigation />
{children}
<Footer />
</>
)
And have your main file as like this:
// index.js
import React from 'react'
import MainLayout from '../components/Layouts/MainLayout.js'
import Banner from '../components/Banner/Banner.js'
export default function Home() {
return (
<>
<MainLayout>
<Banner />
</MainLayout>
</>
)
}

React Router & Global Context

I'm building an e-commerce website with React (my first ever React project) and I'm using React router to manage my pages.
I've got the following component tree structure:
<Router>
<BrowserRouter>
<Router>
<withRouter(Base)>
<Route>
<Base>
<BaseProvider>
<Context.Provider>
<Header>
<PageContent>
The standard React Router structure basically, and withRouter I've got the following:
Base.js
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { Header } from './Header';
import { Footer } from './Footer';
import Provider from '../../BaseProvider';
class Base extends Component {
render() {
return (
<Provider>
<Header/>
<div className="container">{this.props.children}</div>
<Footer />
</Provider>
);
}
}
BaseProvider.js
import React, { Component, createContext } from 'react';
const Context = createContext();
const { Provider, Consumer } = Context;
class BaseProvider extends Component {
state = {
cart: [],
basketTotal: 0,
priceTotal: 0,
};
addProductToCart = product => {
const cart = { ...this.state.cart };
cart[product.id] = product;
this.setState({ cart, basketTotal: Object.keys(cart).length });
};
render() {
return (
<Provider
value={{ state: this.state, addProductToCart: this.addProductToCart }}
>
{this.props.children}
</Provider>
);
}
}
export { Consumer };
export default BaseProvider;
This gives me a template essentially, so I just the children pages without having to include Header and Footer each time.
If I want to use my global context I'm having to import it each time, and it seems like I've done something wrong as surely I should be able to use this on any page since it's exported in BaseProvider?
If I was to visit the About page, I'd get the same component structure, but no access to the consumer without using:
import { Consumer } from '../../BaseProvider';
Why do I have to do this for each file even though it's exported and at the top level of my BaseProvider? It just seems such a bad pattern that I'd have to import it into about 20 files...
Without importing it, I just get:
Line 67: 'Consumer' is not defined no-undef
I tried just adding the contextType to base but I get: Warning: withRouter(Base): Function components do not support contextType.
Base.contextType = Consumer;
I feel like I've just implemented this wrong as surely this pattern should work a lot better.
I'd recommend using a Higher Order Component - a component that wraps other components with additional state or functionality.
const CartConsumer = Component => {
return class extends React.Component {
render() {
return (
<MyContext.Consumer>
<Component />
</MyContext.Consumer>
)
}
}
}
Then in any component where you'd like to use it, simply wrap in the export statement:
export default CartConsumer(ComponentWithContext)
This does not avoid importing completely, but it's far more minimal than using the consumer directly.

How can I access props passed to React.Component

I want to get some props made in the root layer of my react app:
import React from 'react'
import App, { Container } from 'next/app'
export default class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
state = {
language: "pl"
};
render () {
const { Component, pageProps } = this.props
return (
<Container>
<Component lang={this.state.language} />
</Container>
)
}
}
so every new React.Component created should inherit those props. But I'm not sure how I can get them. Let's say I have another component which is <Nav/>.
Shouldn't I be able to get it via props.lang inside Nav.
When I try it says lang undefined.
I would suggest moving language to the React Context API
So this way you create a context
// context.js
import React from 'react';
export const LangContext = React.createContext('pl');
and provide it inside _app.js
// app.js
import React from 'react';
import App, { Container } from 'next/app';
import { LangContext } from '../context';
export default class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
state = {
language: 'EN'
};
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<LangContext.Provider value={this.state.language}>
<Component {...pageProps} />
</LangContext.Provider>
</Container>
);
}
}
and whenever you need to access language value you dont need to pass it anymore. It will be available on LangContext. Example usage
// Nav.js
import Link from 'next/link';
import { LangContext } from '../context';
function Nav() {
return (
<LangContext.Consumer>
{lang => {
return (
<div className="site-nav">
<Link href="/">
<a>index</a>
</Link>
<Link href="/about">
<a>about</a>
</Link>
language = {lang}
</div>
);
}}
</LangContext.Consumer>
);
}
export default Nav;
This helps to solve the issue of passing lang props to pages and then to some specific components like Nav. Just wrap a component into a <LangContext.Consumer> if you need it.
Example index.js page
// index.js
import Nav from '../components/Nav';
export default () => (
<div>
<Nav />
<hr />
Welcome to index.js!
</div>
);
** One note: as far as I see you can only use <SomeContext.Provider> inside _app.js
I'm seeing a couple problems in your code example.
First, props are a property on your component, they should be accessed via this.props.
Here is a basic example of passing props to a child component:
import React, { Component } from 'react';
class App extends Component {
render() {
const greeting = 'Welcome to React';
return (
<div>
<Greeting greeting={greeting} />
</div>
);
}
}
class Greeting extends Component {
render() {
return <h1>{this.props.greeting}</h1>;
}
}
export default App;
Using the code sample above, it would seem that your mistake was to use return <h1>{props.greeting}</h1>; instead of return <h1>{this.props.greeting}</h1>;
Second, it would appear that your component setup is a little off. I would expect your component declaration to look something like this:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
In your code sample, there's no constructor function and state doesn't appear to be set as a property of your component.
Inside of the example <Nav/> component, you must specify at least one argument in the component's function if you wish to access this.props. For example:
const Nav = (props) => ( <div> {this.props.lang} </div> )
Hope this helps!
Summary of my comments above:
Did you try props.lang, or, this.props.lang?
Because you need this.props.lang to access the property.
Hrm, just took a quick peek at my own code -- the initial state is set in constructor(props), and is defined like super(); this.state = (somestate);.
Because you need to set the state in the constructor of classes.

Categories