How to namespace parent props using mapState - reactjs

I am told that props from the parent component are automatically passed to child, you cannot intercept them, even if you use:
function mapStateToProps(state: RootState, ownProps: OwnProps) {
return {
s: {...state},
o: {...ownProps},
};
}
I want to make sure that props from state do not clash with props from the parent component. Is there
we are using Redux connect like:
export default connect<StateProps, DispatchProps, AllProps, RootState>(
mapState,
mapDispatch
)(OurComponent);
so for example if I have a component
'use strict';
import React from 'react';
import { AppDispatch } from '../../cp';
import { RootState } from '../../reducers/root';
import { connect } from 'react-redux';
import moment from "moment";
interface OwnProps {
//The type for the props provided by the parent component
s?: any;
z1?: any,
z2?: any,
z3?: any,
o?:any
}
function mapDispatch(dispatch: AppDispatch<any>) {
return { dispatch };
}
function mapState(state: RootState, ownProps: OwnProps) {
return {
o: {
...ownProps
},
s: {
...state
}
};
}
type StateProps = ReturnType<typeof mapState>;
type DispatchProps = ReturnType<typeof mapDispatch>;
type AllProps = StateProps & DispatchProps & OwnProps;
class Home extends React.Component<AllProps, RootState>{
constructor(p: AllProps) {
super(p);
console.log('home props:', p);
}
render(){
return <div>Home Works</div>;
}
}
export default connect<StateProps, DispatchProps, AllProps, RootState>(mapState, mapDispatch)(Home);
and then I render it with:
<Home dispatch={null as any} s={null as any} o={null as any} z1={1} z2={2} z3={true}/>
I get this logged:

I think the only good solution is to use mergeProps like so:
interface OwnProps {
//The type for the props provided by the parent component
}
function mergeProps(state: RootState, dispatch: Dispatch<any>, ownProps: OwnProps) {
return {
dispatch,
o: ownProps, // ownProps are namespaced onto o.
s: { // stateProps are namespaced onto s.
apiInReach: state.dev.apiInReach,
}
};
}
//// ...
export default connect(
null,
null,
mergeProps
)(Dev);
the documentation for mergeProps is here:
https://github.com/reduxjs/react-redux/blob/master/docs/api/connect.md#mergeprops-stateprops-dispatchprops-ownprops--object

Related

How to connect a React component to my Redux store using a selector that takes an argument in Typescript?

I would like my component to select a portion of my state by providing an id, so I wrote a selector method like so:
const selectThing = (state: RootState, id: string) => state.things.get(id);
Then, to access the id in my mapState, I pass in my component's Props:
const mapState = (state: RootState, props: Props) => ({
thing: selectThing(state, props.id)
})
However, when I connect everything per the docs, I am told that "PropsFromRedux circularly references itself", which makes sense because I am now feeding PropsFromRedux back through my mapState method, as it is extended by Props:
import { connect, ConnectedProps } from 'react-redux'
interface SomeComplicatedThing {
//...
}
interface RootState {
things: Map<string, SomeComplicatedThing>;
}
const selectThing = (state: RootState, id: string) => state.things.get(id);
const mapState = (state: RootState, props: Props) => ({
thing: selectThing(state, props.id)
})
const mapDispatch = {
//whatever
}
const connector = connect(
mapState,
mapDispatch
)
//PropsFromRedux circularly references itself
type PropsFromRedux = ConnectedProps<typeof connector>
interface Props extends PropsFromRedux {
id: string
}
const MyComponent = (props: Props) => (
<div>whatever</div>
)
export default connector(MyComponent)
Is there a better way to approach this? How do I connect up a component with a selector that takes an argument in Typescript? Thanks.
Just separate out your BaseProps from your Redux Props and then add them back in at the end. I'm not sure why the redux docs have it go into a circular reference in a fairly common usage of connect.
import { connect, ConnectedProps } from 'react-redux';
import { type } from 'os';
interface SomeComplicatedThing {
//...
}
interface RootState {
things: Map<string, SomeComplicatedThing>;
}
const selectThing = (state: RootState, id: string) => state.things.get(id);
interface BaseProps {
id: string;
}
const mapState = (state: RootState, props: BaseProps) => ({
thing: selectThing(state, props.id),
});
const mapDispatch = {
//whatever
};
const connector = connect(mapState, mapDispatch);
//PropsFromRedux circularly references itself
type PropsFromRedux = ConnectedProps<typeof connector>;
const MyComponent = (props: PropsFromRedux & BaseProps) => {
return <div>whatever</div>};
export default connector(MyComponent);

How do I define component type?

I'm trying to write a function that takes Higher Order Components as a argument and returns component connected with the redux store. But this code have a type error in the last line. How to I define type correctly?
import React from 'react'
import { connect } from 'react-redux'
import { Dispatch } from 'redux'
import { AppState } from '~/store'
import { decrement, increment } from '~/store/home'
export type CountableProps = StateType & DispatchType
type StateType = ReturnType<typeof mapStateToProps>
const mapStateToProps = (state: AppState) => ({
count: state.home.count
})
type DispatchType = ReturnType<typeof mapDispatchToProps>
const mapDispatchToProps = (dispatch: Dispatch) => ({
increment: () => dispatch(increment()),
decrement: () => dispatch(decrement())
})
const createCountableComponent =
<P extends CountableProps>(Component: React.ComponentType<P>) =>
class extends React.Component<P> {
componentDidMount() {
// do something...
}
render() {
return <Component {...this.props} />
}
}
export const countable = <P extends CountableProps>(Component: React.ComponentType<P>) =>
connect(
mapStateToProps,
mapDispatchToProps
)(createCountableComponent(Component))
Type error
TS2345: Argument of type 'typeof ConfigurableComponent' is not assignable to parameter of type 'ComponentType<Matching<CountableProps, P>>'
Explicitly typing the connect function seems to work:
export const countable = <P extends CountableProps>(Component: React.ComponentType<P>) =>
connect<StateType, DispatchType, Omit<P, keyof (StateType & DispatchType)>>(
mapStateToProps,
mapDispatchToProps
)(createCountableComponent(Component) as any);
We can cast createCountableComponent(Component) as any, since we typed connect with correct typings (state, dispatch and ownProps types). This means when you use this component you will only see props that are not Countable (wich is the desired solution).
So:
class Test extends React.Component<{test: string}> {
render() {
return <div />;
}
};
countable(Test)
will give an error, while:
class Test extends React.Component<CountableProps & {test: string}> {
render() {
return <div />;
}
};
const Test2 = countable(Test)
<Test2 /> -> intelisense will only give you test now :)
Hope this helps.

Importing dispatch actions to React Component assistance

I am having trouble importing a dispatch action. The compiler is complaning that :
Type 'Readonly<{ children?: ReactNode; }> & Readonly<{}>' has no property 'onShowError' and no string index signature.
const { onShowError, error, errorMessage } = this.props this is the code that is causing the problem.
I understand there is something wrong with my imports and how React extends the Component, etc but I just can't find the solution. I'm new to TypeScript let alone JavaScript. I just cannot figure out where something is going wrong.
I have tried creating my own interface CustomProps and declaring onShowError is a function but does not work. Not Assignable to {}
import * as React from "react"
import { Dispatch, Action } from "redux"
import { connect } from "react-redux"
import { AppState } from "reducers"
import { showError } from "data/error_handler"
import Snackbar from "material-ui/Snackbar"
import RaisedButton from "material-ui/RaisedButton"
class ErrorHandler extends React.Component {
hideErrorPopup = () => {
this.setState({
error: false,
})
}
public render() {
const { onShowError, error, errorMessage } = this.props
return (
<div>
<RaisedButton
onClick={onShowError}
label="Toggle ErrorHandler"
/>
<Snackbar
bodyStyle={{ backgroundColor: "#ffa000", marginBottom: "5px" }}
open={error}
message={errorMessage}
autoHideDuration={5000}
onRequestClose={this.hideErrorPopup}
/>
</div>
)
}
}
const mapStateToProps = (state: AppState) => ({
errorMsg: state.errorRedux.errorMessage,
error: state.errorRedux.error,
})
const mapDispatchToProps = (dispatch: Dispatch<Action>) => {
return {
onShowError: () => dispatch(showError()),
}
}
export default connect<any>(
mapStateToProps,
mapDispatchToProps
)(ErrorHandler)
Reducer.ts
import { ErrorHandlerProps, ActionTypes } from "./"
const initialState: ErrorHandlerProps = {
error: false,
errorMessage: "",
}
export default (
state: ErrorHandlerProps = initialState,
action: ActionTypes
) => {
switch (action.type) {
case "SHOW_ERROR":
return {
...state,
}
}
}
Interface.ts & index.ts
export interface ErrorHandlerProps {
error: boolean
errorMessage: string
}
import reducer from "./reducer"
export { reducer }
export * from "./actions"
export * from "./interfaces"
actions.ts
export type ActionTypes = {
type: "SHOW_ERROR"
error: boolean
errorMessage: string
}
export const showError = (): ActionTypes => ({
type: "SHOW_ERROR",
error: true,
errorMessage: "[ACTIONS]",
})
You probably want to explicitly specify the shape of your component:
class myClass extends React.Component<PropShape, StateShape>
To get the props working, provide the types of your props (which includes your component's actual props, and the props injected by connect: mapStateToProps and mapDispatchToProps). In this case, you only need the injected props:
class ErrorHandler extends React.Component<
ReturnType<typeof mapStateToProps>
& ReturnType<typeof mapDispatchToProps>
> {
...
}
const mapStateToProps = (state: AppState) => ({
errorMsg: state.errorRedux.errorMessage,
error: state.errorRedux.error,
})
const mapDispatchToProps = (dispatch: Dispatch<Action>) => {
return {
onShowError: () => dispatch(showError()),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ErrorHandler)
You'll probably also want to include the shape of your state, if you truly intend to keep a separate local component state, although I'm not sure what your final intention there is:
class ErrorHandler extends React.Component<
ReturnType<typeof mapStateToProps>
& ReturnType<typeof mapDispatchToProps>,
IState> {
...
}
interface IState {
error: boolean;
}
See https://github.com/sw-yx/react-typescript-cheatsheet for some common use cases for using React with TypeScript.
If you just want to quickly get around ts' complaint, the as any trick will do:
const { onShowError, error, errorMessage } = this.props as any
To address this properly, you need to pass the CustomProps to your Component:
interface CustomProps {
onShowError: Function;
error: boolean;
errorMessage: string;
}
class ErrorHandler extends React.Component<CustomProps> {
hideErrorPopup = () => {
this.setState({
// ...

Typescript & Redux connect

I'm trying to connect redux to a component using Typescript and keep running into the same error.
Argument of type 'typeof BaseLayoutUnconnected' is not assignable to
parameter of type 'Component < any, {}, any>'. Property 'setState' is
missing in type 'typeof BaseLayoutUnconnected'.
import * as React from 'react';
import { IBaseLayoutProps, IBaseLayoutState } from './base-layout.types';
import { ChatContainer } from '../../components';
import { connect, DispatchProp } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { RouteComponentProps } from 'react-router';
import { ChatActions } from 'app/actions';
import { RootState } from 'app/reducers';
import { omit } from 'app/utils';
export const mapStateToProps = (state: RootState, ownProps) => {
return {
chatItems: state.chatItems
};
};
export const mapDispatchToProps = (dispatch: Dispatch) => ({
actions: bindActionCreators(omit(ChatActions, 'Type'), dispatch)
});
export class BaseLayoutUnconnected extends React.Component<IBaseLayoutProps, IBaseLayoutState> {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { actions, chatItems } = this.props;
return <ChatContainer actions={actions} chatItems={chatItems} />;
}
}
export const BaseLayout = connect(
mapStateToProps,
mapDispatchToProps
)(BaseLayoutUnconnected);
This is being called in my app.tsx via
<Route exact={true} path="/" component={BaseLayout} />
Here are the props and state
export interface IBaseLayoutProps {
chatItems: RootState.ChatState;
actions: ChatActions;
}
export interface IBaseLayoutState {}
ChatActions looks like
import { createAction } from 'redux-actions';
import { ChatItemModel } from 'app/models';
export namespace ChatActions {
export enum Type {
ADD_CHAT_ITEM = 'ADD_CHAT_ITEM'
}
export const addChatItem = createAction<PartialPick<ChatItemModel, 'text'>>(Type.ADD_CHAT_ITEM);
}
export type ChatActions = Omit<typeof ChatActions, 'Type'>;
That's a problem i had too when i first started with Redux and TypeScript. There is a tricky solution. The connect methode takes alot of generics. I try to explain it with your example.
First of all you have to split the properties of your BaseLayoutUnconnected.
export interface IBaseLayoutStateProps {
chatItems: RootState.ChatState;
}
export interface IBaseLayoutDispatchProps {
actions: ChatActions;
}
export interface IBaseLayoutOwnProps {
// put properties here you want to make available from the connected component
}
export type IBaseLayoutProps = IBaseLayoutOwnProps & IBaseLayoutDispatchProps & IBaseLayoutStateProps
export interface IBaseLayoutState {}
Then you have to fill the generics of the different redux functions.
const mapStateToProps: MapStateToProps<IBaseLayoutStateProps, {}, RootState> = (state: RootState): IBaseLayoutStateProps => ({
chatItems: state.chatItems
})
export const mapDispatchToProps: MapDispatchToPropsFunction<IBaseLayoutDispatchProps, IBaseLayoutOwnProps> = (dispatch: Dispatch, ownProps: IBaseLayoutDispatchProps): IBaseLayoutDispatchProps => ({
actions: bindActionCreators(omit(ChatActions, 'Type'), dispatch)
});
export default connect<IBaseLayoutStateProps , IBaseLayoutDispatchProps, IBaseLayoutOwnProps , RootState>(
mapStateToProps,
mapDispatchToProps
)(BaseLayoutUnconnected as any)
a good source, where you can find all this stuff i wrote and more is this repository

Cannot determine how to use Redux and React-router on the same component

I am starting to learn typescript and the is a behaviour that I don't understand.
I got this error:
Type 'ComponentClass<{}>' is not assignable to type 'StatelessComponent<void | RouteComponentProps<any>> | ComponentClass<void | RouteComponentProps<a...'.
Type 'ComponentClass<{}>' is not assignable to type 'ComponentClass<void | RouteComponentProps<any>>'.
Type '{}' is not assignable to type 'void | RouteComponentProps<any>'.
Type '{}' is not assignable to type 'RouteComponentProps<any>'.
Here is my App component :
interface AppProps extends React.Props<void> {
todos: TodoItemData[];
actions: typeof TodoActions;
};
interface AppState {
/* empty */
}
class App extends React.Component<AppProps, AppState>{
render() {return (<div></div>);
}
}
function mapStateToProps(state: RootState) {
return {
todos: state.todos
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(TodoActions as any, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
If I change in the declaration of my App component AppProps by void or React.Props I got no errors but I always have one with AppProps.
I don't understang why it's not working, AppProps is extending from React.Props. Did you see the mistake ?
I ran into a similar problem with react-router#4.1.1.
I fixed it by extending my AppProps interface with RouteComponentProps interface, so in your case the AppProps interface would look like this:
import { RouteComponentProps } from 'react-router';
...
interface AppProps extends RouteComponentProps<any> {
todos: TodoItemData[];
actions: typeof TodoActions;
}
Based on this answer on GitHub, this may work:
export default connect< AppProps, {}, {} >(
mapStateToProps,
mapDispatchToProps
)(App);
This has to do with react-router and redux you have to add types to your connect function:
interface AppProps {
todos: TodoItemData[];
};
interface DispatchProps {
actions: typeof TodoActions;
}
interface AppState {
/* empty */
}
class App extends React.Component<AppProps & DispatchProps & RouteComponentProps, AppState>{
render() {return (<div></div>);
}
}
function mapStateToProps(state: RootState) {
return {
todos: state.todos
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(TodoActions as any, dispatch)
};
}
export default connect<AppProps, DispatchProps, RouteComponentProps<any>(
mapStateToProps,
mapDispatchToProps
)(App);

Resources