When calling a component in which there is a 'connect' from 'redux' and properties that need to be transferred when calling, the linter swears at the absence of properties
Component1
interface Component1Props {
id: numbers;
users: any[];
}
const Component1: React.FC<Component1Props> = ({
id,
users
}) => {
...
};
const mapStateToProps = ({ users }: any) => ({
users
});
export default connect(mapStateToProps, null)(Component1);
Component2
import Component1 from ...
const Component2 = () => {
return <Component1 id={id} />; <-- linter error: property 'users' is missing
}
How fix this?
You may want to use this approach from their official document
typing-the-connect-higher-order-component
ConnectedProps
Use the ConnectedProps<T> type exported by #types/react-redux^7.1.2 to infer the types of the props from connect automatically.
import { connect, ConnectedProps } from 'react-redux'
interface RootState {
}
const mapState = (state: RootState) => ({
})
const mapDispatch = {
}
const connector = connect(mapState, mapDispatch)
type PropsFromRedux = ConnectedProps<typeof connector>
type Props = PropsFromRedux & {
backgroundColor: string
}
const MyComponent = (props: Props) => (
<div style={{ backgroundColor: props.backgroundColor }}>
<button onClick={props.toggleOn}>
Toggle is {props.isOn ? 'ON' : 'OFF'}
</button>
</div>
)
export default connector(MyComponent)
Related
I am a beginner in Redux - I need to replace mapStateToProps and mapDispatchToProps to hooks.
I've replaced mapStateToProps to useSelector, but I'm having trouble replacing mapDispatchToProps to hook useDispatch.
The code I attach below shows what I am currently working on.
interface DepartmentsFilterOwnProps {
id?: GenericId;
name?: string;
productCount?: number;
checkboxIconSize?: CheckboxIconsSize;
className?: string;
}
interface DepartmentsFilterStore {
activeDepartmentsIds: GenericId[];
}
interface DepartmentsFilterActions {
onDepartmentChange: (departmentId: GenericId) => void;
}
export type DepartmentsFilterProps = DepartmentsFilterOwnProps & DepartmentsFilterStore & DepartmentsFilterActions;
export const DepartmentsFilter = ({
id,
name,
productCount,
checkboxIconSize,
className,
onDepartmentChange,
}: DepartmentsFilterProps) => {
const isChecked = activeDepartmentsIds.indexOf(id) > -1;
const onChangeCheckbox = (departmentId: GenericId) => () => onDepartmentChange(departmentId);
const isDisabled = !productCount;
return (
<P.FilterGroup className={className}>
<P.Checkbox
checked={isChecked}
iconSize={checkboxIconSize}
disabled={isDisabled}
onChange={onChangeCheckbox(id)}
>
{name}
<SelectFilterParts.FilterProductCount>{' '}({productCount})</SelectFilterParts.FilterProductCount>
</P.Checkbox>
</P.FilterGroup>
);
};
const activeDepartmentsIds = useSelector(getDepartmentsActiveIdsSelector);
const mapDispatchToProps: MapDispatchToProps<DepartmentsFilterActions, {}> = (dispatch) => ({
onDepartmentChange: (departmentId: GenericId) => {
dispatch(toggleDepartment(departmentId));
},
});
export default connect(null, mapDispatchToProps)(DepartmentsFilter);
The correct way to use useDispatch hook is something like this:
import { useDispatch } from 'react-redux'
export const DepartmentsFilter() {
//assign it to a new variable
const dispatch = useDispatch()
//use it somewhere, for example:
useEffect(() => {
dispatch({ type: 'YOUR_SAGA' })
})
than delete the mapDispatchToProps and the connect
I'm building a custom dropdown component and i'm using redux toolkit to manage the state. It works just fine
But when I reuse the dropdown component in another place, in the same page, the "states conflicts", so when I open one dropdown, the another opens to. (This is my dropdown reducer)
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
interface Dropdown {
isOpen: boolean;
selectedValue: string;
}
export const toggleOpen = createAction("dropdown/toggleOpen");
export const changeSelectedValue = createAction<string>(
"dropdown/changeSelectedValue"
);
const initialState = {
isOpen: false,
selectedValue: "-- Selecione --",
} as Dropdown;
const dropdownReducer = createReducer(initialState, (builder) => {
builder
.addCase(toggleOpen, (state) => {
state.isOpen = !state.isOpen;
})
.addCase(changeSelectedValue, (state, action) => {
state.selectedValue = action.payload;
});
});
const dropdownStore = configureStore({
reducer: dropdownReducer,
});
type RootState = ReturnType<typeof dropdownStore.getState>;
type AppDispatch = typeof dropdownStore.dispatch;
export const useDropdownDispatch = () => useDispatch<AppDispatch>();
export const useDropdownSelector: TypedUseSelectorHook<RootState> = useSelector;
export default dropdownStore;
Is there any way that I can create different "instances" of the same store, so each dropdown has it's own?
PS: I'm populating the Provider in the Dropdown component, so there is one provider to each dropdown, as follow:
import React from "react";
import { Provider } from "react-redux";
import ArrowDown from "../assets/icons/arrow-down";
import ArrowUp from "../assets/icons/arrow-up";
import store, {
useDropdownSelector,
useDropdownDispatch,
toggleOpen,
changeSelectedValue,
} from "../store/reducers/dropdown";
import styles from "./SingleDropdown.module.scss";
interface ItemProps {
value: string;
onClick?: (value: string) => void;
}
const ArrowIcon = () => {
const isOpen = useDropdownSelector((state) => state.isOpen);
return isOpen ? <ArrowUp /> : <ArrowDown />;
};
export const SelectItem: React.FC<ItemProps> = ({
children,
value,
onClick,
}) => {
const dispatch = useDropdownDispatch();
const changeSelectedValueClickHandler = () => {
dispatch(changeSelectedValue(value));
if (onClick) onClick(value);
};
return (
<div
className={styles.dropdown__menu__items}
onClick={changeSelectedValueClickHandler}
id={value}
>
{children}
</div>
);
};
const SelectMenu: React.FC = ({ children }) => {
const isOpen = useDropdownSelector((state) => state.isOpen);
return isOpen ? (
<div className={styles.dropdown__menu}>{children}</div>
) : null;
};
const InitialSelectItem = () => {
const selectedValue = useDropdownSelector((state) => state.selectedValue);
const dispatch = useDropdownDispatch();
return (
<div
onClick={() => dispatch(toggleOpen())}
className={styles.dropdown__field}
>
{selectedValue}
<ArrowIcon />
</div>
);
};
export const SingleSelect: React.FC = ({ children }) => {
return (
<Provider store={store}>
<div className={styles.dropdown}>
<InitialSelectItem />
<SelectMenu>{children}</SelectMenu>
</div>
</Provider>
);
};
Generally, we would suggest not keeping state like this in Redux, for exactly the kind of reason you just saw. It isn't "global" state - only one specific component cares about it:
https://redux.js.org/tutorials/essentials/part-2-app-structure#component-state-and-forms
By now you might be wondering, "Do I always have to put all my app's state into the Redux store?"
The answer is NO. Global state that is needed across the app should go in the Redux store. State that's only needed in one place should be kept in component state.
If you truly need to have this data in Redux, and control multiple "instances" of a component with their own separate bits of state, you could use some kind of a normalized state structure and track the data for each component based on its ID.
I'm looking to correctly annotate the Home function component's parameters, but am running into a little bit of trouble. I was hoping to annotate it like: { events }: { events: Event[] }, but am getting the TypeScript error, Property 'events' does not exist on type '{ children: ReactNode }' in Next.js.
Next does a lot of wizardry behind the scenes, so I am not sure how I can fix this. Any ideas?
import type { NextPage } from 'next';
import { GetServerSideProps } from 'next';
import axios from '../lib/axios';
import { Event } from '../ts/interfaces';
const Home: NextPage = ({ events }) => {
return (
<div>
{events.map((event: Event) => (
<div key={event.title}>{event.title}</div>
))}
</div>
);
};
export const getServerSideProps: GetServerSideProps = async () => {
const res = await axios.get<Event[]>(`${process.env.API_URL}/events`);
return {
props: { events: res.data },
};
};
export default Home;
You need to pass the Home props type (in your case { events: Event[] }) as the generic type to NextPage.
import { Event } from '../ts/interfaces';
const Home: NextPage<{ events: Event[] }> = ({ events }) => {
return (
<div>
{events.map((event: Event) => (
<div key={event.title}>{event.title}</div>
))}
</div>
);
};
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.
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({
// ...