How to replace mapStateToProps to hook useSelector? - reactjs

How to replace this mapStateToProps code to hook useSelector? I have never worked with redux before and am having trouble understanding it.
interface BasicProductMarksListOwnProps {
productMarks: MarkResult[];
}
interface BasicProductMarksListStore {
productMarksState: AdditionalFilterState;
}
interface BasicProductMarksListActions {
onProductMarkChange: (productMarkGroupId: GenericId, isActive: boolean) =>
() => void;
}
export type BasicProductMarksListProps = BasicProductMarksListOwnProps
& BasicProductMarksListStore & BasicProductMarksListActions;
const mapStateToProps: MapStateToProps<BasicProductMarksListStore,
BasicProductMarksListOwnProps, ApplicationState> = (state) => ({
productMarksState: productMarksGroupStateSelector(state),
});

From the code you provided I assume the following would work.
const productMarks = useSelector(state => productMarksGroupStateSelector(state)).
Make sure to execute the code above inside a functional component.

Related

Why is hook not reusable?

I created a custom hook as follows:
import { useState } from 'react';
export const useMyHook = () => {
const [isVisible, setIsVisible] = useState(false);
function toggle() {
setIsVisible(!isVisible);
}
return { isVisible, toggle,}
};
I am only able to use it once (see comment below). When I call the hook again with different const, I get the error:
Property 'isVisible2' does not exist on type '{ isVisible: boolean; toggle: () => void; }'. TS2339
import React from 'react';
import useModal from './useMyHook';
export const App = () => {
const {isVisible, toggle} = useMyHook(); // Example of using once
const {isVisible2, toggle2} = useMyHook(); // am not able to use it here
const {isVisible3, toggle3} = useMyHook(); // am not able to use it here
return (<div> Hello world! </div>);
};
I am incorrectly assuming that creating a new const var allows the reuse of the hook. What can I do to fix this?
Right now, you're returning an object with two properties: isVisible and toggle.
Either destructure into differently-named variables (verbose; not great):
const {isVisible: isVisible2, toggle: toggle2} = useMyHook();
Or return an array from the hook instead:
return [isVisible, toggle];
and
const [isVisible2, toggle2] = useMyHook();
Because in your custom hook, you return an object with 2 properties: isVisible and toggle.
But you destructured it into isVisible2 and toggle2. You can see, there are no isVisible2 and toggle2 in your return.
So, if you want to use this hook again try to assign it to a new variable name like this:
const {isVisible: isVisible2, toggle: toggle2} = useMyHook();

how to get and send data using reactjs and redux

I have problem when trying to send data through the function action in redux,
my code is below
import React from 'react'
import {connect} from 'react-redux'
import {RetrieveCompany} from '../../folder/action/my.actions'
interface Icampaing{
campaing: my_data
}
// campaing IS WORKING WELL, GET ME ALL MY DATA
const Personal: React.FC<Icampaing> = ({campaing}, props: nay) => {
React.useEffect(()=>{
let pf_id: any = campaing.profile ? campaing.profile.id : 0
let pc_id: any = campaing.profile_ca
// THE PROBLEM IS HERE SHOW ME THE ERROR
// TypeError: props.RetrieveCompany is not a function
props.RetrieveCompany(pf_id, pc_id)
},[campaing])
return(<>
{campaing.all_data} // HERE WHEN LOAD DATA campaing WORKING WELL
</>)
}
const mapStateToProps = (state: any) =>({
campaing: state.campaing
})
const mapActionToProps = {
RetrieveCompany
}
export default connect(mapStateToProps, mapActionToProps)(Personal)
please help me, I think forget something.
best words, and happy new year.....!
You should use mapDispatchToProps instead of mapActionToProps
const mapDispatchToProps = (dispatch) => ({
RetrieveCompany: () => dispatch(RetrieveCompany())
// Important: this could be just
// RetrieveCompany depends on how you define your action.
// For naming, I would use camelCase
});
Because what you need to do here is to dispatch an action so that the store will update its state. Then you would read the data returned by mapStateToProps.
I think RetrieveCompany is not among props in deed. Try to spread the rest of the props if you do not want to explicitly name it:
interface Icampaing {
campaing: my_data
[propName: string]: any
}
const Personal: React.FC<Icampaing> = ({ campaing, ...props }) => {
...
or simply add it explicitly since you use it in the component anyways:
interface Icampaing {
campaing: my_data
RetrieveCompany: (pf_id: number, pc_id: number) => void
}
const Personal: React.FC<Icampaing> = ({ campaing, RetrieveCompany }) => {

How to create a modified useSelector custom hook with TypeScript

I want to create custom hook to remove a lot of boilerplate from reusable code.
My redux setup involves a bunch of combined reducers so getting redux values using useSelector from react-redux entails quite a lot of boilerplate code.
Let's say I have an admin reducer inside my rootReducer. I could get one of it's values as follows:
const MyComponent: FC = () => {
const value = useSelector<IRootReducer, boolean>(
({ admin: { val1 } }) => val1
)
// rest of code
}
I want to create a custom hook useAdminSelector based off the implementation above so that it can be used as follows:
const value = useAdminSelector<boolean>(({ val1 }) => val1)
In the definition of the implementation I'd like to have my IAdminReducer interface implemented too.
Here's my attempt:
export function useApplicantSelector<T>((applicantState: IApplicant): T => (retVal)): T {
return useSelector<IReducer, T>((state) => (state.applicant))
}
But this solution is obviously syntactically incorrect.
How about
export const useAdminSelector = <T>(adminSelector: (adminState: IAdminReducer) => T) => {
return useSelector((state: IRootReducer) => adminSelector(state.admin))
}
and then
const value = useAdminSelector<boolean>(({ booleanVal }) => val1)
If I understand you correctly, I think this should solve your problems:)
You don't need a custom hood. Read more about reselect or similar.
If you don't want any libraries - just write your custom select function
const selectVal1 = ({ admin }: IRootReducer) => admin.val1;
and use it
const val1 = useSelector(selectVal1);
UPDATE
What about this?.
[JS]
const useAdminState = (key) => useSelect((state) => state.admin[key]);
[TS]
const useAdminState = (key: keyof IRootReducer['admin']) => useSelect(({ admin }: IRootReducer) => admin[key]);

Implement useSelector equivalent for React Context?

There's a bunch of articles out there that show how Redux can be replaced with context and hooks (see this one from Kent Dodds, for instance). The basic idea is to make your global state available through a context instead of putting it inside a Redux store. But there's one big problem with that approach: components that subscribe to the context will be rerendered whenever any change happens to the context, regardless of whether or not your component cares about the part of the state that just changed. For functional components, React-redux solves this problem with the useSelector hook. So my question is: can a hook like useSelector be created that would grab a piece of the context instead of the Redux store, would have the same signature as useSelector, and, just like useSelector, would only cause rerenders to the component when the "selected" part of the context has changed?
(note: this discussion on the React Github page suggests that it can't be done)
No, it's not possible. Any time you put a new context value into a provider, all consumers will re-render, even if they only need part of that context value.
That's specifically one of the reasons why we gave up on using context to propagate state updates in React-Redux v6, and switched back to using direct store subscriptions in v7.
There's a community-written React RFC to add selectors to context, but no indication the React team will actually pursue implementing that RFC at all.
As markerikson answers, it is not possible, but you can work around it without using external dependencies and without falling back to doing manual subscriptions.
As a workaround, you can let the component re-render, but skip the VDOM reconciliation by memoizing the returned React element with useMemo.
function Section(props) {
const partOfState = selectPartOfState(useContext(StateContext))
// Memoize the returned node
return useMemo(() => {
return <div>{partOfState}</div>
}, [partOfState])
}
This is because internally, when React diffs 2 versions of virtual DOM nodes, if it encountered the exact same reference, it will skip reconciling that node entirely.
I created a toolkit for managing state using ContextAPI. It provides useSelector (with autocomplete) as well as useDispatch.
The library is available here:
https://www.npmjs.com/package/react-context-toolkit
https://github.com/bergkvist/react-context-toolkit
It uses:
use-context-selector to avoid unneccesary rerenders.
createSlice from #reduxjs/toolkit to make the state more modular and to avoid boilerplate.
I've created this small package, react-use-context-selector, and it just does the job.
I used the same approach as used in Redux's useSelector. It also comes with type declarations and the return type matches the selector function's return type making it suitable for using in TS project.
function MyComponent() {
// This component will re-render only when the `name` within the context object changes.
const name = useContextSelector(context, value => value.name);
return <div>{name}</div>;
}
Here is my take on this problem:
I used the function as child pattern with useMemo to create a generic selector component:
import React, {
useContext,
useReducer,
createContext,
Reducer,
useMemo,
FC,
Dispatch
} from "react";
export function createStore<TState>(
rootReducer: Reducer<TState, any>,
initialState: TState
) {
const store = createContext({
state: initialState,
dispatch: (() => {}) as Dispatch<any>
});
const StoreProvider: FC = ({ children }) => {
const [state, dispatch] = useReducer(rootReducer, initialState);
return (
<store.Provider value={{ state, dispatch }}>{children}</store.Provider>
);
};
const Connect: FC<{
selector: (value: TState) => any;
children: (args: { dispatch: Dispatch<any>; state: any }) => any;
}> = ({ children, selector }) => {
const { state, dispatch } = useContext(store);
const selected = selector(state);
return useMemo(() => children({ state: selected, dispatch }), [
selected,
dispatch,
children
]);
};
return { StoreProvider, Connect };
}
Counter component:
import React, { Dispatch } from "react";
interface CounterProps {
name: string;
count: number;
dispatch: Dispatch<any>;
}
export function Counter({ name, count, dispatch }: CounterProps) {
console.count("rendered Counter " + name);
return (
<div>
<h1>
Counter {name}: {count}
</h1>
<button onClick={() => dispatch("INCREMENT_" + name)}>+</button>
</div>
);
}
Usage:
import React, { Reducer } from "react";
import { Counter } from "./counter";
import { createStore } from "./create-store";
import "./styles.css";
const initial = { counterA: 0, counterB: 0 };
const counterReducer: Reducer<typeof initial, any> = (state, action) => {
switch (action) {
case "INCREMENT_A": {
return { ...state, counterA: state.counterA + 1 };
}
case "INCREMENT_B": {
return { ...state, counterB: state.counterB + 1 };
}
default: {
return state;
}
}
};
const { Connect, StoreProvider } = createStore(counterReducer, initial);
export default function App() {
return (
<StoreProvider>
<div className="App">
<Connect selector={(state) => state.counterA}>
{({ dispatch, state }) => (
<Counter name="A" dispatch={dispatch} count={state} />
)}
</Connect>
<Connect selector={(state) => state.counterB}>
{({ dispatch, state }) => (
<Counter name="B" dispatch={dispatch} count={state} />
)}
</Connect>
</div>
</StoreProvider>
);
}
Working example: CodePen
Solution with external store (Redux or Zustand like approach) with new hook useSyncExternalStore comes with React 18.
For React 18: Define createStore and useStore functions:
import React, { useCallback } from "react";
import { useSyncExternalStore } from "react";
const createStore = (initialState) => {
let state = initialState;
const getState = () => state;
const listeners = new Set();
const setState = (fn) => {
state = fn(state);
listeners.forEach((l) => l());
};
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
return { getState, setState, subscribe };
};
const useStore = (store, selector) =>
useSyncExternalStore(
store.subscribe,
useCallback(() => selector(store.getState()), [store, selector])
);
Now use it :
const store = createStore({ count: 0, text: "hello" });
const Counter = () => {
const count = useStore(store, (state) => state.count);
const inc = () => {
store.setState((prev) => ({ ...prev, count: prev.count + 1 }));
};
return (
<div>
{count} <button onClick={inc}>+1</button>
</div>
);
};
For React 17 and any React version that supports hooks:
Option 1: You may use the external library (maintained by React team)
use-sync-external-store/shim :
import { useSyncExternalStore } from "use-sync-external-store/shim";
Option 2: If you don't want to add new library and don't care about concurency problems:
const createStore = (initialState) => {
let state = initialState;
const getState = () => state;
const listeners = new Set();
const setState = (fn) => {
state = fn(state);
listeners.forEach((l) => l());
}
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
}
return {getState, setState, subscribe}
}
const useStore = (store, selector) => {
const [state, setState] = useState(() => selector(store.getState()));
useEffect(() => {
const callback = () => setState(selector(store.getState()));
const unsubscribe = store.subscribe(callback);
callback();
return unsubscribe;
}, [store, selector]);
return state;
}
Sources:
A conference talk from Daishi Kato from React Conf 2021
A blog post about same conference talk by Chetan Gawai
Simple approach to prevent additional renders with HoC and React.memo:
const withContextProps = (WrappedComponent) => {
const MemoizedComponent = React.memo(WrappedComponent);
return (props) => {
const state = useContext(myContext);
const mySelectedState = state.a.b.c;
return (
<MemoizedComponent
{...props}
mySelectedState={mySelectedState} // inject your state here
/>
);
};
};
withContextProps(MyComponent)
I have made a library, react-context-slices, which can solve what you are looking for. The idea is to break the store or state in slices of state, that is, smaller objects, and create a context for each one. That library which I told you does this, exposes a function createSlice which accepts a reducer, initial state, name of the slice, and a function for creating the actions. You create as slices as you want ('todos', 'counter', etc) and integrate them in a unique interface easily, exposing at the end two custom hooks, useValues and useActions, which can 'attack' all the slices (that is, in your client components you do not use useTodosValues but useValues). The key is that useValues accepts a name of the slice, so would be equivalent to the useSelector from redux. The library use immer as redux does. It's a very tiny library which the key point is how is used, which is explained in the readme file. I have also made a post about it. The library exposes only two functions, createSlice and composeProviders.

Knowing when a map state props update

I have the following app in react and redux start kid
in a component, I am using a series of selector that are related to the same store Items :
const mapStateToProps = (state: RootState) => ({
itemsLoading: ItemsSelectors.getItemsIsLoading(state),
items: ItemsSelectors.getCurrentItemList(state),
fields: ItemsSelectors.getCurrentItemFields(state),
columns: ItemsSelectors.getCurrentItemColumns(state)
})
When the store values changes, I would like to update my component state, by doing some calculation with the data.
I am using the following function
UNSAFE_componentWillUpdate(nextProps) {
const displaybleTable = this.getDisplaybleTable(nextProps);
this.setState({
items : displaybleTable.items,
columns : displaybleTable.columns
})
}
So everytime the store change, I get updated, and I update the component state.
The problem is, since I update the component state, I am looping in this function.
Also, I believe it looks a bit wierd.
IS there a way to know when the store value has updates in the component, so thatr component can do some personal data manipulation ?
Which version of react do you use?
If I understood you correctly and assuming react version 16.8+, you can achiev this by using the useEffect() hook. I assume your component is connected to the store using connect() from 'react-redux'. Then it could look like this:
const MyComponent = (props) => {
useEffect(() => {
const displaybleTable = this.getDisplaybleTable(/* arguments */);
this.setState({
items : displaybleTable.items,
columns : displaybleTable.columns
})
}, [props.items])
const getDisplayableTable = (/* args: any */) => {
return ...
}
...
}
export const MyConnectedComponent = connect(
(state: RootState) => ({
itemsLoading: ItemsSelectors.getItemsIsLoading(state),
items: ItemsSelectors.getCurrentItemList(state),
fields: ItemsSelectors.getCurrentItemFields(state),
columns: ItemsSelectors.getCurrentItemColumns(state)
}),
{
// dispatchProps ...
},
(stateProps: any, dispatchProps: any, ownProps: any) => ({
itemsLoading: stateProps.itemsLoading,
items: stateProps.items,
fields: stateProps.fields,
columns: stateProps.columns
})
)(MyComponent)
The second parameter of useEffect defines when useEffect() calls the first parameter, which is a function. So each time 'items' is updated in the store, the update will trigger useEffect which will run the code and sets the state of your component.
EDIT:
ComponentWillUpdate(nextProps) will not be called if some values in your store changes. ComponentWillUpdate only gets called if the props you pass to your component has changed:
export const SomeOtherComponent = (props: any) => {
return (
<MyComponent prop1={val1} prop2={val2} />
)
}
If val1 and val2 changes this would call ComponentWillUpdate of MyComponent (as far as I know, but I'm not sure).

Resources