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 }) => {
Related
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.
So I'm creating my first ReactJS/redux application and I need a little guidance.
I've created a generic apiFetch<T>(method, params) : Promise<T> function which lives in api/apiClient.ts. (Not a React component, but called indirectly from React components)
Basically every fetchEmployee/fetchSettings/fetchWhatever method in rpc/rpcMethods.ts calls this apiFetch<T>() function.
What I'd like to achieve is a statusbar in my app which shows how many concurrent api calls are active. I therefore created a redux rpcStatusSlice based on this redux example.
Can I make apiFetch<T>() update the slice without passing the UseAppDispatch() result as a parameter from my React components?
If I directly import the store in apiClient.ts and call the state modifying functions from rpcStatusSlice on it I get this error:
Uncaught ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
at Module.default (bundle.js:1444:42)
at Module../src/store/store.ts (bundle.js:1957:67)
at Module.options.factory (bundle.js:90091:31)
at __webpack_require__ (bundle.js:89541:33)
at fn (bundle.js:89762:21)
at Module../src/api/apiClient.ts (bundle.js:206:70)
at Module.options.factory (bundle.js:90091:31)
at __webpack_require__ (bundle.js:89541:33)
at fn (bundle.js:89762:21)
at Module../src/api/rpcMethods.ts (bundle.js:288:68)
apiFetch.ts:
import { store } from "../store/store";
import { incrementByAmount } from "../store/features/rpcStatusSlice";
export function apiFetch<T>(method: string, params: any): Promise<T> {
store.dispatch(incrementByAmount(1));
return fetch(apiUrl, {
method: "POST",
cache: "no-cache",
mode: "cors",
redirect: "follow",
body: JSON.stringify(getApiRequest(method, params)),
})
.then(etc)
./store/features/rpcStatusSlice.ts
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
import { RootState } from '../store';
export interface ActiveRequest{
requestType: string;
}
export interface RpcStatus {
activeRequestsCount: 0;
activeRequests: ActiveRequest[];
}
export interface RpcStatusState {
value: RpcStatus;
status: 'idle' | 'loading' | 'failed';
}
const initialState: RpcStatusState = {
value: {
activeRequestsCount: 0,
activeRequests: []
},
status: 'idle',
};
export const rpcStatusSlice = createSlice({
name: 'rpcstatus',
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value.activeRequestsCount += 1;
},
decrement: (state) => {
state.value.activeRequestsCount -= 1;
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value.activeRequestsCount += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = rpcStatusSlice.actions;
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.rpcstatus.value)`
export const selectCount = (state: RootState) => state.rpcStatus.value.activeRequestsCount;
export default rpcStatusSlice.reducer;
./store/store.ts
import { configureStore, ThunkAction, Action } from '#reduxjs/toolkit';
import rpcStatusReducer from './features/rpcStatusSlice';
export const store = configureStore({
reducer: {
rpcStatus: rpcStatusReducer
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
Can I make apiFetch() update the slice without passing the UseAppDispatch() result as a parameter from my React components?
This sounds like you want apiFetch() to do two things:
Make an HTTP request.
Update redux state.
This violates the separation of concerns principle. Instead, call apiFetch() from a thunk that then updates redux state.
If your StatusBar component is the only consumer of the number of concurrent api calls, I'd consider not even using redux for this. This counter is not necessarily even global state, I'd see it as volatile, local component state for the StatusBar. Therefore, the solution I'm proposing relies only on React. It takes advantage of the pattern of keeping state in the module scope of the apiClient. At runtime, your apiClient module exists as a singleton with its own scope (many modules importing the apiClient will always use the same instance).
This abstracts the task of counting api calls away and lets the other parts of the codebase use apiFetch() without worrying about the counter. No need to adjust your existing thunks etc..
// apiClient.ts
let incrementCounter = (): void => null;
let decrementCounter = (): void => null;
export const onRequestStart = (callback: () => void) => {
incrementCounter = callback;
};
export const onRequestEnd = (callback: () => void) => {
decrementCounter = callback;
};
export const apiFetch = (method, params) => {
incrementCounter();
// do actual api call
return someKindOfPromise.finally(decrementCounter);
};
// StatusBar.tsx (component showing the number of concurrent api calls)
import { onRequestStart, onRequestEnd } from 'apiClient.ts';
const StatusBar = () => {
const [numConcurrentCalls, setNumConcurrentCalls] = useState(0);
useEffect(() => {
// Tell api client to always call the following function when a request starts.
onRequestStart(() => setNumConcurrentCalls(num => num + 1));
// Tell api client to always call the following function when a request ends.
onRequestEnd(() => setNumConcurrentCalls(num => num - 1));
}, [setNumConcurrentApiCalls]);
return (
<div>
<p>Concurrent api calls: {numConcurrentCalls}</p>
</div>
);
};
I am using the Context API to load categories from an API. This data is needed in many components, so it's suitable to use context for this task.
The categories can be expanded in one of the child components, by using a form. I would like to be able to tell useCategoryLoader to reload once a new category gets submitted by one of the child components. What is the best practice in this scenario? I couldn't really find anything on google with the weird setup that I have.
I tried to use a state in CategoryStore, that holds a boolean refresh State which gets passed as Prop to the callback and can be modified by the child components. But this resulted in a ton of requests.
This is my custom hook useCategoryLoader.ts to load the data:
import { useCallback } from 'react'
import useAsyncLoader from '../useAsyncLoader'
import { Category } from '../types'
interface Props {
date: string
}
interface Response {
error?: Error
loading?: boolean
categories?: Array<Category>
}
const useCategoryLoader = (date : Props): Response => {
const { data: categories, error, loading } = useAsyncLoader(
// #ts-ignore
useCallback(() => {
return *APICALL with modified date*.then(data => data)
}, [date])
)
return {
error,
loading,
categories
}
}
export default useCategoryLoader
As you can see I am using useCallback to modify the API call when input changes. useAsyncloaderis basically a useEffect API call.
Now this is categoryContext.tsx:
import React, { createContext, FC } from 'react'
import { useCategoryLoader } from '../api'
import { Category } from '../types'
// ================================================================================================
const defaultCategories: Array<Category> = []
export const CategoryContext = createContext({
loading: false,
categories: defaultCategories
})
// ================================================================================================
const CategoryStore: FC = ({ children }) => {
const { loading, categories } = useCategoryLoader({date})
return (
<CategoryContext.Provider
value={{
loading,
topics
}}
>
{children}
</CategoryContext.Provider>
)
}
export default CategoryStore
I'm not sure where the variable date comes from in CategoryStore. I'm assuming that this is an incomplete attempt to force refreshes based on a timestamp? So let's complete it.
We'll add a reload property to the context.
export const CategoryContext = createContext({
loading: false,
categories: defaultCategories,
reload: () => {},
})
We'll add a state which stores a date timestamp to the CategoryStore and create a reload function which sets the date to the current timestamp, which should cause the loader to refresh its data.
const CategoryStore: FC = ({ children }) => {
const [date, setDate] = useState(Date.now().toString());
const { loading = true, categories = [] } = useCategoryLoader({ date });
const reload = useCallback(() => setDate(Date.now.toString()), []);
return (
<CategoryContext.Provider
value={{
loading,
categories,
reload
}}
>
{children}
</CategoryContext.Provider>
)
}
I think that should work. The part that I am most iffy about is how to properly memoize a function that depends on Date.now().
I'm starting with React and TypeScript at the same time and I across a problem while implementing some basic authentication in my application. I've been using Ryan Chenkie's Orbit App and his course on React security as an example to start from.
Right now I'm stuck with compiler complaining about TS2722 error (Cannot invoke object which is possibly undefined) in SignIn.tsx. My suspicion is that all I have to do is to set proper types on all the data structures and function calls, but how and where to set them, somewhat alludes me. So, here's the code:
App.tsx: Nothing fancy here, just an App wrapped in context provider.
import { AuthContext, authData } from "./AuthContext"
const defAuth:authData = {
userId: 0,
name: '',
token: '',
expiresAt: ''
}
const App = () => {
return (
<AuthContext.Provider value={{ authData:defAuth }}>
<Main />
</AuthContext.Provider>
);
}
AuthContext.tsx: When calling createContext() I tried with various default parameters, the general idea is that I could call authContext.setState() and pass the data to it. I am using Partial prefix so that I don't have to pass the setState() to the Provider element.
export interface authData {
userId: number
name: string
token: string
expiresAt: string
}
interface IAuthContext {
authData: authData,
setState: (authInfo:authData) => void
}
const AuthContext = createContext<Partial<IAuthContext>>(undefined!)
const { Provider } = AuthContext
const AuthProvider: React.FC<{}> = (props) => {
const [authState, setAuthState] = useState<authData>()
const setAuthInfo = (data:authData) => {
console.log('Called setAuthInfo')
setAuthState({
userId: data!.userId,
name: data!.name,
token: data!.token,
expiresAt: data!.expiresAt
})
}
return (
<Provider
value={{
authData: authState,
setState: (authInfo:authData) => setAuthInfo(authInfo)
}} {...props}
/>
)
}
export { AuthContext, AuthProvider }
SignIn.tsx: This is again, just a basic sign in component with a form and an onSubmit handler. This is all working as it should until I add the authContext to it. I included only relevant code.
interface loginType extends Record<string, any>{
email: string,
password: string,
remember: boolean
}
const SignIn = () => {
const authContext = useContext(AuthContext)
const { register, handleSubmit, errors } = useForm<loginType>()
const onSubmit = async (data:loginType) => {
const ret = await apiFetch.post('process_login/', formData )
console.log(ret.data)
console.log('Printing context')
authContext.setState(ret.data)
console.log(authContext)
}
/* ... ... */
}
As mentioned before, compiler complains in SignIn.tsx at authContext.setState(ret.data) telling me that it might be undefined. I tried calling createContext() with various parameters, trying to pass it some defaults which would tell the compiler where that setState() will be defined later on in the runtime. I tried calling setState in a few different ways, but nothing really worked.
This is something that clearly works in plain JSX and I'd really like to find a way to make it work in TSX.
Here's what you need to do:
First in App.tsx, you have to use AuthProvider instead of AuthContext.Provider. This way you get rid of the value property.
<AuthProvider>
<Main />
</AuthProvider>
Then, in AuthContext.tsx there's no need to use Partial prefix when creating context. So, a little tweak to the IAuthContext and then pass some default data when creating context.
interface IAuthContext {
authData: authData | undefined,
setState: (authInfo:authData) => void
}
const AuthContext = createContext<IAuthContext>( {
authData: defaultAuthData,
setState: () => {}
})
Now you can call authContext.setState(), passing data as authData type.
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.