Do not subscribe to actions ngxs - redux

I have an state selector and action as below
#Selector()
public static employees(state: EmployeeSearchStateModel) {
return state.employees;
}
#Action(FetchEmployees)
getEmployees({ getState, setState }: StateContext<EmployeeSearchStateModel>) {
const state = getState();
this.employeeService.getEmployees().pipe(tap((res: EmployeeSearch) => {
setState({
...state,
loading: false,
employees: res.userData
});
}));
}
Now, in a component, i want to use it with async pipe, but it does not work. Component declarations are as below:
#Select(ManageEmployeeSearchState.employees) employees$: Observable<EmployeeI[]>;
ngOnInit(){
this.store.dispatch(new FetchEmployees());
}
In HTML when i try to print <pre>{{ employees$ | async | json }}</pre>, it does not work.. i can not see backend API called.
However, if i replace pipe and tap in Action with subscribe, everything works very well. But i do not want to subscribe?
Any leads please?

If you are using pipe operators in the state rather than calling subscribe, you need to return that Observable to NGXS so that the framework will subscribe to it for you:
#Action(FetchEmployees)
getEmployees({ getState, setState }: StateContext<EmployeeSearchStateModel>) {
const state = getState();
return this.employeeService.getEmployees().pipe(tap((res: EmployeeSearch) => {
setState({
...state,
loading: false,
employees: res.userData
});
}));
}

Related

how do I migrate from redux to redux toolkit

I managed to write reducer using createSlice but the action seems to be confusing.
My old reducer :
function listPeopleReducer(state = {
getPeople:{}
}, action){
switch (action.type) {
case D.LIST_PEOPLE: {
return {
...state
, getPeople:action.payload
}
}
default:{}
}
return state
}
By using createSlice from the redux toolkit, I migrated the reducer to this,
const listPeopleReducer = createSlice({
initialState:{getPeople:{}},
name:"listPeople",
reducers:{
listPeople(state,action){
return {
...state,
getPeople : action.payload
}
}
}
})
My old action, makes an api call inside it, with the help of a helper function makeApiRequest (which takes in parameters and returns the response of the api),
export function listPeople(config: any) {
return function (dispatch: any) {
makeApiRequest(config)
.then((resp) => {
dispatch({
type : D.LIST_PEOPLE,
payload : resp.data
})
})
.catch((error) => {
dispatch({
type : D.LIST_PEOPLE,
payload : error
})
})
}
}
With reduxtool kit, we could do something like,
const listPeople = listPeopleReducer.actions.listPeople;
But, how will I write my custom action that contains the helper function makeApiRequest ?
i.e The old Action should be migrated to reduxtoolkit type.
It's definitely tricky when migrating, since there are some major conceptual changes that you must eventually wrap your head around. I had to do it a couple of times before it clicked.
First, when you are creating const listPeopleReducer with createSlice(), that is not actually what you are creating. A slice is a higher level object that can generate action creators and action types for you, and allows you to export reducers and actions FROM it.
Here are the changes I would make to your code:
const peopleSlice = createSlice({
initialState:{getPeople:{}},
name:"people",
reducers:{
listPeople(state,action){
// uses immer under the hood so you can
// safely mutate state here
state.getPeople = action.payload
}
},
extraReducers:
// each thunk you create with `createAsyncThunk()` will
// automatically have: pending/fulfilled/rejected action types
// and you can listen for them here
builder =>
builder.addCase(listPeople.pending, (state,action) => {
// e.g. state.isFetching = true
})
builder.addCase(listPeople.fulfilled, (state,action) => {
// e.g. state.isFetching = false
// result will be in action.payload
})
builder.addCase(listPeople.rejected, (state,action) => {
// e.g. state.isFetching = false
// error will be in action.payload
})
}
})
Then, outside of your slice definition, you can create actions by using createAsyncThunk(), and do like:
export const listPeople = createAsyncThunk(
`people/list`,
async (config, thunkAPI) => {
try {
return makeApiRequest(config)
} catch(error) {
return thunkAPI.rejectWithError(error)
// thunkAPI has access to state and includes
// helper functions like this one
}
}
}
The "Modern Redux with Redux Toolkit" page in the Redux Fundamentals docs tutorial shows how to migrate from hand-written Redux logic to Redux Toolkit.
Your makeApiRequest function would likely be used with Redux Toolkit's createAsyncThunk, except that you should return the result and let createAsyncThunk dispatch the right actions instead of dispatching actions yourself.

Difficulty implementing redux thunk

This is my first time working with redux hooks and I keep receiving the error: "Error: Actions must be plain objects. Use custom middleware for async actions."
I have added the middleware thunk. Following the other peoples questions, I am not sure where I am making the mistake. I'm looking for an explanation on what I am doing wrong and what I should be reading in order to fix it.
Actions:
export const fetchNewsData = () => {
return (dispatch) => {
axios.get('http://localhost:3001/getnews')
.then(response => {
console.log(response.data);
const data = response.data;
dispatch(loadNews(data));
})
.catch(error => {
console.log(error);
dispatch(errorOnNews(error));
});
}
}
export const loadNews = (fetchedData) => {
return {
type: LOAD_NEWS,
payload: fetchedData
}
}
export const errorOnNews = (errorMessage) => {
return {
type: ERROR_ON_NEWS,
payload: errorMessage
}
}
Reducer:
const initialState = {
fetched: false,
data: [],
input: '',
filtered: [],
error: ''
}
const newsReducer = (state = initialState, action) => {
switch(action.type) {
case LOAD_NEWS:
return {
...state,
fetched: true,
data: action.payload
}
case FILTER_NEWS:
return {
...state
}
case ERROR_ON_NEWS:
return {
...state,
error: action.payload
}
default: return state;
}
}
Store:
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import rootReducer from './rootReducer';
const store = createStore(rootReducer, applyMiddleware(thunk));
Component:
const fetch = useDispatch(fetchNewsData());
useEffect(() => {
if(hasFetched){
// work on true condition
} else {
fetch(); // fails on this line.
}
}, []);
useDispatch does not work like this, as it ignores all arguments and just returns you a dispatch function. So you have called dispatch() there, which essentially equals dispatch(undefined) - and the store doesn't know what to make of that action.
Do this instead:
const dispatch = useDispatch();
useEffect(() => {
if(hasFetched){
// work on true condition
} else {
dispatch(fetchNewsData()); // fails on this line.
}
}, []);
Also, generally you are writing a very outdated style of redux here that we do not really recommend to learn or use in new applications any more.
You might have been following an outdated tutorial - as this style requires you to write multiple times the necessary code and is much more error prone.
For up-to-date tutorials featuring modern redux with the official redux toolkit please see the official redux tutorials
Was following a tutorial and creating additional work that was unnessessary. Answer is to:
Cut: const fetch = useDispatch(fetchNewsData());
Change: fetch(); to fetchNewsData();
In this case, I am calling a handling function that will execute the dispatches when required.

BotFramework WebChat - Sample #14 - Middleware for both Redux stores?

I will preface this question by mentioning that I am new to both React and Redux...
I am working with example #14 from the BotFramework-WebChat samples.
https://github.com/microsoft/BotFramework-WebChat/tree/master/samples/14.customization-piping-to-redux
In addition, to piping the Redux action activities to change the UI, I would also like to include middleware that listens for
'DIRECT_LINE/CONNECT_FULFILLED' and then dispatches 'WEB_CHAT/SEND_EVENT' with the 'webchat/join' payload so that I can display a welcome message.
I tried just modifying dispatchIncomingActivityMiddleware.js to look like the following:
export default function (dispatch) {
return () => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const { activity } = action.payload;
if (
activity.type === 'event'
&& activity.from.role === 'bot'
&& activity.name === 'redux action'
) {
dispatch(activity.value);
}
} else if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language
}
}
});
}
return next(action);
};
}
Needless to say, it doesn't work. I think this is because now all of the actions are being dispatched to the second app Redux store and not the Web Chat Redux store. My question is, how do I make it do both? Is there a way to dispatch certain actions to the app Redux store and other actions to the Web Chat store? Is there another way to achieve this?
An object with two properties - dispatch and getState - gets passed to the store middleware. You should either access the dispatch property from the incoming object or deconstruct it in the function header. Try:
export default function ({ dispatch }) {
return ....
}
Also, the welcome message behavior in Web Chat has slightly changed. Take a look at this for more details.
#tdurnford thanks for your response. I was able to finally get this to work by adding the following to WebChat.js:
constructor(props) {
super(props);
this.store = createStore(
{},
({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language
}
}
});
}
return next(action);
},
dispatchIncomingActivityMiddleware(props.appDispatch)
);
this.state = {};
}
Now I am able to have actions dispatched to both the Web Chat redux store and the second custom redux store. Yay! The question I still have is, can you explain the difference between {dispatch} and props.appDispatch in this example? The latter seems to be passed through from the original declaration in index.js:
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('root')
);
but what is {dispatch} referring to exactly in WebChat.js?

combine two redux reducers

I have a use case like this:
eventListReducer: will get a list of events based on date range
eventDetailReducer: will get the event details based on one event id
I know how to do the two above, my question:
When my page loads initially, I will get a list of events based on default date range and load the first event details, I can certainly create an
EventListAndDetailReducer to duplicate eventListReducer and eventDetailReducer. Is there any better way I can reuse the logic?
What I want to achieve is to have another action, that will first call getEvents and update the eventLists state, and then grab the first event and call setEvent and update the eventDetail state.
This is my eventDetailReducer:
const initialState = {
eventDetails: "",
}
const eventReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_EVENT":
state = {
...state,
eventDetails: action.payload
};
break;
}
return state;
}
export default eventReducer;
This is my eventsReducer:
const initialState = {
eventsList: [],
}
//getEventsReducer
const getEventsReducer = (state = initialState, action) => {
switch (action.type) {
case "GET_EVENTS":
state = {
...state,
eventList: ["Joe", "Tom", "Marry"] //assuming this from some other endpoint
};
break;
}
return state;
}
export default getEventsReducer;
What about using EventListAndDetailReducer?
const initialState = {
eventsList: [],
eventDetails: ""
}
export function eventListAndDetailReducer(state, action) {
switch(action.type) {
case GET_EVENTS:
return {...state, eventList: eventsReducer(state.eventsList, action)}
case "SET_EVENT":
return {...state, eventDetails: eventDetailsReducer(state.eventDetails, action)}
default:
return state
}
}
and then somewhen start using combineReducers?
Why not just have the eventDetails reducer also update on the GET_EVENTS action?
const eventReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_EVENT":
state = {
...state,
eventDetails: action.payload
};
break;
case "GET_EVENTS":
state = {
...state,
eventDetails: action.payload[0] // assuming payload is an array
};
break;
}
return state;
}
Remember, all reducers receive all actions, so it does not need to be a 1-1 mapping.
What I understand from you question is that you want another action to do both actions sequentially and be dependent on each. I assume you have some middle ware such as redux-thunk that allows actions to be more than plaIn functions!
export function combinedAction() {
return (dispatch, getState) => {
// Write fetch() request to get events list from anywhere.
// Following should be within .then() if you're using fetch.
// Here events are just hardcoded in reducer!
dispatch(return { type: GET_EVENTS, payload: events }).then( () => {
let event = getState().eventsList[0]
dispatch(return { type: SET_EVENT, payload: event })
})
};
}
This will fire up GET_EVENTS action first and it'll set events array in state.eventsList. Then next action just uses this state information to dispatch next action SET_EVENT. Refer here to learn about chaining actions. How to chain async actions?

Redux Observable: How to use action.payload in latter part of chain?

I've ran into this issue quite a few times where I want to access action.payload further down the chain. But by then, the argument passed to mergeMap has already changed to something else.
Given my action looks like this:
{
type: BUY_GEMS,
payload: { value: 123, productIdentifier: "ABC123" }
}
And this epic:
function purchaseGems(action$, store) {
return action$
.ofType(BUY_GEMS)
.mergeMap(action => {
const { productIdentifier } = action.payload; // <-------- works because it's the first mergeMap in this sequence
return Observable.fromPromise(
// Some promise call
).catch(error => Observable.of(buyGemsRejected(error)));
})
.mergeMap(action => {
const { value } = action.payload; // <----------- doesn't work because "action" is now just the response of the Promise above.
...
});
}
How would I do this?
This trick is to just place your second mergeMap inside the closure where the action is available. In fact, even if you didn't need access to it I generally recommend this pattern in redux-observable whereby you isolate your Observable chains inside your single top-level merging strategy operator (mergeMap, switchMap, etc) because it makes future refactoring like this easier as well as easier error isolation (if added).
function purchaseGems(action$, store) {
return action$
.ofType(BUY_GEMS)
.mergeMap(action => {
const { productIdentifier } = action.payload;
return Observable.fromPromise(somePromise)
.catch(error => Observable.of(buyGemsRejected(error)))
.mergeMap(response => {
const { value } = action.payload;
// ...
});
});
}
Your example contained Observable.fromPromise() which I assume is just pseudo code, so I followed suit with Observable.fromPromise(somePromise) for more clarity for other readers.

Resources