How to replace - mapDispatchToProps to useDispatch - REACT REDUX - mapdispatchtoprops

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

Related

Using `useContext` with TypeScript - Type 'MyType | null` is not assignable to type 'MyType'

I'm working on implementing TypeScript on a small codebase I've been working on and having a few troubles with the above error. I've searched for answers but none seemed to fix the actual issue I was having.
I'm getting the error:
Type 'ContextType | null' is not assignable to type 'ContextType'.
I have tried setting it to null, undefined, and object, and nothing seems to help so I'd appreciate some help with this one!
Store.txt
import { useReducer, createContext, useMemo } from "react";
import { INITIAL_DATA } from './todo/constants';
import { ContextType, ITodo, ACTIONTYPE } from './todo/models';
export const StoreContext = createContext<ContextType | null>(null)
export enum ACTIONS {
DELETE_TODO = "delete_todo",
ADD_TODO = "add_todo",
};
const reducer = (state: ITodo, action: ACTIONTYPE) => {
switch (action.type) {
case ACTIONS.DELETE_TODO:
return [...action.payload];
case ACTIONS.ADD_TODO:
return action.payload;
default:
return state;
}
};
interface Props {
children: React.ReactNode;
}
export const StoreProvider = ({ children }: Props) => {
const [state, dispatch] = useReducer(reducer, INITIAL_DATA);
const contextValue: ContextType = useMemo(() => {
return { state, dispatch };
}, [state, dispatch]);
return (
<StoreContext.Provider value={contextValue}>
{children}
</StoreContext.Provider>
);
};
Component.tsx
import { useContext } from 'react';
import { StoreContext, ACTIONS } from '../../../store';
import { ContextType } from '../../models'
export const AddTodo = () => {
const { state, dispatch }: ContextType = useContext(StoreContext);
const validateFirstStep = async () => {
return await isValid(firstStepForm);
}
const closeDrawer = () => {
onClose();
}
const handleSubmit = async () => {
const newTodoEntry = { /** todo **/ }
const newData = [...state, newTodoEntry];
dispatch({ type: ACTIONS.ADD_TODO, payload: newData });
}
return (
<div>
{ /* Something Todo happens here */ }
</div>
)
}
Models.tsx
import { ACTIONS } from '../../store';
export type ITodos = {
id: string;
todoName: string;
todoType: string;
}[];
export type ContextType = {
state: ITodos;
dispatch: React.Dispatch<ACTIONTYPE>;
}
export type ACTIONTYPE =
| { type: ACTIONS.ADD_TODO, payload: ITodos }
| { type: ACTIONS.DELETE_TODO; payload: ITodos }
You need to provide default context value in case of there is no provider higher in the react tree;
export const StoreContext = createContext<ContextType>({todos: [], dispatch: () => {}})

Why mapStateToProps not showing any updates (TypeScript, ReactJS, Redux)

When I type some text in the inputfield it shows some data when I log inside the reducer, also the console.log(state.genreList) outputs an array. But it will not update the mapStateToProps inside the SearchInput.tsx In action and reducer I see the value is being passed correctly, but still confused why it will not pass them to mapStateToProps. Do I miss something?
To give you a better understanding I will add a codesandbox.
link to codesandbox
// Reducer
import { types } from "../actions";
const initialState = {
genreList: [],
videoList: [],
inputValue: ""
};
export const videoList = (state = initialState, action: any) => {
switch(action.type) {
case types.GET_DATA: {
return {
...state,
genreList: [...state.genreList, action.data]
};
}
case types.GET_INPUT_VALUE: {
return {
...state,
inputValue: action.value
}
}
default:
return state;
}
};
export default videoList;
// Component
import React, { useEffect } from 'react';
import { connect, useDispatch, ConnectedProps } from "react-redux";
import { getData, getInput } from "../../actions/index";
import axios from 'axios';
interface Genre {
id: number;
name: string;
}
interface Video {
id: number;
artist: string;
title: string;
release_year: number;
genre_id: number;
image_url: string;
}
interface IProps {
genres?: Genre[];
videos?: Video[];
input_value?: string;
}
export const SearchInput: React.FC<InputProps | IProps> = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://raw.githubusercontent.com/XiteTV/frontend-coding-exercise/main/data/dataset.json');
dispatch(getData(response.data));
} catch (err) {
console.log(err)
}
}
fetchData();
}, [dispatch]);
const passValue = (e: string) => {
dispatch(getInput(e));
}
return (
<div>
<input type="text" onChange={(e) => passValue(e.target.value)}/>
<div>Search Input</div>
</div>
)
}
function mapStateToProps(state: any){
// why I dont see anything here????
console.log(state);
return {
genres: state.genreList,
}
}
const connector = connect(mapStateToProps);
type InputProps = ConnectedProps<typeof connector>;
export default connect(SearchInput);
Issues
You are importing the named import, i.e. the unconnected, undecorated SearchInput component.
import { SearchInput } from './components/SearchInput/SearchInput';
You don't connect SearchInput to your redux store correctly.
const connector = connect(mapStateToProps);
type InputProps = ConnectedProps<typeof connector>;
export default connect(SearchInput); // <-- mapStateToProps not used
Solution
Connect SearchInput to redux.
const connector = connect(mapStateToProps); // <-- use connector
type InputProps = ConnectedProps<typeof connector>;
export default connector(SearchInput); // <-- here
Default import the connected component.
import SearchInput from './components/SearchInput/SearchInput';

How to do AuthContext, createDataContext using Typescript for React Native Expo dev?

AuthContext.tsx
import createDataContext from './createDataContext';
import serverApi from '../api/server';
const authReducer = ({state, action}: any) => {
switch(action.type){
default:
return state;
}
};
const signup = () => {
return async ({email, password}: any) => {
try{
const response = await serverApi.post('/signup', {email, password});
console.log(response.data)
}catch(err){
console.log(err.message);
}
};
}
const signin = ({dispatch}:any) => {
return ({email, password}: any) => { };
}
const signout = ({dispatch}: any) => {
return () => {};
}
export const {Provider, Context} = createDataContext(
authReducer,
{signin, signout, signup},
{isSignedIn: false}
);
createDataContext
import React, { useReducer } from 'react';
export default ({reducer, actions, defaultValue}: any) => {
const Context = React.createContext();
const Provider = ({ children }: any) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions: any = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
}
I copy the code from a video tutorial where react native app has been developed with js extension. But the project I am working on has tsx extension i.e. TypeScript.
How to convert the above code so it will work in my typescript react native mobile app?
({reducer, actions, defaultValue}: any) is expecting one argument with three properties. But when you call it, you are passing three separate arguments. So you want (reducer: any, actions: any, defaultValue: any). Likewise, a reducer takes two arguments so you want authReducer = (state: any, action: any) =>, and so on for a bunch of your functions.
Now we want to get rid of all the any and use actual types! Some of those types we can import from react and others we will define ourselves.
The part that's tricky is getting your context to know the types for your specific action creators and what arguments each one requires. You want this so that you can get autocomplete suggestions for the actions and so you can know if you are calling them improperly. But that requires more advanced typescript like generics and mapped types so just copy and paste this and don't worry too much.
import React, { useReducer, FunctionComponent, Reducer, Dispatch } from 'react';
interface DataState {
isSignedIn: boolean;
// add any other properties here
}
interface SignInProps {
email: string;
password: string;
}
// you can change this
// it is common to use a type for `Action` that is a union of your specific actions
interface Action {
type: string;
payload: any;
}
// this is where I am getting tricky
type BoundActions<T> = {
[K in keyof T]: T[K] extends (d: Dispatch<Action>) => infer R ? R : never
}
type ContextValue<T> = {
state: DataState;
} & BoundActions<T>
export const createDataContext = <T extends {}>(reducer: Reducer<DataState, Action>, actions: T, defaultValue: DataState) => {
// context needs a defaultValue
const Context = React.createContext({state: defaultValue} as ContextValue<T>);
// type of children is known by assigning the type FunctionComponent to Provider
const Provider: FunctionComponent = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {} as BoundActions<T>;
for (let key in actions) {
// #ts-ignore - I don't want to make a confusing mess so just ignore this
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
}
const authReducer = (state: DataState, action: Action): DataState => {
switch (action.type) {
default:
return state;
}
};
const signup = (dispatch: Dispatch<Action>) => {
return async ({ email, password }: SignInProps) => {
try {
const response = await serverApi.post('/signup', { email, password });
console.log(response.data)
} catch (err) {
console.log(err.message);
}
};
}
const signin = (dispatch: Dispatch<Action>) => {
return ({ email, password }: SignInProps) => { };
}
const signout = (dispatch: Dispatch<Action>) => {
return () => { };
}
export const { Provider, Context } = createDataContext(
authReducer,
{ signin, signout, signup },
{ isSignedIn: false }
);
The point of doing all that is to get intellisense and type checking when you consume the context.
import React, { useContext } from 'react';
import { Provider, Context } from .... // your path
const SampleComponent = () => {
// knows all of the properties available on the context
const {state, signin, signout, signup} = useContext(Context);
const handleClick = () => {
// knows that these need email and password
signin({email: '', password: ''});
signup({email: '', password: ''});
// knows that this one is ok to call with no args
signout();
}
return (
<div>{state.isSignedIn ? "Signed In!" : "Not Signed In"}</div>
)
}
const SampleApp = () => (
<Provider>
<SampleComponent/>
</Provider>
)

How to create a generic Context.Provider in React?

I am using react's context to share data across component.
For example, I could create a user context:
const useFirebaseUser = () => {
const [user, setUser] = useState({} as User);
useEffect(() => {
return firebase.auth().onAuthStateChanged((user) => {
if (user) {
const { displayName, photoURL, uid } = user;
setUser({
displayName,
photoURL,
uid,
isAuthenticated: true,
} as User);
} else {
setUser({} as User);
}
});
}, []);
return user;
};
export const FirebaseUserContext = createContext({} as User);
export const GlobalFirebaseUserProvider = ({ children }: { children: ReactNode }) => (
<FirebaseUserContext.Provider value={useFirebaseUser()}>{children}</FirebaseUserContext.Provider>
);
similarly, I could also create a similar context to share other data, like todos
const useTodos = () => {
const [todos, setTodos] = useState(['']);
// ..
return { todos, setTodos };
};
export const TodosContext = createContext(
{} as { todos: string[]; setTodos: React.Dispatch<React.SetStateAction<string[]>> }
);
export const TodosContextProvider = ({ children }: { children: ReactNode }) => (
<TodosContext.Provider value={useTodos()}>{children}</TodosContext.Provider>
);
Upon these, I want to abstract out the value part. I am trying to create a Generic Provider:
import React, { createContext, ReactNode } from 'react';
export const CreateGenericContext = <T extends {}>(value: T) => {
const GenericContext = createContext({} as T);
const GenericContextProvider = ({ children }: { children: ReactNode }) => (
<GenericContext.Provider value={value}>{children}</GenericContext.Provider>
);
return { GenericContext, GenericContextProvider };
};
thus my user context could simplify into
export const {
GenericContext: UserContext,
GenericContextProvider: UserContextProvier,
} = CreateGenericContext(useUser());
However, React throw error message:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Is this mean it is impossible to create a generic context providre for React? I had searched online, and tutorial seems show that for context not using hooks would work. However, in case of using react hooks, how to create a generic context provider in react?
Delay the custom hook, hook can only be called/invoked inside function component.
import React, { createContext, ReactNode } from 'react';
export const createGenericContext = <T extends {}>(hook: () => T) => {
const GenericContext = createContext({} as T);
const GenericContextProvider = ({ children }: { children: ReactNode }) => (
<GenericContext.Provider value={hook()}>{children}</GenericContext.Provider>
);
return { GenericContext, GenericContextProvider };
};

How to use dispatch in a class component or how to refacter my HOC

I'm having the following HOC component which is working fine but It's giving me an error on the dispatch. I can't use it inside a class component but how do I fix this?
const withAuthentication = <Props extends object>(
Component: React.ComponentType<Props>
) => {
class WithAuthentication extends React.Component<Props & FirebaseInterface> {
render(): React.ReactNode {
const { firebase, ...props } = this.props
const dispatch = useDispatch()
const [authenticated, setAuthenticated] = useState(false)
const { userId, loggedIn } = useSelector(
(state: Record<string, ReduxProvider>) => state.user
)
useEffect(() => {
const listener = firebase.auth.onAuthStateChanged(authUser => {
if (authUser) {
console.log(authUser)
if (!loggedIn) {
firebase.user(userId).once('value', snapshot => {
dispatch(
)
})
}
setAuthenticated(true)
} else {
dispatch(
addUser({
})
)
setAuthenticated(false)
}
})
return (): void => {
listener()
}
}, [setAuthenticated, firebase, dispatch, loggedIn, userId])
}
}
return withFirebase(WithAuthentication)
}
export default withAuthentication
Any help would be appreciated!
You can't use useDispatch in class component.
You should use import.
import {connect} from 'react-redux'
import {compose} from 'redux'
...
const {dispatch} = this.props
...
return compose(withFirebase, connect(state => ({}), dispatch => ({dispatch})) )(WithAuthentication)
...
...

Resources