I am using ngrx in an Angular 6 app and I can successfully load data from a database and display it on the screen. However, when I navigate to a new page in the app and then navigate back, the state is not preserved and it appears the #Effect is called again and loads the data from the database again. Here is my code:
Effect
export class ProductEffects {
constructor(private productService: ProductService, private actions$: Actions) { }
#Effect()
loadProducts$: Observable<Action> = this.actions$.pipe(
ofType(productActions.LOAD_ACTION),
switchMap(action =>
this.productsService.getProductDetails().pipe(
map(product => new productActions.LoadSuccess(product)),
catchError(err => of(new productActions.LoadError(err)))
)
)
);
Reducer
export interface ProductState {
product: Product;
}
const initialState: ProductState = {
product: {}
};
export function reducer(state = initialState, action: Action) {
switch (action.type) {
case SET_PRODUCT:
return { ...state, product: (action as SetProductAction).payload };
case LOAD_SUCCESS:
return { ...state, product: (action as LoadSuccess).payload, error: '' };
default: return state;
}
}
Actions
export const SET_PRODUCT = '[Product] Set Product';
export const LOAD = '[Product] Load';
export const LOAD_SUCCESS = '[Product] Load Success';
export const LOAD_ERROR = '[Product] Load Error';
export class SetProductAction implements Action {
readonly type = SET_PRODUCT;
constructor(public payload: Product) { }
}
export class Load implements Action {
readonly type = LOAD;
}
export class LoadSuccess implements Action {
readonly type = LOAD_SUCCESS;
constructor(public payload: Product) { }
}
export type ProductActions = SetProduct | Load | LoadSuccess;
Store
export interface State extends fromRoot.AppState {
product: fromProduct.ProductState;
}
// selectors
const getProductState = createFeatureSelector<fromProduct.ProductState>('product');
export const getProduct = createSelector(
getProductState,
state => state.product
}
Component
products$: Observable<Product>;
constructor(private store: Store<fromProduct.State>) { }
ngOnInit() {
this.store.dispatch(new Load());
this.products$ = this.store.pipe(select(fromProduct.getProduct));
}
Your ngrx implementation seems all ok to me. What you describe is an absolutely normal behavior. As soon as you navigate away from a page, the component destroyed and the new one is created, i.e. the ngOnInit is called. If you put the Loading logic in the ngOnInit, the state is loaded everytime you navigate to this page.
Related
Building state with ngxs in angular
//Component.ts
ngOnInit(): void {
const dataInsdeStore = this.store.selectSnapshot(MarketState);
if(!dataInsdeStore.loaded){
const category = this.marketplaceService.getMarketplaceCategories().subscribe(cat => {
const item = this.marketplaceService.getMarketplaceItems().subscribe(i => {
const obj = {
categories: cat,
items: i,
loaded: true
}
this.store.dispatch(new SetMarketAction(obj))
})
})
}
const test = this.store.selectSnapshot(MarketState);
this.store.dispatch(new GetMarketStateAction())
}
Upon checking wether the state is empty or not, and dispatching SetMarketAction I get empty object anyways
//Market.actions.ts
import { MarketStateInterface } from "../interfaces/interfaces";
class SetMarketAction {
static readonly type = '[MARKET] Set';
constructor(public marketState : MarketStateInterface){}
}
class GetMarketStateAction {
static readonly type = '[MARKET] Get';
}
export { SetMarketAction, GetMarketStateAction };
//Market.state.ts
import { Action, State, StateContext } from '#ngxs/store';
import { MarketStateInterface } from '../interfaces/interfaces';
import { Injectable } from '#angular/core';
import { GetMarketStateAction, SetMarketAction } from './market.actions';
#State<MarketStateInterface>({
name: 'market',
defaults: {
categories: [],
items: [],
loaded: false,
},
})
#Injectable()
export class MarketState {
constructor() {}
#Action(GetMarketStateAction)
getMarket(ctx: StateContext<MarketStateInterface>) {
const state = ctx.getState();
console.log(state);
return state;
}
#Action(SetMarketAction)
setMarket(ctx: StateContext<MarketStateInterface>, action: SetMarketAction) {
ctx.setState(action.marketState);
}
}
Every time I conosle.log the state at any given time I get the empty array, the interface of the state is correct
An unknown error is showing with ngrx and angular. When i use store.select of ngrx in my component with state variable. than it is giving an unknown error.
component file
#Component({
selector: 'app-all-user',
templateUrl: './all-user.component.html',
styleUrls: ['./all-user.component.css']
})
export class AllUserComponent implements OnInit {
constructor(private userService: UsersService, private readonly store: Store) { }
users: Users[] = []
isLoading:boolean = false
isLoaded: boolean = false
p: number = 1;
ngOnInit() {
this.getAllUsers()
}
getAllUsers(){
this.store.dispatch(new UserListAction())
this.userService.getAllUser().subscribe((res:any) => {
this.store.dispatch(new UserListCreatorSuccess(res.users))
})
this.store.select(getUserStateUser).subscribe((res:any) => {
this.users = res.users
})
}
}
Index reducer
export interface RootReducerState {
usersstate: fromUser.userReducerState
}
export const RootReducer: ActionReducerMap<RootReducerState> = {
usersstate: fromUser.userReducer
};
// get user state
export const getUserState = (state:fromUser.userReducerState) => state
export const getUserStateLoaded = createSelector(getUserState, (state:fromUser.userReducerState) => state.loaded)
export const getUserStateLoading = createSelector(getUserState, (state:fromUser.userReducerState) => state.loading)
export const getUserStateUser = createSelector(getUserState, (state:fromUser.userReducerState) => state.users)
export const metaReducers: MetaReducer<RootReducerState>[] = !environment.production ? [] : [];
It is working fine with angular 10 but on working with angular 14. It is giving issue of overload.
Please have a look at the screenshot
getUserState selector is not correct. it should be
export const getUserState = createFeatureSelector<fromUser.userReducerState>('usersstate')
first questioner here!
I'm new to React and find it confusing to manage state with redux. From the redux-logger output, it seems that I am successfully changing the redux state regarding a user sign-in but I don't really know how to set it to props, and as such, I'm getting an undefined value for currentUser (which is the prop I want to manage across all my pages). I'm using both withRouter and Redux in an effort to pass user properties to app.js.
It starts with an API call to the backend to see if the user can login, if success then returns an object {isAdmin: "", uId: ""}.
import React from "react";
import { withRouter } from "react-router-dom";
import { setCurrentUser } from "../../redux/user/user-actions";
import { connect } from "react-redux";
// sign-in.jsx
class Login extends React.Component {
constructor(props) {
super(props);
}
onSubmitClick = async (e) => {
e.preventDefault();
fetch("/api/login", {
method: "post",
body: JSON.stringify({
email: "",
password: "",
}),
})
.then((res) => res.json())
.then((user) => {
if (user.error) {
this.setState({ error: user.error });
} else {
// Set the user in redux too:
this.props.dispatch(setCurrentUser(user));
// Redirect to main page after login
this.props.history.push({
pathname: "/",
search: "?uid=" + user.key + "?admin=" + user.admin,
state: { userId: user.key, isAdmin: user.admin },
});
}
});
};
render() {
return (...)
}
const mapStateToProps = ({ user }) => ({
currentUser: user.currentUser,
});
export default connect(mapStateToProps)(withRouter(Login));
The line with code: this.props.dispatch(setCurrentUser(user)); successfully changed the state but not the props value.
Here is the redux stuff:
// user-actions.js --------------------------------------------------------------------------------------
export const setCurrentUser = (user) => ({
type: "SET_CURRENT_USER",
payload: user,
});
// user-reducer.js --------------------------------------------------------------------------------------
// The initial state is basically a null user (ID)
const initialState = {
user: null,
};
/*
This is essentially a function that takes the current state
and action as an argument and returns a new state result.
i.e. (state, action) => newState
*/
const userReducer = (state = initialState, action) => {
// Conditional for the current action type
if (action.type.localeCompare("SET_CURRENT_USER") === 0) {
// Return a new state object
return {
// Which has the existing data but also..
...state,
// The new user object (just an ID at this point)
user: action.payload,
};
} else {
// Otherwise we return the state unchanged
// (usually when the reducer doesnt pick up the certain action)
return state;
}
};
export default userReducer;
// store.js --------------------------------------------------------------------------------------
import { createStore, applyMiddleware } from "redux";
/*
Useful for debugging redux --> logger
Is a logger middleware that console.logs the actions fired and change of state
*/
import logger from "redux-logger";
import rootReducer from "./root-reducer";
const middlewares = [logger];
const store = createStore(rootReducer, applyMiddleware(...middlewares));
export default store;
// root-reducer.js --------------------------------------------------------------------------------------
import { combineReducers } from "redux";
import userReducer from "./user/user-reducer";
export default combineReducers({
user: userReducer,
});
And finally, the App.js relevant code
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
...props,
u_id: null,
};
}
unsubscribeFromAuth = null;
componentDidMount() {
const { setCurrentUser } = this.props;[enter image description here][1]
const userState = this.props.location;
console.log(this.props);
// Make sure that state for a user isnt undefined
if (userState.state) {
this.unsubscribeFromAuth = true;
const user = userState.state.userId;
this.props.dispatch(setCurrentUser(user));
}
console.log(this.props);
}
componentWillUnmount() {
this.unsubscribeFromAuth = false;
}
render() {
return (...)
}
}
const mapStateToProps = (state) => ({
currentUser: state.currentUser,
});
//Access the state and dispatch function from our store
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
dispatch,
});
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(App));
Console output with redux-logger:
https://i.stack.imgur.com/r9JyV.png
As you can see, currentUser is undefined but all props in the location are there, I'm probably making some really dumb mistake when setting currentUser with the setCurrentUser action, both in the login and then again in the componentDidMount in the app.jsx
I'll add more detail upon request
Any help would be appreciated GREATLY! :)
You are saving the user in redux under user but you are trying to access it in the mapStateToPRops via currentUser:
const mapStateToProps = (state) => ({ currentUser: state.currentUser, });
Change it to const mapStateToProps = (state) => ({ currentUser: state.user, });
and it should work.
Also this:
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
dispatch,
});
is equivalente to:
const mapDispatchToProps = ({
setCurrentUser
});
https://react-redux.js.org/using-react-redux/connect-mapdispatch#defining-mapdispatchtoprops-as-an-object
I'm currently doing a setup of an ngrx app. One of the goal is to use ngrx to train myself even if the application is too small.
It's a small project for beer inventory management.
I've a component in which I want to display my beer list:
export class BeerListComponent implements OnInit {
availableBeers$: Beer[];
constructor(private breadcrumbService: BreadcrumbService,
private beerService: BeersService,
private store: Store<fromBeer.BeerState>) {
this.breadcrumbService.setItems([
{ label: 'Beers' },
{ label: 'List' }
]);
}
ngOnInit() {
this.store.select(fromBeer.getAvailableBeers).subscribe((beers: Beer[]) => {
console.log(beers);
this.availableBeers$ = beers;
})
console.log('fetching beers');
this.beerService.fetchBeersList();
}
}
my beer service does the following:
export class BeersService {
private firebaseSubscription: Subscription[] = [];
constructor(
private db: AngularFirestore,
private store: Store<fromBeers.BeerState>) { }
fetchBeersList() {
this.firebaseSubscription.push(this.db
.collection('beers')
.valueChanges()
.subscribe((beers: Beer[]) => {
console.log("Received a firebase change");
console.log(beers);
this.store.dispatch(new SetAvailableBeers(beers));
}, error => {
console.log(error);
}));
}
}
and here are my reducer/actions:
Actions
export enum BeersActionTypes {
SET_AVAILABLE_BEERS = '[Beers] SET_AVAILABLE_BEERS'
};
export class SetAvailableBeers implements Action {
readonly type = BeersActionTypes.SET_AVAILABLE_BEERS;
constructor(public payload: Beer[]) {
console.log(payload);
}
}
export type BeersActions
= SetAvailableBeers;
Reducer
export interface BeerState {
availableBeers: Beer[];
};
const initialState: BeerState = {
availableBeers: []
};
export function beersReducer(state = initialState, action: BeersActions): BeerState {
switch (action.type) {
case BeersActionTypes.SET_AVAILABLE_BEERS: {
console.log("Setting available beerse")
return {
...state,
availableBeers: action.payload
};
}
default: {
return state;
}
}
}
export const getAvailableBeers = (state: BeerState) => state.availableBeers;
What I cannot understand:
I receive beers from firebase, but my component never gets the update. If I check the chrome dev tools, I only get one this undefined but this doesn't get updated after.
Here are my logs:
I feel I did not register correctly to my ngrx state, but I cannot figure out what I did wrong?
I think I found my error!
I was subscribing directly to the beerReducers.getAvailableBeers and not the one of my app.reducer(which contains the CreateSelector and stuff)
Have problem with state in my component.
I'm trying to get status from my reducer but state is empty just getting undefined
Here is my actionCreator
export function checkLogin() {
return function(dispatch){
return sessionApi.authCheck().then(response => {
dispatch(authSuccess(true));
}).catch(error => {
throw(error)
})
}
}
My reducer
export const authStatus = (state = {}, action) => {
switch(action.type){
case AUTH_FALSE:
return{
status: action.status
}
case AUTH_TRUE:
return {
...state,
status: action.status
};
default:
return state;
}
};
And here is my component where i'm trying to get state
const mapStateToProps = (state) => {
return {
status: state.status
}
};
const mapDispatchToProps = (dispatch:any) => {
const changeLanguage = (lang:string) => dispatch(setLocale(lang));
const checkAuth = () => dispatch(checkLogin());
return { changeLanguage, checkAuth }
};
#connect(mapStateToProps, mapDispatchToProps)
I need to get status from the state
Component
import * as React from "react";
import Navigation from './components/navigation';
import { connect } from 'react-redux';
import { setLocale } from 'react-redux-i18n';
import cookie from 'react-cookie';
import {checkLogin} from "./redux/actions/sessionActions";
class App extends React.Component<any, any> {
constructor(props:any) {
super(props);
this.state = {
path: this.props.location.pathname
};
}
componentDidMount(){
this.props.checkAuth();
this.props.changeLanguage(cookie.load('lang'));
}
componentWillUpdate(){
}
render() {
return (
<div>
<Navigation path={this.state.path} />
{this.props.children}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
status: state.status
}
};
const mapDispatchToProps = (dispatch:any) => {
const changeLanguage = (lang:string) => dispatch(setLocale(lang));
const checkAuth = () => dispatch(checkLogin());
return { changeLanguage, checkAuth }
};
#connect(mapStateToProps, mapDispatchToProps)
export class Myapp
extends App {}
You cannot access props that are asynchronous inside of the constructor. As the constructor will be executed only once, when you instantiate your component. When you instantiate your component your asynchronous call has not responded yet, therefore this.props.status is undefined.
You could use componentWillReceiveProps from React lifecycle methods for example:
componentWillReceiveProps(nextProps) {
console.log(nextProps.status);
}
This method will be executed everytime a prop connected, or passed, to the component will change.
You could also use this.props.status inside of the render as this one is also executed everytime a prop changed.
For a better understanding of react lifecycle you could have the look at the different methods available, here : https://facebook.github.io/react/docs/react-component.html