How does <Component {...pageProps} /> function? - reactjs

newbie here,
The code Im learning from this and
I also find almost every _app.js have this line code too.
class Name extends App {
render() {
const { Component, pageProps } = this.props;
const config = { some config here };
return (
<AppProvider config = { config }>
<Component {...pageProps} />
</AppProvider>
);
}
}
I know that <Component {...pageProps} /> part represents all other pages. And when I navigate page it change in pageprops.
It just I don't know how it call other page?

Component is provided as a prop from whichever component is calling Name (lets call it Foo).
As you mentioned that navigation changes props, I am assuming that when page is navigated, this Foo undergoes some change and hence passes a different Component and/or pageProps to Name. Hence the Component instance in the new page gets new props.
If you want to call it in other pages, check how it has been passed from Foo and follow the same method in your component.

Related

React - update or re-render parent component when navigating to another child

I have two components ChildA and ChildB which share a fair amount of UI elements plus the same logic to retrieve data from API before the component is rendered. The common logic and UI elements are externalized in Parent component.
The API call is executed from Parent in useEffect hook.
The common UI elements are rendered based on the data retrieved by the API call.
Problem:
When I navigate from /parent/a to /parent/b, the API call is not executed so the component doesn't update.
I guess the reason is Parent has already been mounted the first time I entered {base_url}/parent/a in the browser, so it doesn't execute useEffect a second time when I try to navigate to /parent/b. However I have no idea how to solve it. Am I going all wrong here?
Steps to reproduce:
Enter URL {base_url}/parent/a in the browser ; the API call is executed, everything works fine
Click a link to navigate to /parent/b ; the component doesn't update.
Requirements:
I would like to avoid using class components as much as possible
I don't use redux and I don't want to use it just to solve this problem
Routing:
<Route path="/parent/a" render={props => <Parent child={ChildA} name="child-a" {...props} /> } exact />
<Route path="/parent/b" render={props => <Parent child={ChildB} name="child-b" {...props} /> } exact />
Child A :
export default function ChildA() {
return (
<div>
<h1>Child A</h1>
{/*Clicking this link will reproduce the issue*/}
<Link to="b">Go to Child B</Link>
</div>
)
}
Child B :
export default function ChildB() {
return (
<div>
<h1>Child B</h1>
{/*Clicking this link will reproduce the issue*/}
<Link to="a">Go to Child A</Link>
</div>
)
}
Parent :
interface ParentProps {
child: any,
name: string
}
export default function Parent(props: ParentProps) {
const Child = props.child
const [model, setModel] = useState<Model | null>(null)
useEffect(() => {
fetchData()
}, [])
function fetchData() {
console.log('EXECUTING API CALL FOR:', props.name)
// ...API call ommitted for brevity...
}
return (
<div>
<h1>Parent</h1>
{/* ...Display lots of UI depending on Model - omitted for brevity... */}
<Child/>
</div>
)
}
Since you're passing an empty dependencies array, the effect only gets run once. You can add props.name to the dependencies array to make sure fetchData gets called when props.name changes.
useEffect(() => {
function fetchData() {
console.log('Executing API call for:', props.name)
// Fetch data
}
fetchData()
}, [props.name])
The fetchData function is added inside the effect to make the effect's dependencies clearly visible. I would recommend reading this for more information. And you can use the exhaustive-deps ESLint rule which is a part of the eslint-plugin-react-hooks package to help you find effects where the dependencies array does not include all the effect's dependencies.

Transform Loading HOC to use Hooks and avoid react navigation problem

I have made a HOC for showing a loading modal when my component is loading.
export const withLoading = (Component) => {
return function HOCLoading(props) {
const [isLoading, setIsLoading] = useState(false)
return (
<>
<Component
{...props}
isLoading={isLoading}
setIsLoading={setIsLoading}
/>
<Loading isLoading={isLoading} />
</>
)
}
}
And I'm using it as
export default withLoading(MyComponent)
It was working fine until I realize that the navigationOptions stopped working, which is obvious because withLoading return a component that don't have navigationOptions, so my workaround was.
const LoadingMyComponent = withLoading(MyComponent)
And then set navigationOptions to LoadingMyComponent.
But this looks bad and doesn't make it easier than having a state for loading and rendering Loading.
Is there a way to transform this HOC into a react hooks or do something that I don't mess with the navigationOptions and also encapsulates the Loading component and logic?
I've had the exact same problem with react-navigation and I'm pretty sure that no really clean solutions exist, as the concept of setting a navigationOptions static property isn't, in the first place, really good (they should have made a hook for that in my opinion).
So either you copy navigationOptions (what you're doing), or, if this is not too problematic with your current architecture, you put everything in a MyScreen component that isn't wrapped by another HOC, like this:
const LoadingMyComponent = withLoading(MyComponent);
function MyScreen() {
// You can exchange data with props or contexts if needed
return <LoadingMyComponent />;
}
MyScreen.navigationOptions = { /* ... */ };
// MyScreen is never wrapped in a HOC and its navigationOptions are accessible

React implicit mapping mechanism

I am going through the tutorial:
https://www.robinwieruch.de/gentle-introduction-higher-order-components/
And they have such kind of statements:
const withTodosNull = (Component) => (props) =>
!props.todos
? null
: <Component { ...props } />
As I understand Component is passed to the function, then its props get implicitly taken and fed into the return function. I do not understand how the React is doing that. I would honestly expect something like (Component) => (Component.props). What is the mechanism for this? Is it mapped correctly only if we supply the argument as props or we can supply any name? Is there a specific name for such implicit assignment?
Update
Maybe I was not clear enough, but what I am really interested in is from where props appear in the inner function if they are not passed to the previous, outer, function. I understand how the HOCs work, how to think about them, but this moment is very unclear and what in React is doing that? Is there some kind of an engine running behind the scenes, idk...
This technique is called higher-order components (HOCs) and is a way of extending components with some extra functionality.
It might look easier at first if you rewrite it using regular functions instead of arrow functions:
function withTodosNull(Component) {
return function(props) {
if (!props.todos) {
return null;
} else {
return <Component {...props} />
}
}
}
withTodosNull takes in a component and returns a new component. If this new component that is returned gets a todos prop, the component passed into the HOC will be rendered with all the props. If todos is not given as a prop, null will be rendered.
It will be probably easier to understand if we rewrite arrow functions using classic function():
function withTodosNull(Component) {
return function(props) {
if (!props.todos) {
return null;
}
return <Component {...props} />;
}
}
The inner unnamed function is a functional component. It takes properties and renders either as null or as Component.
The outer function is something called high-order-component (HoC). It is a function, that wraps a component and returns a new component.
There is no connection between Component and props. They are only parameters of two different functions.
Specifically, when you call:
class MyComponent: React.Component {
}
const myComponentWithTodosNull = withTodosNull(MyComponent);
it is the same as writing:
const myComponentWithTodosNull = props => {
if (!props.todos) {
return null;
}
return <MyComponent {...props} />;
}
Higher-Order Components are functions that "enhance" components passed as a parameter. To understand where the props are coming from let's see what would it look like to use such component.
There's our basic component, which will be passed to the HoC:
function TodoList(props) {
return (
<div>We have {props.todos.length} tasks to do!</div>
);
}
And now, we can use our HoC to create new "enhanced" component, which prevents displaying this message, when there aren't any tasks left:
const EnhancedTodoList = withTodosNull(TodoList);
Then we can use this new component, to render the message (or not, if there aren't any tasks):
<EnhancedTodoList todos={someTodos} />
As you can see, EnhancedTodoList is the first component, which gets todos. Then it decides if props should be passed to TodoList, or should it return null, when there aren't any todos.
Todos are passed explicitly from the component which renders the HoC. EnhancedTodoList acts just like a filter for TodoList.

getInitialProps never gets called...what am I doing wrong?

I'm new to React and Next.js, so bear with me. But I've spent three hours searching, and I cannot for the life of me figure out what I'm doing wrong here.
this.props.test doesn't output anything, even though it seems like it should output 'test'.
It's like getInitialProps is never even called.
class Example extends React.Component {
static async getInitialProps() {
return {
test: 'test'
}
}
render() {
return (
<h1>Hi {this.props.test}</h1>
)
}
}
I came here with the same problem, and my Component was in /pages so the other answers didn't really help.
My issue was that I was using a custom App (/pages/_app.js) and it had a getInitialProps in it.
It's a little vague in the documentation, but in the custom app example it does mention (emphasis mine):
Only uncomment [getInitialProps] if you have blocking data requirements for every single page in your application. This disables the ability to perform automatic static optimization, causing every page in your app to be server-side rendered.
This essentially means that if your custom _app.js has a getInitialProps method, you are telling Next.js that this is the only blocking data and every other page component can just be assumed to not have or need their own getInitialProps. If you have one anyway, it'll be ignored and that's where my problem was.
This was solved by calling the getInitialProps on the page component from inside the getInitialProps for the custom App.
// pages/_app.js
const App = ({Component, pageProps }) => (
<React.Fragment>
<AppHeader />
<Component {...pageProps />
</React.Fragment>
)
App.getInitialProps = async ({Component, ctx}) => {
let pageProps = {}
if(Component.getInitialProps){
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
// pages/home.js
const Home = ({name}) => <div>Hello world, meet {name}!</div>
Home.getInitialProps = async () => {
return { name: 'Charlie' }
}
export default Home
I'm not sure why you can't return pageProps directly (nothing gets passed), but it seems to be something special and it's working for me now, so..
Because getInitialProps only works for Pages in Next.js, the correct method for child components is componentDidMount with setState instead of props.
Just remove pages/_app.js file if it's your case.
That happens because _app.js overrides the defaults of NextJS.
If you still want to use getInitialProps along with _app.js you must declare this very function inside _app.js:
Inside _app.js:
export default class MyApp extends App {
static async getInitialProps(appContext){
// your logic goes here
return {}
}
render(){
const {Component} = this.props;
return (
<Layout>
<Component />
</Layout>
)
}
}

How to re render a static component in reactjs

I am developing a webapp in reactjs using typescrpit in visual studio 2017, which is very new technology for me. I am stuck at a problem where i dont know how to re-render a component from another component.
I have a log in page. What i want to do is display the username in my header component when the user logs in. Only problem is, the header component is common for all my web pages. Following is my layout.tsx file:-
export interface LayoutProps {
children?: React.ReactNode;
}
export class Layout extends React.Component<LayoutProps, {}> {
public render() {
return <div>
<Header />
<div className='layout_wrapper'>
{this.props.children}
</div>
<Footer />
</div>;
}
}
This is the only file where i have used my header component so that i wont have to use it in every component i create. componentDidMount() of header component will check for access token and make an api call to get user details. Now my question is, how can i re-render this component from another component so that when the user logs in, he can see his name in this header component? Please tell me if i need to provide more code if my question is not clear. Thanks
Considering this is a small app, this solution will work. But it shouldn't be used when the app isn't a small one, because it will make the code complex.
So, according to information given by you, the hierarchy is as follows:
<Header>
<SignIn>
<SignInContent/>
</SignIn>
</Header>
,where SignInContent component is calling the api. We will define a function in Header, and pass it as props to the SignIn component
export class Header extends React.Component<HeaderProps, HeaderState> {
constructor(){
this.state = { isLoggedIn: false }; //Add this to existing state variables
}
render() {
return {
<SignIn setIsLoggedInTrue={this.setIsLoggedInTrue.bind(this)} />
}
}
componentDidUpdate(prevProps, prevState) {
if(this.state.isLoggedIn && !prevState.isLoggedIn) {
// Make the api call for fetching user details
}
}
setIsLoggedInTrue() {
this.setState({isLoggedIn: true});
}
}
And in the SignIn component, again in the render method pass the props to SignInContent like this:
<SignInContent setIsLoggedInTrue={this.props.setIsLoggedInTrue} />
once it is logged in, you can call this.props.setIsLoggedInTrue function from the SignInContent component. This should solve your purpose
Your component will be re-rendered if the supplied props have changed.
By looking at your code, the value of this.props.children should change which will eventually trigger a re-render of your component
Also, if you want to know how to trigger it at login. There should be a componentWillReceiveProps method which checks the login state of the user, and if found logged in will update the values passed to the component :)
EDIT
Here's a sample code to your problem. Since this.props.children is used. You'll need to use it like this.
render() { return (
<Layout>
{this.state.userName}
</Layout>
);}
Now call the api and set the userName

Resources