React and typescript ReactRouterProps issue - javascript

I need to deconstruct my general props and match (to get an "id" from the URL).
Component (using props):
interface Props extends RouteComponentProps<{id: string}>{
initialProjectName: string;
workspaceId: string;
}
const AddResources: React.FC<Props> = ({
initialProjectName,
workspaceId,
match,
}) => {
const projectId = match.params.id; // used here without any error
But the parent component is showing error when I pass props
Parent
<div>
<h1>Start Project Page</h1>
<AddResources
initialProjectName={initialProjectName}
workspaceId={workspaceId} // error
/>
</div>
Error Message

By using RouteComponentProps you specify that your component requires the route props, but you will also need to make sure you pass these props. If the component is directly underneath a Route (i.e. as a child or by using <Route component={..}/>, the route props are passed automatically, if not, you can use withRouter (docs) to obtain them:
const AddResourcesWithRouter = withRouter(AddResources);
and use AddResourcesWithRouter istead of AddResources.

I think you should try useHistory, useRouteMatch, useLocation or wrap withRouter(AddResources)

Related

Use urlParams in mapStateToProps in react-router v6

My mapStateToProps needs the component urlParams to select a portion of the redux store and I have tried using but I keep getting the error: "TypeError: ownProps.useParams is not a function"
my code
If I'm understanding your question/code correctly, you are trying to use the collectionId route match param as a key into the selectCollection selector.
In react-router-dom v6 there are no longer route props so rendered components need to use the React hooks to access navigate, location, and match/params. You could go the route of creating a custom withRouter HOC to inject the match params as a prop, and be accessible for the connect HOC, but use of two Higher Order Components is completely unnecessary.
I propose using the hooks in the component.
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
const CollectionPage = () => {
const { collectionId } = useParams();
const collection = useSelector(selectCollection(collectionId));
return (
... JSX ...
);
};
export default CollectionPage;

Flow type system in React components

i start to use Flow in my React application and i have question about the type system.
I have a stateless component like this
export type LinkProps = {
to: string,
icon: React$Element<any>,
style: Object
}
const Link = ({
to,
icon,
style
}: LinkProps) => (
<sample>
</sample>
)
And another component where override some props
const NavLink = (props: LinkProps) => <Link icon={<Icon />} {...props} />
So, if i use the Link component directly i have the Flow type system but is not the case with the NavLink component.
Why i can use the NavLink component without respect the type system?
It sounds like you might need to add // #flow to the top of the file where you are using the NavLink component

How to use React.memo with react-redux connect?

Does anyone know how to wrap a React component with React.memo when one is using the connect function from react-redux?
For example, how would you modify the following?
let Button = (props: Props) => (
<button onClick={props.click}>{props.value}</button>
);
Button = connect(
mapStateToProps,
mapDispatchToProps
)(Button);
I've tried:
let Button = React.memo((props: Props) => (
<button onClick={props.click}>{props.value}</button>
));
Button = connect(
mapStateToProps,
mapDispatchToProps
)(Button);
However, the function returned by connect expects a component to be passed so it errors with:
Uncaught Error: You must pass a component to the function returned by
connect. Instead received {"compare":null}
React.memo is nothing but a HOC, so you can just use:
Without memo:
connect(
mapStateToProps,
mapDispatchToProps
)(Button);
With memo:
connect(
mapStateToProps,
mapDispatchToProps
)(React.memo(Button));
And even wrap to connect: (This should be the solution with connect)
React.memo(
connect(
mapStateToProps,
mapDispatchToProps
)(Button)
);
Like we do with withRouter: withRouter(connect(...)())
Same issue here. Fixed by upgrading react-redux to version 5.1.0.
Your solution should work (I didn't try copy-pasted like that), but you also have to update react-redux to the latest version.
By the way, I think the proper implementation of React.memo within many HOC would be to be the closest to the component itself : the goal of React.memo is to check if all the new props received by the component are the same as the last props. If any HOC transforms or adds any props to the component - which connect does by mapping the Redux store to the props, React.memo should be aware of it in order to decide wether or not to update the component.
So I would go for something like that :
//import what you need to import
const Component = props => <div>{/* do what you need to do here */}</div>
export default compose(
connect(mapStateToProps, dispatchToProps),
/* any other HOC*/
React.memo
)(Component);
Codesandbox demo
As the error message says, you need to pass a component to the returned function from connect.( which means the second pair of () in connect()() )
As React.Memo returns a component, pass it into the second function of connect.Here's how you can do this.
export const MemoizedDemoComponent = connect(mapStateToProps)(React.memo(DemoComponent);
Demo component:
import React from "react";
import { connect } from "react-redux";
const DemoComponent = props => (
<div>
<p>My demo component fueled by: {props.fuel}!</p>
<p>Redux: {props.state}</p>
</div>
);
const mapStateToProps = state => ({
state: "your redux state..."
});
// create a version that only renders on prop changes
export const MemoizedDemoComponent = connect(mapStateToProps)(
React.memo(DemoComponent)
);
For a working example check also codesandbox.
For someone who want to know why react-redux throw this error.
For me, I used version 5.0.7, react-redux/src/components/connectAdvanced.js line: 92
invariant(
typeof WrappedComponent == 'function',
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
);
After upgrading this code is changed to :
invariant(
isValidElementType(WrappedComponent),
`You must pass a component to the function returned by ` +
`${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
);
How to check the WrappedComponent is changed to isValidElementType(WrappedComponent) which is exposed by react-is
So, yarn update react-redux to the version that mentioned by #Maxime Chéramy at least

Is it possible to create a React component interface?

I have the following react component:
class Cmp extends React.Component {
render () {
return <h3>{this.props.title}</h3>;
}
}
But I would like to expose or say to the consumer of my component to use it with a title otherwise it does not work the component.
Consumer would use it like
<Cmp title='Some fancy title' />
I need the consumer of my component to know that he should provide a title otherwise the component does not have any sense.
You can use PropTypes and set it to isRequired. You can also check if the prop is set at componentWillReceiveProps() and throw your error.
If you return null from a render method, nothing is rendered. You could use this knowledge to conditionally check if the prop is passed, and return null if the prop is not passed. The advantage here over using componentWillReceiveProps() is that you could use a functional component rather than a class component.
In rare cases you might want a component to hide itself even though it
was rendered by another component. To do this return null instead of
its render output.
Preventing Component from Rendering
Realistically you would also use PropTypes.
Cmp.propTypes = {
title: PropTypes.string.isRequired
};
Short Example
import React from 'react';
import PropTypes from 'prop-types';
const Cmp = (props) => props.title ? <h3>{props.title}</h3> : null
Cmp.propTypes = {
title: PropTypes.string.isRequired
}
export default Cmp;

Connect after routed component causing boolean values in props

I am currently building an app with React, React Router and React Redux
Versions:
React - v15.5.4
React Router - v4.0
React Redux - v.5.0.6
I am new to React and even newer to Redux and right when I got my head around the connect HOC I started to have this error that I cant seem to figure out.
When I connect a component to my redux store after a <switch> element and some <Route> elements. My connect within that returns my props as false boolean values where as the component within the connect has the correct props.
See code and error below for example.
Component
UserDashboardPage = connect(state => {
console.log("STATE", state);
return {
user: state.user.user,
userAuth: state.user.userAuth,
userFetched: state.user.fetched
};
})(UserDashboardPage);
UserDashboardPage.propTypes = {
user: PropTypes.shape(),
userAuth: PropTypes.shape(),
userFetched: PropTypes.boolean,
dispatch: PropTypes.func
};
CONSOLE LOG STATE
Connect with boolean prop values
Component with correct props
ERROR:
You are overwriting the local UserDashboardPage variable with the result of calling connect(). You then set PropTypes on the component returned by connect().
While you can do that, what you want in this case is to set the PropTypes of the wrapped component, not the wrapper component. Just swapping the order of execution will do it:
UserDashboardPage.propTypes = {
};
UserDashboardPage = connect(state => {
...
})(UserDashboardPage);
But you may want to consider using a different variable name for one component or the other, e.g.
UserDashboardPage.propTypes = {
};
const ConnectedUserDashboardPage = connect(state => {
...
})(UserDashboardPage);
This is usually not a problem since most people just immediately export the connected component as the default export:
export default connect(...)
The false values you're seeing are from React assigning default values to those props that failed validation. And they will always fail validation since those props are pulled from context, not passed down as normal props.
why are you passing UserDashboardPage into connect? This should be your non connected component

Categories