Hello I use ngrx and spring boot to display all products. But I can't display one selected product.
My getProductAction et effect work well.
This is the result in Redux:
Some help please
This is my state and initialState:
export interface ProduitState extends EntityState<Produit>{
isLoading: boolean;
selectedProduitId: any;
error: any;
produits: any;
searchQuery: string;
}
enter image description here
export const produitAdapter: EntityAdapter<Produit> = createEntityAdapter<Produit>({
selectId: (produit: Produit) => produit.id,
sortComparer: false,
});
export const produitInitialState: ProduitState = produitAdapter.getInitialState({
isLoading: true,
selectedProduitId: null,
error: null,
produits: [],
searchQuery: ''
});
export const selectedProduitId = (state: ProduitState) => state.selectedProduitId;
export const selectIsLoading = (state: ProduitState) => state.isLoading;
export const selectError = (state: ProduitState) => state.error;
export const selectSearchQuery = (state: ProduitState) => state.searchQuery;
This my action:
export const getProduitAction = createAction('[Produit] Get Produit', props<{produitId: string}>());
const produitPayload = props<{produit: Produit}>();
export const getProduitSuccessAction = createAction('[Produit] Get Produit Success', produitPayload);
This is my reducer:
on(ProduitActions.getProduitAction, (state, {produitId}) => ({
...state, selectedProduitId: undefined, isLoading: true, isLoaded: false, error: null
})),
This is my effect:
getProduit$ = createEffect(() =>
this.actions$.pipe(
ofType(getProduitAction),
exhaustMap(action => {
alert("getProduit Effects "+ action.produitId);
return this.produitService.getProduit(action.produitId)
.pipe(
//tap(res => console.log(res + "TAG EEEEE")),
map((produit, id) => getProduitSuccessAction({produit})),
catchError(err => this.handleError(err))
)
})
)
);
I do this code to get de current selected id:
public produitsState = createFeatureSelector<state.ProduitState>('produit');
private selectors = state.produitAdapter.getSelectors(this.produitsState);
private selectCurrentProduitId = createSelector(this.produitsState, state.selectedProduitId);
private isLoading = createSelector(this.produitsState, state.selectIsLoading);
private error = createSelector(this.produitsState, state.selectError);
private searchQuery = createSelector(this.produitsState, state.selectSearchQuery);
I do this code to get de current selected product but it doesn't work:
getCurrentProductSelected() {
//console.log("ProduitStore: getCurrentProductSelected()");
//console.log(this.store.select(this.selectCurrentProduitId));
alert(this.getProducts());
return combineLatest(
this.getProducts(),
this.store.select(this.selectCurrentProduitId),
(products, selectedId) => selectedId.map(id => {
alert(id +" getCurrentProductSelected");
alert(products +" getCurrentProductSelected");
return products[id];
})
);
}
I try to get the product but I have nothing:
let id = this.route.snapshot.paramMap.get('id');
this.store.dispatchGetProduitAction(id);
this.produit$ = this.store.getCurrentProductSelected();
this.produit$.pipe(
map(produit =>{
alert(produit.nom + " this.produit$");
})
);
Help please. Thanks
It does not look like you set 'selectedProduitId' anywhere. And without that, how would 'getCurrentProductSelected' return anything. If i understand your idea correctly, you need to do something like this:
on(ProduitActions.getProduitAction, (state, {produit}) => ({
...state, selectedProduitId: produit.id
})),
But it's just a blind guess.
Related
i want to moving to #reduxjs/toolkit, actually my app use the following packages:
"#reduxjs/toolkit": "^1.8.3",
"react-redux": "^7.2.2",
"#types/react-redux": "^7.1.20",
actually i use this type of action for my redux store :
export function action<T extends string, P>(type: T, payload?: Partial<P>) {
return { type, ...payload };
}
i change my configure store like this :
const sagaMiddleware = createSagaMiddleware();
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), sagaMiddleware],
});
sagaMiddleware.run(rootSaga);
export default store;
type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
i've created a simple button component like this :
interface FavoriteBtnProps {
className: string;
}
function FavoriteBtn({ className }: FavoriteBtnProps) {
const { windowDisplay } = useAppSelector(getFavoritePlacesState);
const dispatch = useDispatch();
const handleClick = () => {
dispatch(setWindowDisplay(!windowDisplay));
};
return (
<div className={className} onClick={handleClick}>
<FontAwesomeIcon icon={faBookmark} />
</div>
);
}
export default FavoriteBtn;
it work's fine but need to use generic redux dispatch (Dispatch()). the useAppDispatch created don't work with old actions
now i want to add in a parent component of FavoriteBtn
const { leftPanelDisplay } = useAppSelector(getPanelState);
const { windowDisplay } = useAppSelector(getFavoritePlacesState);
console.log(windowDisplay);
const dispatch = useDispatch();
const handleCollapseIcon = () => {
dispatch(
leftPanelDisplay
? panelsActions.leftPanel.collapse()
: panelsActions.leftPanel.uncollapse()
);
// dispatch(setWindowDisplay(!windowDisplay));
};
this code works fine the console log give me the good value, but if i uncomment
dispatch(setWindowDisplay(!windowDisplay));
i have this error :
Uncaught TypeError: Cannot destructure property 'windowDisplay' of
'Object(...)(...)' as it is undefined.
and i don't see favoriteAndHistoric state in the reduxDevTools
only the state created by the toolkit crashes.
here my slice
const favoritePlacesSlice = createSlice({
name: 'favoritePlaces',
initialState,
reducers: {
setWindowDisplay: (state, action: PayloadAction<boolean>) => {
state.windowDisplay = action.payload;
},
setFavorites: (state, action: PayloadAction<FavoriteSearch[]>) => {
state.favoritePlaces = action.payload;
},
},
extraReducers: (builder) => {
// GET ALL
builder.addCase(fetchFavoriteSearchs.pending, (state, action) => {
state.apiStatus = APIStatus.PENDING;
});
builder.addCase(fetchFavoriteSearchs.fulfilled, (state, action) => {
state.apiStatus = APIStatus.FULFILLED;
state.favoritePlaces = action.payload ?? [];
});
// ADD
builder.addCase(addFavoriteSearch.pending, (state, action) => {
state.apiStatus = APIStatus.PENDING;
});
builder.addCase(addFavoriteSearch.fulfilled, (state, action) => {
state.apiStatus = APIStatus.FULFILLED;
state.favoritePlaces = state.favoritePlaces.concat(action.payload);
});
// DELETE
builder.addCase(deleteFavoriteSearch.pending, (state, action) => {
state.apiStatus = APIStatus.PENDING;
});
builder.addCase(deleteFavoriteSearch.fulfilled, (state, action) => {
state.apiStatus = APIStatus.FULFILLED;
state.favoritePlaces = state.favoritePlaces.filter(
(f) => f.idIri !== action.payload
);
});
},
});
export const { setFavorites, setWindowDisplay } = favoritePlacesSlice.actions;
export default favoritePlacesSlice.reducer;
my rootReducer :
import { combineReducers } from 'redux';
...
import favoritePlacesReducer from '../features/favoritePlaces/favoritePlacesSlice';
const rootReducer = combineReducers({
...
favoritePlacesReducer,
});
export default rootReducer;
export type RootState = ReturnType<typeof rootReducer>;
i've allready tryed to reinstall #reduxjs/toolkit and react-redux from scratch but don't fix the problem and more, i have type errors on createAsyncThunk.
why it works on a componant and not on an other?
thx for help
what do i do wrong?
fetchEmail$ = createEffect(() => this.actions$.pipe(
ofType(RDX_EMAIL_CONFIRM_FETCH),
switchMap(ac => axios.post(axiosInstance.post('/api/email-confirm/check-email', {
email: ac.payload.email
}).then(res => {
return {
type: RDX_EMAIL_CONFIRM_FETCH_SUCCESS,
};
}).catch(err => {
return {
type: RDX_EMAIL_CONFIRM_FETCH_ERROR
};
})))
))
would noah like to call some api and return an action based on that?
unfortunatley has the following error
Type 'Observable<unknown>' is not assignable to type 'EffectResult<Action>'.
Type 'Observable<unknown>' is not assignable to type 'Observable<Action>'.
Type 'unknown' is not assignable to type 'Action'
here's my reducer maby the problem occurs here
Is it the actions payload definition?
import { createAction, createReducer, on, props } from '#ngrx/store';
import { tassign } from 'tassign';
export const RDX_EMAIL_CONFIRM_FETCH = 'RDX_EMAIL_CONFIRM_FETCH';
export const RDX_EMAIL_CONFIRM_FETCH_SUCCESS = 'RDX_EMAIL_CONFIRM_FETCH_SUCCESS';
export const RDX_EMAIL_CONFIRM_FETCH_ERROR = 'RDX_EMAIL_CONFIRM_FETCH_ERROR';
export const rdxEmailConfirmFetch = createAction(
RDX_EMAIL_CONFIRM_FETCH,
props<{email: string}>()
);
export const rdxEmailConfirmFetchSuccess = createAction(RDX_EMAIL_CONFIRM_FETCH_SUCCESS);
export const rdxEmailConfirmFetchError = createAction(RDX_EMAIL_CONFIRM_FETCH_ERROR);
const initialState = {
isFetch: false
}
export const emailConfirmReducer = createReducer(
initialState,
on(rdxEmailConfirmFetch, (state) => tassign(state, {
isFetch: true
})),
on(rdxEmailConfirmFetchSuccess, (state) => tassign(state, {
isFetch: false
})),
on(rdxEmailConfirmFetchError, (state) => tassign(state, {
isFetch: false
}))
)
You have to use RxJS of operator to convert it to observable, just like below.
fetchEmail$ = createEffect(() => this.actions$.pipe(
ofType(RDX_EMAIL_CONFIRM_FETCH),
switchMap(ac => axios.post(axiosInstance.post('/api/email-confirm/check-email', {
email: ac.payload.email
}).then(res => {
return of({
type: RDX_EMAIL_CONFIRM_FETCH_SUCCESS,
});
}).catch(err => {
return of({
type: RDX_EMAIL_CONFIRM_FETCH_ERROR
});
})))
))
Here my redux state , the state has dynamic nested object name
const search = {
client :
{ result: [],
selected: null,
isLoading: false,
isSuccess: false,},
[dynamicKey] :
{ result: [],
selected: null,
isLoading: false,
isSuccess: false,},
[dynamicKey2] :
{ result: [],
selected: null,
isLoading: false,
isSuccess: false,}
};
I'm trying to get nested object by dynamic key , here is my selector code :
import { createSelector } from "reselect";
export const searchState = (state) => state.search;
export const selectSearch = (keyRef) =>
createSelector([searchState], (search) => search[keyRef]);
You forgot to ask the question but your code looks fine as it is. In the component you can use useMemo to not needlessly create the selector:
//renamed the selector to create...
export const createSelectSearch = (keyRef) =>
createSelector([searchState], (search) => search[keyRef]);
//compnent example
const MyComponent = ({keyRef}) => {
const selectSearch = React.useMemo(
()=>createSelector(keyRef),//create the selector when keyRef changes
[keyRef]
);
const result = useSelector(selectSearch)
return <jsx />
}
Some more information about this pattern can be found here
I have problem to update state in ngrx reducer for action loadSuccess
State after load action
{
dashboard: {
serverList: null,
isLoading: true,
error: 'zero'
}
}
State after loadSuccess action
{
dashboard: {
isLoading: false,
error: 'zero'
}
}
const t1 = (state, action) => {
return ({...state, serverList: action.payload.servers});
};
const reducer = createReducer(initialState,
on(load, state => ({...state, isLoading: true})),
on(loadSuccess, (state, {servers}) => ({...state, serverList: servers})),
// on(loadSuccess, t1)
);
export function dashboardReducer(state: State | undefined, action: Action) {
return reducer(state, action);
}
when I swap on(loadSuccess.... line with t1 state it works. I actually check what the object in parameter is and fetch value what I want. But why it fails for on(loadSuccess, (state, {servers}) => ({...state, serverList: servers}))?
Actions definition
export const load = createAction('[Dashboard Component] Load');
export const loadSuccess = createAction('[Dashboard Component] LoadSuccess', props<{servers: ServerInfo[]}>());
should you share how you define the loadSuccess action?
looks like you have the payload property and should use it as payload.servers.
2nd Parameter of the given function contains the action, not the payload..
on(loadSuccess, (state, {payload}) => ({...state, serverList: payload.servers})),
I found that problem came from ngrx docs, from the definition of effect.
#Injectable()
export class MovieEffects {
loadMovies$ = createEffect(() => this.actions$.pipe(
ofType('[Movies Page] Load Movies'),
mergeMap(() => this.moviesService.getAll()
.pipe(
map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),
catchError(() => EMPTY)
))
)
);
constructor(
private actions$: Actions,
private moviesService: MoviesService
) {}
}
Action here is defined as { type: '[Movies API] Movies Loaded Success', payload: movies }
After change to use directly defined above action export const loadSuccess = createAction('[Dashboard Component] LoadSuccess', props<{servers: ServerInfo[]}>()); it works
loadSomething$ = createEffect(() => this.actions$.pipe(
ofType(DashboardActions.load),
mergeMap(() => this.service.getServers().pipe(map(servers => Actions.loadSuccess({serverList: servers})))),
catchError(() => of({type: '[Dashboard Component] ServerInfo Loaded Error'}))
)
);
I have the following store
export const featureAdapter: EntityAdapter<IProduct> = createEntityAdapter<IProduct>({
selectId: model => model.id,
});
export interface State extends EntityState<IProduct> {
selectedProduct?: IProduct;
isLoading?: boolean;
error?: any;
}
export const initialState: State = featureAdapter.getInitialState({
selectedProduct: IProduct,
isLoading: false,
error: null
});
I would like that my selected product always point on a Entity and get updates with it. I believe since actions are always creating new object, the link is not possible, I decided to change the selectedProduct from a reference to a simple id.
export const initialState: State = featureAdapter.getInitialState({
selectedProduct: string,
isLoading: false,
error: null
});
but how do i retrieve my entity with the same ID, and get updates on my observable if it is changed ?
I tried
export const getSelectedProduct: MemoizedSelector<object, any> = createSelector(
selectAllEntities,
selectedProduct,
(entities, id) => entities[id]
);
export const selectEntityById = createSelector(
selectAllEntities,
(entities, props) => entities[props.id]
);
my questions are
Is this the only way to select an entity by id ?
this.store$.pipe(
select(ProductStoreSelectors.selectEntityById, { id: product.id }),
map(product => console.log(product))
)
and
this.store$.select(ProductStoreSelectors.getSelectedProduct).subscribe((product: string) => {
console.log(product)
}),
this never trigger when I change my selected product Id
EDIT :
the reducer on select do the following
const SET_SELECTED_PRODUCT = (state: State, action: featureAction.SetSelectedProduct) => {
return {
...state,
selectedProduct: action.payload.product.id
};
};
Try something like this in your selector:
export const selectEntityById = (id: number) => createSelector(
selectAllEntities,
(entities) => entities[id]
);
and your select:
select(ProductStoreSelectors.selectEntityById(id))