Destructuring this.props into Component - reactjs

In this sample code below: It is React and Next.js
import App, { Container} from "next/app";
import React from "react";
class MyApp extends App{
render() {
const { Component } = this.props;
return (
<Container>
<p>Hey I am on every page</p>
<Component/>
</Container>
)
}
}
export default MyApp;
My question is about this line:
const { Component } = this.props;
My questions:
How is this working? This is the highest page level I have. So who is passing this.props to it?
Also what kind of syntax is that? Why it called Components? Could he call it something else?

Your MyApp component extends a component named App, which is an internal Next react component. The reason you'd want to extend App is if you're calling getInitialProps before each page is loaded. You can think of App as a HOC (higher order component) that calls getInitialProps in MyApp first, then other Next methods within a page, like: getInitialProps, getServerSideProps, getStaticProps, getStaticPaths second.
This creates a render-blocking scenario where MyApp.getInitialProps can block page-level methods from being executed until it has been resolved.
In your case, you don't even need to extend App since you're not calling getInitialProps, and instead, you can just use a pure function (or remove _app.js completely if you don't need to include metadata or wrap each page with some sort of layout component):
function MyApp({ Component, pageProps }) {
return (
<Component {...pageProps} />
);
}
export default MyApp;
All subsequent pages (like /pages/example.js) will be passed to this MyApp component as the named Component prop while any props returned from one the methods mentioned above will be passed to MyApp as the named pageProps prop.
You can kind of think of App (next/app) like this:
import React, { Component } from "react";
import MyApp from "../_app.js"; // custom _app page
import Example from "../example.js"; // a page component
class App extends Component {
static async getInitialProps(ctx) {
const { pageProps } = await MyApp.getInitialProps(ctx);
return { pageProps }; // this.props.pageProps
};
render() {
return <MyApp Component={Example} pageProps={this.props.pageProps} />
}
}
--
On a side note, the Container component has been deprecated since v9.0.4

The Component prop is the active page, so whenever you navigate between routes, Component will change to the new page. Therefore, any props you send to Component will be received by the page.

Related

How to render a component with props derivative from NextJS router

I'm trying to render a component that uses a dynamic router path prop. I want mysite.com/something to load the component with the something prop. If the route is mysite.com/somethingelse, I want to load the component with the somethingelse prop. Here's my code:
page.js:
import { useRouter } from "next/router";
import List from "./List";
function DefaultPage() {
const router = useRouter();
console.log(router.query.category); // Works correctly
return (
<div>
<List category={router.query.category} />
</div>
);
}
export default DefaultPage;
The component, list.js:
import React, { Component } from "react";
class List extends Component {
constructor(props) {
super(props);
console.log(this.props.category); // This is where I'm confused
}
static defaultProps = { category: "default" };
render() {
return <p>Hello</p>;
}
}
export default List;
The problem is, this.props.category always returns as default (my default prop), unless I recompile. It works perfectly after a fresh compile, but then breaks after every subsequent refresh in the browser.
I can visually see the router query returning the correct value in the log, but the component is rendering before everything else, thus returning a default value. Is there a way I can stop the List component from rendering before its own props are specified? Or is there a better way of doing this all together? Thanks.
I would do something like this in the DefaultPage component:
if(router.query.category === 'something') {
return <ListComponent/>
}
if(router.query.category === 'somethingElse') {
return <SomethingElseComponent/>
}
If you don't want to use two separate components, you could pass the prop to useEffect so it can re-render the component when that prop changes https://reactjs.org/docs/hooks-effect.html

Page does not reload when changing query params through 'next/router' Router.push()

I am using nextjs for my application. On updating query parameters of a page, the page should get re-rendered. For some reason that is not happning. Some online article suggests that we should get the new query props in componentWillReceiveProps, but that too isn't happening. Need help.
/----- modules/my-component.js ------/
import { Component } from 'react'
import Router from 'next/router'
class MyComponent extends Component {
someEvent = () => {
Router.push({
pathname: '/logs',
query: { keyword: 'blah' }
})
}
render(){
return (
<div onClick={this.someEvent}>Click Me</div>
)
}
}
export default MyComponent
/----- pages/logs.js ------/
import { Component } from 'react'
import { withRouter } from 'next/router'
import MyComponent from '~/modules/my-component'
import Router from 'next/router'
class Logs extends Component {
componentWillUpdate(props){
//does not get printed
console.log("================componentWillUpdate", this.props.router.query)
}
componentWillReceiveProps(props){
//does not get printed
console.log("================componentWillReceiveProps", this.props.router.query)
}
render(){
return (
<div>
<div>My Logs page</div>
<MyComponent />
</div>
)
}
}
export default withRouter(Logs)
first of all I see something strange with your someEvent = keyword => { function. Is that the actual code, or an example you created for SO?
Because if you call it like this: <div onClick={this.someEvent}>Click Me</div>, then keyword will actually be an event, not a keyword.
It also seems like you aren't referencing props anywhere in your JSX for the Logs component. You should try to reference it.
Third, what is the call to withRouter(Logs)? That isn't standard. By default in Next.js you should have the component exported by default.
Are you using a custom router? If so, then it's likely not going to work with functions in 'next/router'. Next.js which comes with its own router.
Try to export the component by default and to make sure that keyword is an actual keyword, not an event.

Extend React lifecycle hook (e.g add a print statement on every ComponentDidMount)

I want to add some behaviour on a given lifecycle hook of a React application.
For example, adding a console.log('Component is mounted') on every ComponentDidMount of all the components of an application, without having to define it in every one of them (as a decorator for example), sort of like a global extender of that method that adds some code to it. Like that: Extending Vue Lifecycle Hooks but for React.
Anyone has an idea on how to achieve that? Cheers!
You can use hoc. In the root app, apply the higher order component.
Example:
const withMountHOC = WrappedComponent => {
return class extends Component {
componentDidMount() {
console.log('mounted');
}
render() {
return <WrappedComponent {...this.props} />
}
}
}
export default withMountHOC;
In your app component:
const WrappedApp = withMountHOC(App);
ReactDOM.render(
WrappedApp,
document.getElementById('root')
);
Since the parent componentDidMount hook is called after child componentDidMount hook, the HOC componentDidMount will be applied in any nested level of the component.
You may also be interested to see this blog: Replacing Mixins in React.
create CustomComponent.js
import React, { Component } from 'react'
class CustomComponent extends Component {
constructor(props){
super();
}
componentDidMount(){
console.log('component is mounted');
}
render () {
return (
<div>
{this.props.children}
</div>
)
}
}
export default CustomComponent
Now create MyComponent.js that extends CustomComponent.js
import React, { Component } from 'react'
import CustomComponent from './CustomComponent'
class MyComponent extends CustomComponent {
render () {
return (
<div>
Hello from MyComponent
</div>
)
}
}
export default MyComponent;
now you see console , you have log : "component is mounted"
but if you write componentDidMonunt() inside MyComponent.js , you will get log from MyComponent.js

React.js - how to pass event handlers to deeply nested component without props drilling?

I have the structure of components (nested) that seems like this:
Container
ComponentA
ComponentB
ComponentC(want to handle event here with state that lives on container)
Do I need to pass as props all the way from Container, ComponentA, ComponentB and finally ComponentC to have this handler? Or is there another way like using Context API?
I'm finding a bit hard to handle events with react.js vs vue.js/angular.js because of this.
I would recommend using either Context API (as you mentioned) or Higher Order Components (HoC)
Context Api is your data center. You put all the data and click events that your application needs here and then with "Consumer" method you fetch them in any component regardless of how nested it is. Here is a basic example:
context.js //in your src folder.
import React, { Component, createContext } from "react";
import { storeProducts } from "./data"; //imported the data from data.js
const ProductContext = createContext(); //created context object
class ProductProvider extends Component {
state = {
products: storeProducts,
};
render() {
return (
<ProductContext.Provider
//we pass the data via value prop. anything here is accessible
value={{
...this.state,
addToCart: this.addToCart //I wont use this in the example because it would
be very long code, I wanna show you that, we pass data and event handlers here!
}}
>
// allows all the components access the data provided here
{this.props.children},
</ProductContext.Provider>
);
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
Now we set up our data center with .Consumer and .Provider methods so we can access
here via "ProductConsumer" in our components. Let's say you want to display all your products in your home page.
ProductList.js
import React, { Component } from "react";
import Product from "./Product";
import { ProductConsumer } from "../context";
class ProductList extends Component {
render() {
return (
<React.Fragment>
<div className="container">
<div className="row">
<ProductConsumer>
//we fetch data here, pass the value as an argument of the function
{value => {
return value.products.map(product => {
return <Product key={product.id} />;
});
}}
</ProductConsumer>
</div>
</div>
</React.Fragment>
);
}
}
export default ProductList;
This is the logic behind the Context Api. It sounds scary but if you know the logic it is very simple. Instead of creating your data and events handlers inside of each component and prop drilling which is a big headache, just put data and your event handlers here and orchestrate them.
I hope it helps.

How to link 'from outside' into Navigator (for example from a global footer)

With React Navigation, is there a way to link from outside to a specific path/screen inside a Navigator?
For example to implement a global footer, like this:
<Provider store={store}>
<View>
<AppNavigator />
<MyFooter /> // Link from here to a path/screen inside AppNavigator
</View>
</Provider>
I think refs might work here. If you want to use Navigator from the same level you declare it you can use react's refs and pass props to MyFooter. Look at example in official documentation.
const AppNavigator = StackNavigator(SomeAppRouteConfigs);
class App extends React.Component {
someFunction = () => {
// call navigate for AppNavigator here:
this.navigator && this.navigator.dispatch({ type: 'Navigate', routeName, params });
}
render() {
return (
<View>
<AppNavigator ref={nav => { this.navigator = nav; }} />
<MyFooter someFunction={this.someFunction} />
</View>
);
}
}
Go to this link:
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
React Navigation Version: 5.x
Sometimes you need to trigger a navigation action from places where you do not have access to the navigation prop, such as a Redux middleware. For such cases, you can dispatch navigation actions from the navigation container.
If you're looking for a way to navigate from inside a component without needing to pass the navigation prop down, see useNavigation.
You can get access to the root navigation object through a ref and pass it to the RootNavigation which we will later use to navigate.
// App.js
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
In the next step, we define RootNavigation, which is a simple module with functions that dispatch user-defined navigation actions.
// RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
// add other navigation functions that you need and export them
Then, in any of your javascript modules, just import the RootNavigation and call functions which you exported from it. You may use this approach outside of your React components and, in fact, it works just as well when used from within them.
// any js module
import * as RootNavigation from './path/to/RootNavigation.js';
// ...
RootNavigation.navigate('ChatScreen', { userName: 'Lucy' });
Apart from navigate, you can add other navigation actions:
import { StackActions } from '#react-navigation/native';
export function push(...args) {
navigationRef.current?.dispatch(StackActions.push(...args));
}
Note that a stack navigators needs to be rendered to handle this action. You may want to check the docs for nesting for more details.
When writing tests, you may mock the navigation functions, and make assertions on whether the correct functions are called with the correct parameters.
Handling initialization
When using this pattern, you need to keep few things in mind to avoid crashes in your app.
The ref is set only after the navigation container renders
A navigator needs to be rendered to be able to handle actions
If you try to navigate without rendering a navigator or before the navigator finishes mounting, it will throw and crash your app if not handled. So you'll need to add an additional check to decide what to do until your app mounts.
For an example, consider the following scenario, you have a screen somewhere in the app, and that screen dispatches a redux action on useEffect/componentDidMount. You are listening for this action in your middleware and try to perform navigation when you get it. This will throw an error, because by this time, the parent navigator hasn't finished mounting. Parent's useEffect/componentDidMount is always called after child's useEffect/componentDidMount.
To avoid this, you can set a ref to tell you that your app has finished mounting, and check that ref before performing any navigation. To do this, we can use useEffect in our root component:
// App.js
import { NavigationContainer } from '#react-navigation/native';
import { navigationRef, isMountedRef } from './RootNavigation';
export default function App() {
React.useEffect(() => {
isMountedRef.current = true;
return () => (isMountedRef.current = false);
}, []);
return (
<NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
);
}
Also export this ref from our RootNavigation:
// RootNavigation.js
import * as React from 'react';
export const isMountedRef = React.createRef();
export const navigationRef = React.createRef();
export function navigate(name, params) {
if (isMountedRef.current && navigationRef.current) {
// Perform navigation if the app has mounted
navigationRef.current.navigate(name, params);
} else {
// You can decide what to do if the app hasn't mounted
// You can ignore this, or add these actions to a queue you can call later
}
}

Resources