I have found a code that solved my problem in Next JS re rendering when changing pages. But now i need to send props to the children component. I got no idea how i can make it works here, this is my layout.js code. As you can see i can send props to Header component but for children i dont know how, because it is a variable and not a component.
import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{children}
<Footer />
</Fragment>
);
}
Thank you for the help
Have you considered using React's Context API? The idea is that when using the Context API your component's state get's lifted, to be managed at a global scale. If a component needs a prop, instead of passing props down manually (prop drilling) you can simply wrap you component in what's known as a context provider. This will allow that Component to access the global state of your application. This is good because, when your application gets bigger, you may need to pass props down through many components which can clutter and add unneeded confusion.
React provides some great documentation to set your React application up to use the Context API. Highly recommend checking it out!
https://reactjs.org/docs/context.html
Try this
import Header from "../components/header";
import Footer from "../components/footer";
import { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
function recursiveMap(children, fn) {
return React.Children.map(children, child => {
if (!React.isValidElement(child) || typeof child.type == 'string') {
return child;
}
if (child.props.children) {
child = React.cloneElement(child, {
children: recursiveMap(child.props.children, fn)
});
}
return fn(child);
});
}
// Add props to all child elements.
const childrenWithProps = recursiveMap(children, child => {
// Checking isValidElement is the safe way and avoids a TS error too.
if (isValidElement(child)) {
// Pass additional props here
return cloneElement(child, { currentUser: { ...user } })
}
return child;
});
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{childrenWithProps}
<Footer />
</Fragment>
);
}
You can use React's cloneElement to achieve that.
React.cloneElement(children, {
isRegisterPage: pageProps.isRegisterPage,
isLoginPage: pageProps.isLoginPage,
outHome: pageProps.outHome
})
Complete example in your case:
import Header from "../components/header";
import Footer from "../components/footer";
import React, { Fragment } from "react";
export default function Layout({ children, ...pageProps }) {
return (
<Fragment>
<Header
isRegisterPage={pageProps.isRegisterPage}
isLoginPage={pageProps.isLoginPage}
outHome={pageProps.outHome}
/>
{
React.cloneElement(children, {
isRegisterPage: pageProps.isRegisterPage,
isLoginPage: pageProps.isLoginPage,
outHome: pageProps.outHome
})
}
<Footer />
</Fragment>
);
}
From the answer of Lucas Raza, below is an example that uses Context API to apply themes to different components
1.Create a context File
//ThemeContex.js
import { createContext, useState } from "react";
export const ThemeContext = createContext();
export const withThemeContext = Component => {
const WrappedComp = props => {
const [darkColor,lightColor] = ["#3b3b3b", "#ddd"]
const [lightBackgoround,darkBackgoround] = ["#ececec","#1d2a35"]
const darkTheme = {
backgroundColor: darkBackgoround,
color:lightColor,
}
const lightTheme = {
backgroundColor: lightBackgoround,
color:darkColor,
}
const themes = {
darkTheme, lightTheme
}
const [theme, setTheme] = useState(lightTheme)
const children ={
theme,
themes,
setTheme,
}
return(
<StylesContext.Provider value={{...children}} >
<Component {...props} />
</StylesContext.Provider>
)
}
return WrappedComp;
}
In _app.js, import withThemeContext higher component and wrap MyApp with it when exporting it.
import { withThemeContext } from '../components'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default withThemeContext(MyApp)
You can know use theme any where in a component
import { useContext } from 'react'
import {ThemeContext} from '../components'
export default function Home() {
const { theme } =useContext(ThemeContext)
return (
<div id="home" style={theme}>
// Home logic...
</div>
)
}
Related
When I implement a simple React component with Mui's withStyles HOC, I have to use the keyword "default" when exporting the component. Why can't I use the HOC in the return statement within the functional component?
Is there something about Js or ReactJs that I'm missing?
Since I am forced to export this component as default, I lose the possibility to use the named import functionality, without using another import/export layer in between.
Below is the current working code:
// Card.js
import React from "react";
import {
Card,
withStyles
} from "#material-ui/core";
const styles = theme => ({
card: {
margin: theme.spacing(2)
}
});
function CustomCard(props) {
const {classes} = props;
return (
<Card className={classes.card}>
Export me without being the default component.
</Card>
);
}
export default withStyles(styles)(MediaCard);
// Elsewhere.js
import CustomCard from "Card";
...
But i'd rather write something like this:
// Cards.js
import React from "react";
import {
Card,
withStyles
} from "#material-ui/core";
const styles = theme =\> ({
card: {
margin: theme.spacing(2)
},
anotherCard: {
margin: theme.spacing(4)
}
});
export function CustomCard(props) {
const {classes} = props;
return withStyles(styles)(
<Card className={classes.card}>
Jeah. I'm not the default component.
</Card>
);
}
export function AnotherCard(props) {
const {classes} = props;
return withStyles(styles)(
<Card className={classes.anotherCard}>
Jeah. I'm not the default component either.
</Card>
);
}
// Elsewhere.js
import { CustomCard, AnotherCard } from "Cards";
...
You can do it the way you want to but you to change the way you define your components. The technical reason is that all exports except default need to be named, otherwise you can't import them and know what's what. Since withStyles() returns a statement and not a named variable/function you can't export it without a name.
export const AnotherCard = withStyles(styles)((props) => {
const {classes} = props;
return (
<Card className={classes.anotherCard}>
Jeah. I'm not the default component either.
</Card>
);
});
The downside of this is of course now your components aren't hoisted.
I have a site built with React Static that has a Header component that is always present. Depending on if the current page has a hero component or not, the Header should be either light or dark.
The Header is rendered outside of the routes and the useEffect is triggered before the children is rendered. This is probably because of the routing.
This is the current code:
// App.js
import React, { useState, useEffect } from 'react'
import { Root, Routes } from 'react-static'
export default () => {
const [useDarkTheme, setUseDarkTheme] = useState(false);
useEffect(() => {
if (typeof document !== "undefined") {
const heroPresent = document.querySelectorAll(".o-hero").length > 0;
console.log("The hero is present: " + heroPresent);
setUseDarkTheme(!heroPresent);
}
})
return (
<Root>
<React.Suspense fallback={ <em>Loading...</em> }>
<Header useDarkTheme={ useDarkTheme } />
<Routes default />
</React.Suspense>
</Root>
);
}
What will be rendered at <Routes default /> is the static pages configured in React Static's static.config.js.
Below is an example of the Hero component:
// Hero.js
import React from "react";
export default () => {
console.log("This is the Hero rendering. If this exist, the Header should be dark.");
return (
<div className="o-hero">
<p>Hero!</p>
</div>
);
}
When I run the application and look at the logs this is what I get:
The hero is present: false
This is the Hero rendering. If this exist, the Header should be dark.
How could I somehow detect the presence of the Hero from the Header although the Hero is in a router and the Header is not? This feels like quite a common use case, but I could not find any info on the interwebs.
Thanks in advance!
So I ended up using useContext to provide all children with a getter and a setter for the Header's theme (dark or light). The solution is very much inspired from this answer. The solution looks like this:
// App.js
import React, { useState, useContext } from 'react'
import { Root, Routes } from 'react-static'
import { HeaderThemeContext } from "./context";
export default () => {
const { theme } = useContext(HeaderThemeContext);
const [headerTheme, setHeaderTheme] = useState(theme);
return (
<Root>
<React.Suspense fallback={ <em>Loading...</em> }>
<HeaderThemeContext.Provider value={ { theme: headerTheme, setTheme: setHeaderTheme } }>
<Header theme={ headerTheme } />
<Routes default />
</HeaderThemeContext.Provider>
</React.Suspense>
</Root>
);
}
// Hero.js
import React from "react";
import { headerThemes, setHeaderTheme } from "./context";
export default () => {
setHeaderTheme(headerThemes.DARK);
console.log("This is the Hero rendering. If this exist, the Header should be dark.");
return (
<div className="o-hero">
<p>Hero!</p>
</div>
);
}
// context.js
import React, { createContext, useContext } from "react";
export const headerThemes = {
LIGHT: "light",
DARK: "dark",
};
export const HeaderThemeContext = createContext({
theme: headerThemes.LIGHT,
setTheme: () => {}
});
// This is a hook and can only be used in a functional component with access to the HeaderThemeContext.
export const setHeaderTheme = theme => useContext(HeaderThemeContext).setTheme(theme);
This gives global access to set and get the header theme, which might not be optional, but it works for now and I think it's fine. Please let me know if there is a better way of doing this.
Edit: Perhaps this could be referenced as a context subscriber?
I'm not even sure if this is the right concept that I'm trying to achieve. I want to be able to create a component that does the dirty work and just attaches context to the component that can the be consumed..
I've tried to find anything similar with no luck, which, leads me to believe I am not thinking of the right literal context of what it is I'm doing...
I've tried something like this:
import React, { Component } from "react";
export const Context = React.createContext();
export class ContextProvider extends Component {
state = {
scanning: false
};
render() {
return (
<Context.Provider
value={{
state: this.state,
handleClick: () => this.setState({
scanning: !this.state.scanning
})
}}
>
{this.props.children}
</Context.Provider>
);
}
}
And I trying to make it work with this..
import React from "react";
import { Context } from "./Context";
const WithContext = (children) => (props) => {
return (
<Context.Consumer>
{ state => (<children {...props} context={state} />) }
</Context.Consumer>
)
};
and then consuming with...
...
<WithContext>
<MyComponent />
</WithContext>
...
But, it just seems to fail or states that I'm returning a function instead of a react component..
Your WithContext component will not work like that... It needs to be a function that has the same functionality as the render function. like so:
import React from "react";
import { Context } from "./Context";
const WithContext = ({ children, ...props }) => (
<Context.Consumer>{state => React.Children.map(children, (child) => (
React.cloneElement(child, { context: state })
))}</Context.Consumer>
);
note that we traverse every direct child of the withContext children using React.Children.map (docs) and add a context prop to them by making use of React.cloneElement (docs). This keeps the child component's original props and shallow merges them into the second parameter passed to the function.
There are a bunch of little errors in your code for using context... Here is a complete example...
Let's say we have a structure where we have App.js -> Parent.js -> Child.js components... Instead of passing the data via props from App to Parent to Child we want to make use of the context API and avoid the prop drilling and have the Child consume the data directly...
Here is what that will look like:
context.js:
import React from 'react';
const Context = React.createContext();
export class Provider extends React.Component {
state = { name: 'Bob', age: 20 };
handleGrowUp = () => {
this.setState({ age: this.state.age + 1 });
};
render() {
return (
<Context.Provider
value={{
state: {
...this.state,
},
actions: {
growUp: this.handleGrowUp,
},
}}
>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
App.js:
import React from 'react';
import Parent from './Parent';
import { Provider } from './context';
const App = () => (
<Provider>
<Parent />
</Provider>
);
export default App;
Parent.js:
import React from 'react';
import Child from './Child';
const Parent = () => (
<div>
<Child />
</div>
);
export default Parent;
Child.js:
import React from 'react';
import { Consumer } from './context';
const Child = () => (
<div>
<Consumer>
{value => (
<div>
<p>{value.state.name}</p>
<p>{value.state.age}</p>
<button onClick={value.actions.growUp}>Grow Up</button>
</div>
)}
</Consumer>
</div>
);
export default Child;
Here is a working demo: https://codesandbox.io/s/9z06xzlyly
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.
I have been doing some experiment on React 16.3.1 ContextAPI. and I encountered into something that I couldn't fathom. I was hoping I could use your help.
Note: The problem have been solved but, its not the solution I am looking for.
Let start with first experiment on multiple components within same file Index.js.
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class AppProvider extends Component {
state = {
name: 'Superman',
age: 100
};
render() {
const increaseAge = () => {
this.setState({ age: this.state.age + 1 });
};
const decreaseAge = () => {
this.setState({ age: this.state.age - 1 });
};
return (
<Provider
value={{
state: this.state,
increaseAge,
decreaseAge
}}
>
{this.props.children}
</Provider>
);
}
}
class Person extends Component {
render() {
return (
<div className="person">
<Consumer>
{context => (
<div>
<p>I'm {context.state.name}</p>
<p>I'm {context.state.age}</p>
<button onClick={context.increaseAge}>
<span>+</span>
</button>
<button onClick={context.decreaseAge}>
<span>-</span>
</button>
</div>
)}
</Consumer>
</div>
);
}
}
class App extends Component {
render() {
return (
<AppProvider>
<div className="App">
<p>Imma Apps</p>
<Person />
</div>
</AppProvider>
);
}
}
export default App;
As result, this render out perfect without any error. I am able to see name (Superman) and age (100). I am able to increase and decrease age by 1.
As you can see, I have imported {createContext} from react then created {Provider, Consumer}. Wrapped <Provider> with state value and <Consumer>.
Next Experiment, was exact copy each component from index.js and paste them separately into their own files.
AppProvider.js
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class AppProvider extends Component {
state = {
name: 'Superman',
age: 100
};
render() {
const increaseAge = () => {
this.setState({ age: this.state.age + 1 });
};
const decreaseAge = () => {
this.setState({ age: this.state.age - 1 });
};
return (
<Provider
value={{
state: this.state,
increaseAge,
decreaseAge
}}
>
{this.props.children}
</Provider>
);
}
}
export default AppProvider;
Person.js
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class Person extends Component {
render() {
return (
<div className="person">
<Consumer>
{context => (
<div>
<p>I'm {context.state.name}</p>
<p>I'm {context.state.age}</p>
<button onClick={context.increaseAge}>
<span>+</span>
</button>
<button onClick={context.decreaseAge}>
<span>-</span>
</button>
</div>
)}
</Consumer>
</div>
);
}
}
export default Person;
App.js
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
class App extends Component {
render() {
return (
<AppProvider>
<div className="App">
<p>Imma Apps</p>
<Person />
</div>
</AppProvider>
);
}
}
export default App;
As result, I am getting error - TypeError: Cannot read property 'state' of undefined.
I am unable to grasp what the exactly error was.. All I did was copy and paste each into files without changing any syntax.
Although, Alternative method was to create a new file and add syntax following...
Context.js
import { createContext } from 'react';
const Context = createContext();
export default Context;
Then go into each files (AppProvider.js. Person.js and App.js) and replace...
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();'
...into...
import Context from './Context.js';. Also replace... <Provider> into <Context.Provider> and <Consumer> into <Context.Consumer>.
And this killed the error. However, this is not the solution I am looking for. I wanted to use <Provider> tag instead of <Context.Provider>.
Question is, Why am I getting this error?
Why am I unable to use this method...
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();'
for each components in separate files so I could use <Provider> tag ?
Are there any way around to get the solution I'm looking for?
Your help is appreciated and Thanks in advance.
Your are getting TypeError: Cannot read property 'state' of undefined.
Beacuse every time you call const { Provider, Consumer } = createContext(); it creates a new object, this object need to be exported in order for consumers to consume this specific object.
So in person.js
when you try doing {context.state.age} it really does not have state on this object, you just created a new Context which is empty or rather with React internal methods and properties.
So in order to consume the same object just export it, like you did in Context.js and instead of doing:
import { createContext } from 'react';
const Context = createContext();
export default Context;
replace to:
import { createContext } from 'react';
const { Provider, Consumer } = createContext();
export { Consumer, Provider };
Then when you want to use it in other files ( meaning import it ) just call:
import { Consumer, Provider } from './Context.js';