How to pass props in React Components correctly? - javascript

I have routes array, which pass into RouteComponent
const info = [
{ path: '/restaurants/:id', component: <Restaurant match={{ params: '' }} /> },
{ path: '/restaurants', component: <ListRestaurant match={{ path: '/restaurants' }} /> }
];
I use Axios for connection with back-end
Restaurant Component:
async componentDidMount() {
this.getOne();
}
getOne() {
const { match } = this.props;
Api.getOne('restaurants', match.params.id)
Restaurant Component:
When I see console there is en error like this
So, what can be passed as the props? Can't find solution.
Thanks in advance
App.js
import ...
import info from './components/info/routes';
class App extends Component {
render() {
const routeLinks = info.map((e) => (
<RouteComponent
path={e.path}
component={e.component}
key={e.path}
/>
));
return (
<Router>
<Switch>
{routeLinks}
</Switch>
</Router>
);
}
}
RouteComponent.js
import { Route } from 'react-router-dom';
class RouteComponent extends Component {
render() {
const { path, component } = this.props;
return (
<Route path={path}>
{component}
</Route>
);
}
}
RouteComponent.propTypes = {
path: PropTypes.string.isRequired,
component: PropTypes.object.isRequired,
};
EDITED 22/03/2020
Line with gives this: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
Check the render method of Context.Consumer.
class RouteComponent extends Component {
render() {
const { path, component } = this.props;
return (
<Route path={path} component={component} />
);
}
}
RouteComponent.propTypes = {
path: PropTypes.any.isRequired,
component: PropTypes.any.isRequired,
};
But as you see, I make PropTypes 'any'

Ok there are some changes you will need to do and might not be enough. So let me know if does not work in comments.
Step one
send component correctly to routes
class RouteComponent extends Component {
render() {
const { path, component } = this.props;
return (
<Route path={path} component={component}/>
);
}
}
Step two
Send JSX element, not JSX object
const info = [
{ path: '/restaurants/:id', component: Restaurant },
{ path: '/restaurants', component: ListRestaurant }
];

Import RouteProps from react-router-dom and use it as the interface for props in routecomponent.js.
Then, instead of calling component via expression, call it like a component, i.e.
Eg,
function Routecomponent({ component: Component, ...rest }: RouteProps) {
if (!Component) return null;
return (
<Route
{...rest}
<Component {...props} />
/>}
)
}

Related

Create React element from object attribute (with props)

In the following code I would like to pass props to the e.component element
But i'm getting an error :
Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
How can i do that ?
element={<MainComponent msg={msg} />} works but it does not meet my needs ❌❌
The element must be called like this e.component ✔️✔️
const routes = [
{
name: `main`,
path: `/main`,
component: <MainComponent />,
},
]
function MainComponent(props) {
return <h2>{`Hello ${props.msg}`}</h2>
}
function App() {
const msg = `world`
return (
<BrowserRouter basename="/">
<Routes>
{routes.map((e, j) => {
return (
<Route
key={`${j}`}
path={e.path}
// want to pass "msg" props to e.component ???????
element={<e.component msg={msg} />}
/>
)
})}
</Routes>
</BrowserRouter>
)
}
If you want to be able to pass additional props at runtime then you can't pre-specify MainComponent as JSX. Instead you could specify MainComponent as a reference to the component instead of JSX, and then render it as JSX when mapping. Remember that valid React components are Capitalized or PascalCased.
Example:
const routes = [
{
name: 'main',
path: '/main',
component: MainComponent,
},
];
function MainComponent(props) {
return <h2>Hello {props.msg}</h2>;
}
function App() {
const msg = 'world';
return (
<BrowserRouter basename="/">
<Routes>
{routes.map((e, j) => {
const Component = e.component;
return (
<Route
key={j}
path={e.path}
element={<Component msg={msg} />}
/>
)
})}
</Routes>
</BrowserRouter>
);
}
Try this
const routes = [
{
name: `main`,
path: `/main`,
component: (props) => <MainComponent {...props} />,
},
]```
If you want to render a component instance, you have to:
pass: component: <MyComponent />
use: {component}
If you have to pass a component:
pass: Component: MyComponent
use: <Component />
If you want to pass a render prop:
pass: component: (params) => <MyComponent {...params} />
use: {component()}

Why is my component unable to access data from my reducer?

I am writing a React app in which somebody can sign up as a business or user, and a user is able to search for a business by name. I do not understand why I am getting an error when trying to render my search component, saying "TypeError: Cannot read properties of undefined (reading 'toLowerCase')". I do not understand why I am getting this error because I believe I am passing in the appropriate data via my reducers and the Redux store. This is my search component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import BusinessCard from '../Business/BusinessCard'
import { Card } from 'semantic-ui-react';
class Businesses extends Component {
state = {newSearch: ""}
handleInputChange = e => {
this.setState({newSearch: e.target.value})
}
render() {
const { businessesReducer} = this.props;
let businessesMatch = businessesReducer.businesses.filter( (business ) => business.name.toLowerCase().includes(this.state.newSearch.toLowerCase()))
return (
<div>
<input placeholder="Search Events and Services Near You" value={this.state.newSearch} name="businessName" type="text" onChange={this.handleInputChange} />
<Card.Group itemsPerRow={3}>
{ businessesMatch.map((business, id) => <BusinessCard key={id} business={business} />)}
</Card.Group>
</div>
)
}
}
const mapStateToProps = (state) => {
return ({
businessesReducer: state.businessesReducer
})
}
export default connect(mapStateToProps)(Businesses);
My businesses reducer:
const initialState =
{
businesses:[],
isLoading: false
}
export default (state = initialState, action) => {
switch (action.type) {
case 'LOADING':
return {
...state,
isLoading: true
}
case "GET_ALL_BUSINESSES_SUCCESS":
return { ...state,
businesses: action.businesses,
isLoading: false
}
default:
return state
}
}
BusinessCard.js (which I am trying to render per the user's search)
import React, { Component } from 'react';
import { Card } from 'semantic-ui-react';
import { connect } from 'react-redux';
class BusinessCard extends Component {
constructor(props) {
super(props);
}
render(){
const { business, businessesReducer } = this.props;
return(
<Card>
<div key={business.id} >
<Card.Content>
<Card.Header><strong>{business.name}</strong></Card.Header>
</Card.Content>
</div>
</Card>
)
}
}
const mapStateToProps = state => {
return {
businesses: state.businesses,
businessesReducer: state.businessesReducer
}
}
export default connect(mapStateToProps)(BusinessCard);
And App.js
import { getAllBusinesses } from './actions/business/business';
import { BrowserRouter as Router, Route, Switch} from 'react-router-dom';
import history from './history';
class App extends React.Component {
componentDidMount() {
this.props.getAllBusinesses();
}
render() {
return (
<Router history={history}>
<div className="App">
<NavBar />
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About} />
<Route path="/services" component={Services} />
<Route path="/shop" component={Shop}/>
<Route path="/login-signup" component={LoginContainer}/>
<Route path="/signup" component={Signup}/>
<Route path="/business-signup" component={BusinessSignup}/>
<Route path="/professional-signup" component={ProfessionalSignup}/>
<Route path="/search" component={Businesses}/>
</Switch>
</div>
</Router>
)
}
}
const mapStateToProps = (state) => {
return {
businessesReducer: state.businessesReducer
}
}
export default connect(mapStateToProps, {getAllBusinesses})(App);
Does anybody have any idea why my search component cannot access "business" and its properties? Everything looks correct to me.
1: It would be good if you could show getAllBusinesses.
2: Please make sure if data exists in your store, you can use redux-dev-tools for that.
3: The first time that your component renders there is no data in your store and it's just an empty array so please first check if name exists and has value then try to convert it to lower case.
It would be something like this:
let businessesMatch = businessesReducer.businesses.filter(
(business) =>
business.name &&
business.name
.toLowerCase()
.includes(this.state.newSearch.toLowerCase())
);
Or if with optional chaining:
let businessesMatch = businessesReducer.businesses.filter((business) =>
business?.name
.toLowerCase()
.includes(this.state.newSearch.toLowerCase())
);
If none of these help please provide more information like a code sandbox.

How do I turn a JSX Element into a Function Component in React?

My React app has the following in App.js:
const App = () => (
<Router>
<Switch>
... various routes, all working fine ...
<Route exact path={ROUTES.DASHBOARD} render={(props) => <Dashboard {...props} />}/>
</Switch>
</Router>
);
I'm getting an error on Dashboard, which says JSX element type 'Dashboard' does not have any construct or call signatures.
This is because Dashboard is created like this:
const DashboardPage = ({firebase}:DashboardProps) => {
return (
<div className="mainRoot dashboard">...contents of dashboard...</div>
);
}
const Dashboard = withFirebase(DashboardPage);
export default Dashboard;
and withFirebase is:
import FirebaseContext from './firebaseContext';
const withFirebase = (Component:any) => (props:any) => (
<FirebaseContext.Consumer>
{firebase => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
export default withFirebase;
So withFirebase is exporting a JSX element, so that's what Dashboard is. How can I ensure that withFirebase is exporting a Component instead?
So withFirebase is exporting a JSX element, so that's what Dashboard is. How can I ensure that withFirebase is exporting a Component instead?
withFirebase is not creating a JSX element, it is creating a function which creates a JSX Element -- in other words that's a function component. Perhaps it helps to type it properly.
const withFirebase = <Props extends {}>(
Component: React.ComponentType<Omit<Props, "firebase"> & { firebase: Firebase | null }>
): React.FC<Props> => (props) => (
<FirebaseContext.Consumer>
{(firebase) => <Component {...props} firebase={firebase} />}
</FirebaseContext.Consumer>
);
Those type are explained in detail in this answer. Is your context value sometimes null? Can your DashboardPage handle that, or do we need to handle it here? Here's one way to make sure that DashboardPage can only be called with a valid Firebase prop.
const withFirebase = <Props extends {}>(
Component: React.ComponentType<Omit<Props, "firebase"> & { firebase: Firebase }>
): React.FC<Props> => (props) => (
<FirebaseContext.Consumer>
{(firebase) =>
firebase ? (
<Component {...props} firebase={firebase} />
) : (
<div>Error Loading Firebase App</div>
)
}
</FirebaseContext.Consumer>
);
Now that we have fixed the HOC, your Dashboard component has type React.FC<{}>. It's a function component that does not take any props.
You do not need to create an inline render method for your Route (this will actually give errors about incompatible props). You can set it as the component property component={Dashboard}.
complete code:
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
// placeholder
class Firebase {
app: string;
constructor() {
this.app = "I'm an app";
}
}
const FirebaseContext = React.createContext<Firebase | null>(null);
const withFirebase = <Props extends {}>(
Component: React.ComponentType<Omit<Props, "firebase"> & { firebase: Firebase }>
): React.FC<Props> => (props) => (
<FirebaseContext.Consumer>
{(firebase) =>
firebase ? (
<Component {...props} firebase={firebase} />
) : (
<div>Error Loading Firebase App</div>
)
}
</FirebaseContext.Consumer>
);
interface DashboardProps {
firebase: Firebase;
}
const DashboardPage = ({ firebase }: DashboardProps) => {
console.log(firebase);
return <div className="mainRoot dashboard">...contents of dashboard...</div>;
};
const Dashboard = withFirebase(DashboardPage);
const App = () => {
const firebase = new Firebase();
return (
<FirebaseContext.Provider value={firebase}>
<Router>
<Switch>
<Route component={Dashboard} />
</Switch>
</Router>
</FirebaseContext.Provider>
);
};
export default App;

react, typescript - a type for both stateless and normal components

I am trying to implement a ProtectedRoute component that has a component prop - which can be a stateless (pure) component or a normal react component.
These are my types:
export interface Props {
isAuthenticated: boolean;
component: React.PureComponent | React.Component;
exact?: boolean;
path: string;
}
This is my ProtectedRoute component:
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import { ROUTES } from '../../constants/routes';
import { Props } from './ProtectedRoute.types';
const ProtectedRoute = (props: Props) => {
const { isAuthenticated, component: Component, ...rest } = props;
return (
<Route
{...rest}
children={props =>
!isAuthenticated ? (
<Redirect to={{ pathname: ROUTES.login, state: { from: props.location } }} />
) : (
<Component {...props} />
)
}
/>
);
};
export default ProtectedRoute;
I am getting the following error here:
Type error: JSX element type 'Component' does not have any construct
or call signatures. TS2604
This is how I use it:
import React from 'react';
import { Route, Switch } from 'react-router-dom';
import ProtectedRoute from './ProtectedRoute';
import { ROUTES } from '../../constants/routes';
import Login from '../Login/Login';
const PlaceholderComponent = () => <div>This is where we will put content.</div>;
const NotFoundPlaceholder = () => <div>404 - Route not found.</div>;
const Routes = () => {
return (
<Switch>
<Route exact path={ROUTES.login} component={Login} />
{/* TODO protected route */}
<ProtectedRoute exact path={ROUTES.list} component={PlaceholderComponent} />
<ProtectedRoute exact path={ROUTES.procedure} component={PlaceholderComponent} />
{/* catchall route for 404 */}
<Route component={NotFoundPlaceholder} />
</Switch>
);
};
export default Routes;
And getting the following error here:
Type '() => Element' is not assignable to type 'PureComponent<{}, {},
any> | Component<{}, {}, any>'. Type '() => Element' is missing the
following properties from type 'Component<{}, {}, any>': context,
setState, forceUpdate, render, and 3 more. [2322]
This makes me think that I am using the incorrect type definition. What would be the 'correct' way to go about this? My purpose is to check that ProtectedRoute always gets a React component as the component prop.
A type for both functional and class components is ComponentType.
It should be:
export interface Props {
isAuthenticated: boolean;
component: React.ComponentType;
exact?: boolean;
path: string;
}
Probably found it, but will leave this open, since I do not know if this is the correct solution:
export interface Props {
isAuthenticated: boolean;
component: React.ComponentClass<any> | React.StatelessComponent<any>;
exact?: boolean;
path: string;
}

connect react redux HOC got error of `Cannot call a class as a function`

What's wrong with my HOC below? I got error of cannot call a class as function?
https://i.imgur.com/SirwcGZ.png
My HOC
const CheckPermission = (Component) => {
return class App extends Component {
componentDidMount() {
this.props.fetchUsers().then(resp => {
this.setState({user: true, loading: false});
})
}
render() {
const { user, loading } = this.props
loading && <div>Loading...</div>
!user && <Redirect to="/dashboard" />
return <Component {...this.props} />
}
}
}
export default connect(state=>state.global, {fetchUsers})(CheckPermission)
This is how I import and user CheckPermission:
<Route exact path='/dashboard' component={CheckPermission(Dashboard)} />
you can't wrap checkPermissions with a connect because it is also a HOC.
Instead you have to compose them.
import { compose } from 'redux';
...
export default compose(
connect(state=>state.global, {fetchUsers}),
CheckPermission
);

Categories

Resources