Reactjs - Component as props - javascript

So, I am rendering different components based on the URL using BrowserRouter and Route. But, There is a lot of markup which is similar while rendering. So, I have created a Wrapper which should take components as props and solve this!!
class Wrapper extends React.Component {
componentDidMount() {
this.props.setActiveTab(this.props.activeTab);
console.log('Wrapper props: ', this.props)
}
render() {
const OtherComponent = this.props.otherComponent
return (
<Row>
<Col md='8' id='content-block'>
<SwitchTab />
</Col>
<Col md='4' id='info-block'>
<InfoComponent info={this.props.info} {...this.props}/>
{
this.otherComponent &&
<OtherComponent {...this.props}/>
}
</Col>
</Row>
)
}
}
These are some of the Routes:
<Route
exact
path='/r/all/'
render={() =>
<Wrapper
setActiveTab={context.toggleTab}
activeTab={'3'}
info='all'
/>
}
/>
<Route
exact
path='/u/:username/'
render={(props) => {
return (
<Wrapper
setActiveTab={context.toggleTab}
activeTab={'4'}
info='user'
user={props.match.params.username}
otherComponent={Profile}
username={props.match.params.username}
/>
// <Profile username={props.match.params.username} />
)
}}
/>
<Route
exact
path='/r/:subreddit/'
render={(props) => {
return (
<Wrapper
setActiveTab={context.toggleTab}
activeTab={'4'}
info='subreddit'
otherComponent={Subreddit}
subreddit={props.match.params.subreddit}
/>
// <Subreddit subreddit={props.match.params.subreddit} />
)
}}
/>
The otherComponent is not getting rendered. I don't know where the problem is. Also if there is any other better method, please do state that.

You are checking if this.otherComponent is truthy before rendering. You just want to check if OtherComponent is truthy.
{OtherComponent && <OtherComponent {...this.props} />}
You could also change Wrapper to render children if you would prefer.
Example
class Wrapper extends React.Component {
componentDidMount() {
this.props.setActiveTab(this.props.activeTab);
console.log("Wrapper props: ", this.props);
}
render() {
const { children, ...restProps } = this.props;
return (
<Row>
<Col md="8" id="content-block">
<SwitchTab />
</Col>
<Col md="4" id="info-block">
<InfoComponent {...restProps} />
{children}
</Col>
</Row>
);
}
}
// Usage
<Wrapper>
<OtherComponent />
</Wrapper>

this.otherComponent &&
<OtherComponent {...this.props}/>
A quick look, this.otherComponent is not defined so the component is not getting rendered, should be this.props.otherComponent??

Related

How to use ternary operator to return jsx using react?

I want to hide a component if the user is in the "/items" page.
below is my code,
function Main() {
const isAdmin = getUser();
return(
<Switch>
<Route
exact
path="/items"
render={routeProps => (
<Layout>
{isAdmin ? <Items {...routeProps} />: <NotFound/>}
</Layout>
)}
/>
//other Routes
</Switch>
);
}
const Layout: React.FC = ({ children }) => (
<>
<TopBar />
{children}
<BottomBar />
</>
);
Now when the user is in /items page I don't want the TopBar and BottomBar to be displayed.
how can I do it? could someone help me with this? thanks.
Change your Layout component as below:
const Layout: React.FC = ({ children }) => {
const history = useHistory();
const isItemsPath = history.location.pathname.includes("/items");
return (
<>
{!isItemsPath && <TopBar />}
{children}
{!isItemsPath && <BottomBar />}
</>
);
}

How to access props in the component using react and typescript?

i want to access props in the react functional component using react and typescript.
I have the MainComponent which has Layout component and i pass prop isOpen to Layout component from MainComponent like in below code,
const Layout: React.FC = ({children}) => ( //how to access isOpen prop here
<>
<leftNav />
{children}
<RightNav isOpen={isOpen} />
</>
);
interface Props {
item: item;
}
function Main({ item }: Props) {
return (
<Wrapper>
<Switch>
<Route
path="/items"
render={routeProps => (
<Layout isOpen={isOpen}> //passing prop here
<Items />
</Layout>
)}
/>
</Switch>
</Wrapper>
)
}
I have tried to access it like below
interface Props {
children: any;
isOpen: boolean;
}
const Layout: React.FC = ({children, isOpen}: Props) => (
<>
<leftNav />
{children}
<RightNav isOpen={isOpen} />
</>
);
But the above throws error jsxelement is not assignable to type FC component.
could someone help me fix this. thanks.
React.FC is generic and takes a type argument for the props. You can write your layout component like this.
interface Props {
isOpen: boolean;
// if you want isOpen props to be optional
// isOpen?: boolean;
}
const Layout: React.FC<Props> = ({children, isOpen}) => (
<>
<leftNav />
{children}
<RightNav isOpen={isOpen} />
</>
);
Your main component is fine.
You need to type the props for the FC generic:
//interface Props { ... }
const Layout: React.FC<Props> = ({children, isOpen}) => (
<>
<leftNav />
{children}
<RightNav isOpen={isOpen} />
</>
);
or omit the FC altogether:
//interface Props { ... }
const Layout: ({children, isOpen}: Props) => (
<>
<leftNav />
{children}
<RightNav isOpen={isOpen} />
</>
);

not found page is not shown when route is not matched

As per me, I have a bit complex routing because I have to handle different domain for different modules. That is why i configured the routes in a following way.
Here it is
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<Root />
</ConnectedRouter>
</Provider>,
MOUNT_NODE,
);
class App extends React.Component {
render() {
return (
<Switch>
<UnauthenticatedRoute path="/auth" component={AsyncLogin} {...this.props} />
<AuthenticatedRoute path="/" component={AsyncHome} {...this.props} />
</Switch>
);
}
}
class Home extends React.Component<propsCheck> {
componentDidMount() {
this.props.getUser();
}
renderRoutes(userRole, roles, userData, props) {
const domain = window.location.hostname; // domain will be like app.abc.com, app.def.com.
switch (domain) {
case GROWTH_URL:
return growthRoutes(userRole, roles, userData, props);
case CONTENT_URL:
return contentRoutes(userRole, roles, userData, props);
default:
return growthRoutes(userRole, roles, userData, props);
}
}
render() {
if (this.props.loading) {
return <Spinner background="none" />;
}
return <Switch>{this.renderRoutes(userRole, roles, userData, this.props)}</Switch>;
}
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
)(Home);
export default withRouter(withConnect);
function NotFoundPage() {
return <div>Not found</div>;
}
export function growthRoutes(userRole, roles, userData, props) {
return (
<Switch>
<Route
exact
path="/"
render={() =>
(!isEmpty(userRole) && userRole.client !== null && isClient(roles)) ||
(!isEmpty(userData) && userData.client !== null && isClient(userData.roles)) ? (
<Redirect to={`${!isEmpty(userRole) ? userRole.client[0].company_slug : userData.company[0]}`} />
) : (
<Redirect to="/clients" />
)
}
/>
<DashboardRoute path="/clients" component={Clients} {...props} />
<DashboardRoute path="/:company/" component={ClientDetail} {...props} />
<DashboardRoute path="/:company/client_detail" component={ClientDetail} {...props} />
<DashboardRoute path="/:company/edit-client" component={Admin(Client)} {...props} />
<DashboardRoute path="/tasks" component={Tasks} {...props} />
<DashboardRoute to="*" component={NotFoundPage} />
</Switch>
);
}
I could not show NotFoundPage this way and could not figure out why it is not working. I have no idea where should i use the snippet <Route path="*" component={NotFoundPage}>. Nowhere it works. Can anyone look at this, please?
The problem is with respect to how you define your routes. since you have a Route /:company/, it will match anything which starts with / like /abc/, /abc/sfsf/fsgs and so on
So what you need to do is to first reorder your Routes, so that all Routes within Switch component work like
<Switch>
<Route
exact
path="/"
render={() =>
(!isEmpty(userRole) && userRole.client !== null && isClient(roles)) ||
(!isEmpty(userData) && userData.client !== null && isClient(userData.roles)) ? (
<Redirect to={`${!isEmpty(userRole) ? userRole.client[0].company_slug : userData.company[0]}`} />
) : (
<Redirect to="/clients" />
)
}
/>
<DashboardRoute path="/clients" component={Clients} {...props} />
<DashboardRoute path="/tasks" component={Tasks} {...props} />
<DashboardRoute path="/:company/client_detail" component={ClientDetail} {...props} />
<DashboardRoute path="/:company/edit-client" component={Admin(Client)} {...props} />
<DashboardRoute exact path="/:company" component={ClientDetail} {...props} />
<DashboardRoute to="*" component={NotFoundPage} />
</Switch>
Now in the above case routes like /abc/ffg/ will show NotFoundPage but still Routes like /abc will still match /:company where company will be abc. So what you need to do in ClientDetail, check if the company is a valid company and return the Correct view and if it isn't you return the NotFoundPage
ClientDetails
render() {
if(!validCompanies.includes(this.props.match.company)) {
return <DashboardRoute to="*" component={NotFoundPage} />
}
// Normal component logic here
}

Routes inside component previously routed

I am using react-router-dom 4.2. I have my App.js with Authenticated components inside. This components are created by me and add a little of business logic, create the component via React.createElement, and route them via Route component. Nothing unusual.
The App.js:
// ...
const App = props => (
<BrowserRouter>
<Fragment>
<Switch location={location}>
<Route
exact
path={URLS.ROOT}
render={() => <Redirect to={URLS.DASHBOARD} />}
/>
<Authenticated
{...props}
resource={ResourcesCode.DASHBOARD}
patent={PatentsCode.VIEW}
path={URLS.DASHBOARD}
component={Dashboard}
/>
<Authenticated
{...props}
resource={ResourcesCode.SUBSCRIBE}
patent={PatentsCode.VIEW}
path={URLS.SUBSCRIBE}
component={Subscribe}
/>
</Fragment>
</BrowserRouter>
// ...
Inside of the component Subscribe (mentioned above in the 2nd Authenticated component), I have more routes as you can see below:
// ...
<Route
path={URLS.SUBSCRIBE}
exact
render={() => (
//...
)}
/>
<Route
path={URLS.SUBSCRIBETWO}
exact
render={() => (
//...
)}
/>
// ...
The point is that this routes on the child component (Subscribe) are ignored.
Why are them ignored? How can I solve it?
I really need this routes inside the child component. I don't want to move them to App.js
IMPORTANT EDIT:
The second route is ignored, I realized that the first doesn't. In other words, The Route component with path={URLS.SUBSCRIBE} is working, but the component with path={URLS.SUBSCRIBETWO} is ignored, so here is the problem to solve.
EDIT:
For if you need, the Authenticated component:
// ...
}) => (
<Route
path={path}
exact={exact}
render={route => {
if (!authenticated) {
if (loggingIn) {
return '';
}
return <Redirect to={URLS.LOGIN} />;
}
if (!roleSubReady) {
return '';
}
if (path !== URLS.SUBSCRIBE && user.pendingSubscription) {
if (isLoading) {
return '';
}
return <Redirect to={URLS.SUBSCRIBE} />;
}
if (path === URLS.SUBSCRIBE && !user.pendingSubscription) {
if (isLoading) {
return '';
}
return <Redirect to={URLS.DASHBOARD} />;
}
if (resource && !checkPermission(user, resource, patent)) {
return <NotAuthorized history={route.history} />;
}
return (
<React.Fragment>
<Menu user={user} path={path} isLoading={isLoading} />
<Header show={showHeaderAndFooter} user={user} path={path} />
<MainContent>
{React.createElement(component, {
user,
resource,
...route,
})}
<Footer show={showHeaderAndFooter} />
</MainContent>
</React.Fragment>
);
}}/>
);

React router error (Failed prop type: Invalid prop `children` supplied to `Switch`, expected a ReactNode.)

Try to modify component, the main idea is, I want create login page, try modify App.js but get error
warning.js?6327:36 Warning: Failed prop type: Invalid prop children
supplied to Switch, expected a ReactNode.
My code is:
class App extends Component {
constructor(props) {
super(props)
}
routeWithSubRoutes(route) {
return (
<Route
key={_.uniqueId()}
exact={route.exact || false}
path={route.path}
render={props => (
// Pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes || null} />
)}
/>
);
}
render () {
return (
<div className={styles.App}>
<Helmet {...config.app} />
<NavBar />
<Switch>
{routes.map(route => this.routeWithSubRoutes.bind(this,route))}
</Switch>
</div>
)
}
}
export default App;
Code that I try to modify
export default () => {
// Use it when sub routes are added to any route it'll work
const login = () => {
}
const routeWithSubRoutes = route => (
<Route
key={_.uniqueId()}
exact={route.exact || false}
path={route.path}
render={props => (
// Pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes || null} />
)}
/>
);
var isLogin = false;
if(!isLogin) {
return (
<Login />
)
}
if(isLogin) {
return (
<div className={styles.App}>
<Helmet {...config.app} />
<NavBar />
<Switch>
{routes.map(route => routeWithSubRoutes(route))}
</Switch>
</div>
);
}
};
this code is working, but my not, how to fix this?
Function.bind doesn't call the function, it only binds its context. Instead, you should bind it in the constructur:
class App extends Component {
constructor(props) {
super(props)
this.routeWithSubRoutes = this.routeWithSubRoutes.bind(this)
}
/* ... */
render () {
return (
<div className={styles.App}>
<Helmet {...config.app} />
<NavBar />
<Switch>
{routes.map(route => this.routeWithSubRoutes(route))}
</Switch>
</div>
)
}
}

Categories

Resources