React.js : Passing Props from a Function to a Component - javascript

I'm trying to have App (component) call Main (function) which in turn calls Leads (component) and I want the props to follow. Main is the function that returns all the routes for my App. I'm using React Router v4.
I've simplified the code as much as possible below, hopefully not too much:
App calls Main and passes props: leads & library.
App.js
class App extends Component {
render() {
return (
<div>
<nav>
<Link to="/library">Library</Link>
<Link to="/leads">Leads</Link>
</nav>
<Main
leads={this.state.leads}
library={this.state.library}
/>
</div>
);
}
}
export default App;
Props are available here, no problem. My understanding however is that props is a local variable to the function Main, so having something point to it is the issue as props is destroyed once the function has run.
Main.js (simplified)
const Main = (props) => (
<main>
<Switch>
<Route exact path="/leads" render={(props) => (
<Lead
{...props}
leads={props.leads}
/> )}
/>
</Switch>
</main>
)
export default Main;
Here, this.props.leads in Leads.js points to null and {Object.keys(this.props.leads)} fails. (I've removed the code for renderLeads() for simplicity)
Leads.js (simplified)
class Lead extends React.Component {
render() {
return (
<div>
<h2>Leads</h2>
<table>
<tbody>
{Object.keys(
this.props.leads).map(
this.renderLeads)}
</tbody>
</table>
</div>
);
}
}
export default Lead;
I've "solved" this problem by making Main an extended class of React.Component. I still feel Main should be a function has it only manipulates the data and doesn't hold data of its own...
Should Main be a function?
Is my assessment of the situation accurate?
What would the proper way of passing props from Main to Leads be?
Thanks in advance.

Your mistake is known by "variable shadowing", when you declare a variable in a inner scope with the same name of another one that lives in a upper scope, in this case, the variable 'props'.
In the Main.js, you're rendering the <Lead /> passing an functional component to the route, passing the props that the Router gives to you, not the one you pass when you rendering <Main /> in the App.js.
I know is a bit confusing, but this explains why it worked when you change Main to an Class component, you probably was calling with this.props, right? So in this case you calling the right one.
You can decide if <Main /> should be a functional component or an class, but usually components that don't have state should be functional. You can simply change the names of your props in your outer scope (in the main) or in the inner (Route). Example:
<Route exact path="/leads" render={(routerProps) => (
<Lead
{...routerProps}
leads={props.leads}
/> )}
/>
Now, we're passing the right props in the leads.

Related

Use Redux module inside react component

I have a module A built in react-redux. A is the parent component name which looks somewhat like
const app = (
<Provider store={store}>
<A />
</Provider>
);
ReactDOM.render(app, document.getElementById('id'));
Inside component A there is a component C.js which has a connect function
A.js
render() {
return (
<div>
<C />
</div>
);
}
C.js
render() {
return(<div>SomeCode</div>
}
export default connect(mapStateToProps, mapDispatchToProps)(C);
There is another B.js file which is a ReactComponent (no redux used here). It has its own state.
I want to use the component A inside B's render method something like
import A from '/path'
render() {
return (<A/>)
}
While doing so I am getting an error.
Could not find "store" in the context of "Connect(C)".
Either wrap the root component in a , or pass a custom React context provider to
and the corresponding React context consumer to Connect(C) in connect options.
Any help appreciated.
I modified parent and exported it as following
render(){
return(
<Provider store={store}>
<A />
</Provider>
);}
Importing A worked fine

How can I avoid unnecessary re rendering of an HOC component, when the parameter component renders in ReactJS with react router

I am using HOC(Layout component here) to wrap my custom component. The Layout component contains Header and sidebar. On clicking link, it will be rendering the respective component. But my problem is that with every route, my HOC gets rendered as route target component is wrapped in this HOC. How can I make my HOC render only once.
Example Snippet.
App.js
<Router>
<Switch>
<PrivateRoute path="routeOne" component={RouteOne}/>
<PrivateRoute path="routeTwo" component={RouteTwo}/>
</Switch>
</Router>
RouteOne.js
import React from "react"
import Layout from "/hoc"
const RouteOne = () =>{
return({..jsx..})
}
export default Layout(RouteOne)
Layout.js
const Layout(WrappedComponent) => {
const userDetails = useSelector(state);
useEffect(()=>{
dispatch(fetchSomething())
},[dispatch])
return ( <HeaderNavbarUILayout header={<Header
username={userDetails.userName}>
content={<WrappedComponent/>);
}
export default Layout
I want to render my HOC component only once. How can I do that?
EDIT:
I would follow this pattern https://simonsmith.io/reusing-layouts-in-react-router-4
const DefaultLayout = ({component: Component, ...rest}) => {
return (
<Route {...rest} render={matchProps => (
<div className="DefaultLayout">
<div className="Header">Header</div>
<Component {...matchProps} />
<div className="Footer">Footer</div>
</div>
)} />
)
};
then where you would normally define routes, replace it with this:
<DefaultLayout path="/" component={SomeComponent} />
I would take a look at the following docs:
how to use useEffect
https://reactjs.org/docs/hooks-reference.html#useeffect
how to implement should component update
https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-shouldcomponentupdate
conditionally firing an effect
```
useEffect(
() => {
...
};
},
[whatever you're watching],
);
```
The hoc being wrapped around is rendered every time and that is expected. But the React's diffing algorithm will render only the changed DOM elements. The problem here was the dispatch is being called every time when the Layout page is rendered and the state gets updated and the particular DOM hence gets updated. This gives an impression of "reload" effect. Dispatching the action conditionally will do the trick. Dispatch only when the state changes.

React access a variable from child component

I have a main layout for my app and all components are rendered inside it, but this main layout needs to access a variable PageTitle from it's child components, I have looked in react documentation and come up with React Context, but I could'nt figure out how to use it in this case, sorry I am new to React.
Every component rendered by routes has a variable called PageTitle, the MainLayout should use this variable to render current page title on the top of the app, these are all functional components.
<MainLayout>
<Route path='/' exact component={HomePage} />
<Route path='/invoice' exact component={Invoice} />
</MainLayout>
Update:
I can create a context and store the value like this, but I couldn't figure out how to change it in child components.
Also I think this is a bit overkill, and there should a better solution.
export const AppContext = React.createContext({ PageTitle: 'Home' });
<AppContext.Consumer>{value=>value.PageTitle}</AppContext.Consumer>

React Router structuring - passing functions

I'm quite new to reactjs and was just wondering if there is any easy way to display information from the same component to different routes. In the following code as an example I have just two functions that are just returning divs full of text, and calling them and rendering them right away (in the class or in the router) would just have them be on the same "page".
I've tried passing the ref by props but they always ended up undefined. I figured a state change would be awkward since there is no real "event". I'm using create-react-app, react-routerv4, and react-bootstrap.
In App.js
import React, { Component } from 'react';
import './App.css';
import NavBar from './Components/NavBar/NavBar.js';
import Band from './Components/Text/Band.js';
import { Router, BrowserRouter, Link, Route } from 'react-router-dom';
class App extends Component {
render() {
return(
<div>
<BrowserRouter>
<div className="RenderRouter">
<Route exact path='/' component={NavBar}/>
<Route exact path='/' component={ControlledCarousel}/>
<Route exact path='/' component={Home}/>
//<Route exact path='/Artists/ArtistX' component={Band}/>
<Route exact path='/Artists/Artist1' component={NavBar}/>
<Route exact path='/Artists/Artist1' render={props => <Band band1text = {this.props.band1text} />}/>
<Route exact path='/Artists/Artist2' component={NavBar}/>
<Route exact path='/Artists/Artist2' render={props => <Band band2text = {this.props.band2text} />}/>
</div>
</BrowserRouter>
</div>
);
}
}
export default App;
In Band.js
import React, { Component } from 'react';
import './Band.css';
class Band extends Component {
//Constructor for state change would go here
band1text(props) {
return(
<div id="band1Text" className="BandText">
<h1>"The best riffs!</h1>
</div>
);
};
band2text(props) {
return(
<div id="band2Text" className="BandText">
<p>More info coming soon! Check out the interview!</p>
</div>
);
};
//Create handlers to call functions, and pass reference?
render() {
return(
<div className="BandDescription">
//calling in DOM render object, can't pass props from here?
//{this.props.band1text()} = compiler error
{this.band1text()}
{this.band2text()}
</div>
);
}
}
export default Band;
It would probably be easier to just have separate components and classes for every piece of each route (i.e, BandX.js, CarouselX.js) but that could get verbose and one would have to import many files. I'm using react to build a music player component for the app as well, that's why I'm not just using standard JS.
Try writing something like this in your Band component render:
render() {
return(
<div className="BandDescription">
{this.props.band1text && this.band1text()}
{this.props.band2text && this.band2text()}
</div>
);
}
This way it checks for the prop before running whichever method. If both methods are passed, both functions will return. And you shouldn't need to pass props to those methods. Try writing them as arrow functions so they will be bound band1text = () => { ... }, you will still be able to access this.props.band1text from inside the method.
The props would be undefined because there is no props with bandText being passed down to App component. Routes are nested in App component and this.props.band1Text means you are expecting to read from props passed to App. Try passing band1Text and band2Text as props to App component.
Also to read a props that's not a function just use {this.props.band1Text} in the Band.js component

react router this.props.location

I need a help with react-router v2+
I have to change class of navbar when route changed
for example for route /profile className will be "profile-header"
I tried to use this.props.location in navbar component but it shows undefined
Hope your help
Your navbar component (as you described it in your question) is probably not the route component, right? By route component I mean the one that you use in your react-router configuration that is loaded for a specific route.
this.props.location is accessible only on such route component, so you need to pass it down to your navbar.
Let's take an example:
Your router config:
<Router>
<Route path="/" component={App}>
// ...
</Router
Route component App:
class App extends React.Component{
// ...
render() {
return <Navbar location={this.props.location}/>
}
}
There could be a scenario where you may not have access to props.location to pass to the nav component.
Take for example - We had a header component in our project which was included in the routing switch to make it available to all routes.
<Switch>
<Fragment>
<Header/>
<Route path='..' component={...}/>
<Route path='..' component={...}/>
</Fragment>
</Switch>
In the above scenario there is no way to pass the location data to the Header component.
A better solution would be to us the withRouter HOC when a component is not being rendered by your router.
You will still have access to the router properties history, match and location when you wrap it in the withRouter HOC:
import { withRouter } from 'react-router-dom'
....
....
export default withRouter(ThisComponent)
react-router v4
From documentation:
<Route> component property should be used, when you have an existing component. <Route> render property takes an inline function, that returns html.
A <Route> with no path will always match.
Based on this, we can make a <Route> wrapper to any level of your html structure. It will always be displayed and have access to the location object.
As per your request, if a user comes to /profile page, the <header> will have profile-header class name.
<Router>
<Route render={({ location }) =>
<header className={location.pathname.replace(/\//g, '') + '-header'}>
// header content...
</header>
<div id="content"></div>
<footer></footer>
} />
</Router>
I couldn't solve it with the solutions given here and here is what worked for me:
I imported history into my component and assigned history.location.pathname to a variable which I later used for dynamic style manipulation.
In case you are rendering the component with pre-defined location.state values, first set your state with props.location.state then use your state data in your elements.

Categories