NGRX action is dispatched but effect is not firing - javascript

I'm having an issue where my NGRX effect isn't firing. Here are my actions, reducers, effects, and component code.
Here's the snippet of code from my effects, I've replaced the actual name of entities and services with just placeholders.
// entity.effects.ts
createEntity$: Observable<Action> = this.actions$.pipe(
ofType<CreateEntity>(EntityActionTypes.CreateEntity),
map(action => {
console.log("here");
return action.payload;
}),
mergeMap(data => {
return this.service.createEntity(data).pipe(
map(data => new CreateEntitySuccess(data)),
catchError(error => of(new CreateEntityError(error)))
);
})
);
entity.actions.ts
import { Action } from "#ngrx/store";
export enum EntityActionTypes {
CreateEntity = "[Entity API] Create Entity",
CreateEntitySuccess = "[Entity] Create Entity Success",
CreateEntityError = "[Entity] Create Entity Error"
}
export class CreateEntity implements Action {
readonly type = CreateEntityActionTypes.CreateEntity;
constructor(public payload: any) {}
}
// ... CreateEntitySuccess and CreateEntityError are defined here...
export type EntityActionsUnion =
| CreateEntity
| CreateEntitySuccess
| CreateEntityError;
Essentially, I have a container that on form submit dispatches an the CreateEntity action. However, I don't see the console.log() I've written into the effect. Furthermore, I have another effect for loading making a request for all entities from a REST API on LoadEntities which is working. The effect to create a new entity however is not, I don't even think it's firing.
I am also not receiving any errors. Here is the code that dispatches my action:
import * as EntityActions from '../actions/entity.actions.ts
createBuilding() {
const data = this.form.value;
const image = this.fileUploadComponent.file;
const payload = {
data,
image
};
this.store.dispatch(new EntityActions.CreateEntity(payload));
}
I have Redux dev-tools and i see the dispatch firing and the new state of loading: true being returned. However, the effect is not firing, nor are my Success or Error dispatches for resolutions firing either. Any idea as to why this is happening?

have you decorated your effects method with #Effect()? that's what i usually miss :)

Related

React hook, wired issue when use useState, while if use setState work perfectly, how to solve it

dear community, I am facing a wired issue, and I don't know how to summary my situation in the question title, so I wonder if the question title is accurate enough.
I was trying to convert a class component to a hook component.
The class version code like this
async componentDidMount() {
const { dispatch, itemId } = this.props;
try {
if (itemId) {
await dispatch({
type: 'assignment/fetchSubmissionsByAssignment', //here to fetch submissions in props
payload: {
id: itemId
}
});
}
const { submissions } = this.props;
this.setState({
studentSubmissions: submissions,
});
} catch (error) {
throw error.message;
}
}
render() {
const { studentSubmissions } = this.state;
return (
<Table dataSource={studentSubmissions} />
)
}
export default SubmissionsDetail;
and in hook, it look like this
const [studentSubmissions, setStudentSubmissions] = useState([]);
useEffect(() => {
async function fetchSubmissions() {
const { dispatch, itemId } = props;
try {
if (itemId) {
await dispatch({
type: 'assignment/fetchSubmissionsByAssignment',
payload: {
id: itemId
}
});
}
const { submissions } = props;
setStudentSubmissions(submissions)
} catch (error) {
throw error.message;
}
};
fetchSubmissions()
}, []);
return (
<Table dataSource={studentSubmissions} />
)
export default SubmissionsDetail;
I omitted some code for better reading, like connect to redux store or others.
and the component is import in the parent file like this
import SubmissionsDetail from './SubmissionsDetail'
{assignmentIds.map((itemId) => {
<SubmissionsDetail itemId={itemId}/>
})}
it work perfect in class component, the expected result should return tables like this
However, when I change to use hook, the result return like this
or sometimes all data in tables become submissions3
I try to console.log(submissions) inside the try{...} block, when in class, the result is
which is correct, there have two assignments, the one have 4 submissions, another one have zero submission.
But the output in hook is different, the result is like this
either both have 4 submissions, either both have zero. That means one obj affect all other obj.
It seems like if useState change, it would influence other objs, that make me really confused. I think in the map method, each item is independent, right? If so, and how to explain why it work perfectly in class setState, but failed in hook useState?
I hope my question is clear enough, If you know how to describe my question in short, plz let me know, I would update the title, to help locate experts to answer.
Please don't hesitate to share your opinions, I really appreciate and need your help, many thanks!
Edit: You are probably going to want to rework the way you store the submission inside of the redux store if you really want to use the Hook Component. It seems like right now, submissions is just an array that gets overwritten whenever a new API call is made, and for some reason, the Class Component doesn't update (and it's suppose to update).
Sorry it's hard to make suggestions, your setup looks very different than the Redux environments I used. But here's how I would store the submissions:
// no submissions loaded
submissions: {}
// loading new submission into a state
state: {
...state,
sessions: {
...state.session,
[itemId]: data
}
}
// Setting the state inside the component
setStudentSubmissions(props.submissions[itemId])
And I think you will want to change
yield put({
type: 'getSubmissions',
payload: response.data.collections
});
to something like
yield put({
type: 'getSubmissions',
payload: {
data: response.data.collections,
itemId: id
});
If you want to try a "hack" you can maybe get a useMemo to avoid updating? But again, you're doing something React is not suppose to do and this might not work:
// remove the useEffect and useState, and import useMemo
const studentSubmissions = useMemo(async () => {
try {
if (itemId) {
await dispatch({
type: "assignment/fetchSubmissionsByAssignment", //here to fetch submissions in props
payload: {
id: itemId,
},
});
return this.props.submissions;
}
return this.props.submissions;
} catch (error) {
throw error.message;
}
}, []);
return (
<Table dataSource={studentSubmissions} />
)
export default SubmissionsDetail;
There is no reason to use a local component state in either the class or the function component versions. All that the local state is doing is copying the value of this.props.submissions which came from Redux. There's a whole section in the React docs about why copying props to state is bad. To summarize, it's bad because you get stale, outdated values.
Ironically, those stale values were allowing it to "work" before by covering up problems in your reducer. Your reducer is resetting the value of state.submissions every time you change the itemId, but your components are holding on to an old value (which I suspect is actually the value for the previous component? componentDidMount will not reflect a change in props).
You want your components to select a current value from Redux based on their itemId, so your reducer needs to store the submissions for every itemId separately. #Michael Hoobler's answer is correct in how to do this.
There's no problem if you want to keep using redux-saga and keep using connect but I wanted to give you a complete code so I am doing it my way which is with redux-toolkit, thunks, and react-redux hooks. The component code becomes very simple.
Component:
import React, { useEffect } from "react";
import { fetchSubmissionsByAssignment } from "../store/slice";
import { useSelector, useDispatch } from "../store";
const SubmissionsDetail = ({ itemId }) => {
const dispatch = useDispatch();
const submissions = useSelector(
(state) => state.assignment.submissionsByItem[itemId]
);
useEffect(() => {
dispatch(fetchSubmissionsByAssignment(itemId));
}, [dispatch, itemId]);
return submissions === undefined ? (
<div>Loading</div>
) : (
<div>
<div>Assignment {itemId}</div>
<div>Submissions {submissions.length}</div>
</div>
);
};
export default SubmissionsDetail;
Actions / Reducer:
import { createAsyncThunk, createReducer } from "#reduxjs/toolkit";
export const fetchSubmissionsByAssignment = createAsyncThunk(
"assignment/fetchSubmissionsByAssignment",
async (id) => {
const response = await getSubmissionsByAssignment(id);
// can you handle this in getSubmissionsByAssignment instead?
if (response.status !== 200) {
throw new Error("invalid response");
}
return {
itemId: id,
submissions: response.data.collections
};
}
);
const initialState = {
submissionsByItem: {}
};
export default createReducer(initialState, (builder) =>
builder.addCase(fetchSubmissionsByAssignment.fulfilled, (state, action) => {
const { itemId, submissions } = action.payload;
state.submissionsByItem[itemId] = submissions;
})
// could also respond to pending and rejected actions
);
if you have an object as state, and want to merge a key to the previous state - do it like this
const [myState, setMyState] = useState({key1: 'a', key2: 'b'});
setMyState(prev => {...prev, key2: 'c'});
the setter of the state hook accepts a callback that must return new state, and this callback recieves the previous state as a parameter.
Since you did not include large part of the codes, and I assume everything works in class component (including your actions and reducers). I'm just making a guess that it may be due to the omission of key.
{assignmentIds.map((itemId) => {
<SubmissionsDetail itemId={itemId} key={itemId} />
})}
OR it can be due to the other parts of our codes which were omitted.

NGRX entity updateOne not working: id undefined

I decided to ask for help, I just cannot get my head around NGRX Entity! (This code was created initially by NX ).
I have followed the NGRX Entity guide, I have also looked at loads of tutorial videos but I still cannot get NGRX Entity updateOne to work.
Getting this error below - I can load the entities into the store with no issue, and these are building my UI fine.
I have an Entity collection of buttons and want to update the Store State of a button when clicked - that's all!
(any ideas why this is not working??)
ERROR TypeError: Cannot read property 'id' of undefined
at http://localhost:4200/vendor.js:83815:26
at Array.filter (<anonymous>)
at updateManyMutably (http://localhost:4200/vendor.js:83811:27)
at updateOneMutably (http://localhost:4200/vendor.js:83801:16)
at Object.operation [as updateOne] (http://localhost:4200/vendor.js:83622:27)
at http://localhost:4200/main.js:1169:28
at http://localhost:4200/vendor.js:88532:26
at reducer (http://localhost:4200/main.js:1173:12)
at http://localhost:4200/vendor.js:87072:20
at combination (http://localhost:4200/vendor.js:86960:37)
This is the code I have so far:
// state
export interface QuickButton {
id: number;
isSelected: boolean;
title: string;
linkUrl: string;
}
// in component
this.store.dispatch( actions.setQuickFilter( evt ) );
// evt = {id: 1, isSelected: true, linkUrl: "", title: "Video"}
// in actions
export const setQuickFilter = createAction(
'[QuickBar] setQuickFilter',
props<{update: Update<QuickButton>}>()
);
// in reducer
export const QUICKBAR_FEATURE_KEY = 'quickBar';
export interface State extends EntityState<QuickButton> {
selectedId?: string | number; // which QuickBar record selected
loaded: boolean; // has the QuickBar list been loaded
error?: string | null; // last none error (if any)
}
export interface QuickBarPartialState {
readonly [QUICKBAR_FEATURE_KEY]: State;
}
export const quickBarAdapter: EntityAdapter<QuickButton> = createEntityAdapter<QuickButton>();
export const initialState = quickBarAdapter.getInitialState({
// set initial required properties
loaded: false,
});
const quickBarReducer = createReducer(
initialState,
on(QuickBarActions.loadQuickBarSuccess, (state, action) =>
quickBarAdapter.addAll( action.quickBar, state )
),
on(QuickBarActions.loadQuickBarFailure, (state, { error }) => ({
...state,
error,
})),
on(QuickBarActions.setQuickFilter, (state, {update}) => {
/// **** This is NOT Working *****
return quickBarAdapter.updateOne( update, state);
}
)
);
export function reducer(state: State | undefined, action: Action) {
return quickBarReducer(state, action);
}
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = quickBarAdapter.getSelectors();
You're dispatching your action incorrectly.
this.store.dispatch(actions.setQuickFilter(evt));
should be
this.store.dispatch(actions.setQuickFilter({ update: evt }));
Yay!! finally.
This was a real dumb error - from not understanding Entity.
Lots of trial and error & logging to solve this!
Solution:
Change the dispatch call in component from:
this.store.dispatch( actions.setQuickFilter( {update: evt} } ) );
to:
this.store.dispatch( actions.setQuickFilter( {update: {id: evt.id, changes: evt} } ) );
Now all my subscribed features will be able to use the updated values in the buttons to control their own UI elements. Finally!

Why is my array variable undefined in my component when I try to use array methods on it?

I'm having an issue with displaying an array of Pokemon moves that I've retrieved from the database (using a rails/react/redux). The strange thing is that when I display the entire array without trying to use join on it, everything's fine. But when I try to use join on it, I get this error message: Uncaught TypeError: Cannot read property 'join' of undefined. And when I display the entire array variable as is, without modifying anything, it does display.
I've gone through my code and I've confirmed that the routing is fine and the right action creators are being dispatched. I've also gone through my reducers and I can confirm that they are using the right action constants to make decisions as to what the new state object is. The only thing that I can think of is that maybe the problem lies in one of these places below:
// the component that renders my Pokemon details onto the page
class PokemonDetail extends React.Component {
componentDidMount() {
this.props.requestSinglePokemon(this.props.match.params.pokemonId);
}
componentDidUpdate(prevProps) {
if (prevProps.match.params.pokemonId !== this.props.match.params.pokemonId) {
this.props.requestSinglePokemon(this.props.match.params.pokemonId);
}
}
render() {
const { pokemon } = this.props;
if (!pokemon) return null;
return (
<section className='pokemon-detail'>
<figure>
<img src={ pokemon.image_url } alt={ pokemon.name } />
</figure>
<ul>
<li><h1>{ pokemon.name }</h1></li>
<li>Type: { pokemon.poke_type }</li>
<li>Attack: { pokemon.attack }</li>
<li>Defense: { pokemon.defense }</li>
<li>Moves: { pokemon.moves.join(', ') }</li>
</ul>
</section>
);
}
}
// the reducer
const pokemonReducer = (state = {}, action) => {
Object.freeze(state);
let poke;
switch(action.type) {
case RECEIVE_ALL_POKEMON:
return Object.assign({}, state, action.pokemon);
case RECEIVE_SINGLE_POKEMON:
poke = action.payload.pokemon;
return Object.assign({}, state, { [poke.id]: poke });
default:
return state;
}
}
// the action creators
export const requestSinglePokemon = id => dispatch => {
dispatch(startLoadingSinglePokemon());
return APIUtil.fetchSinglePokemon(id).then(pokemon => dispatch(receiveSinglePokemon(pokemon)));
}
export const receiveSinglePokemon = payload => ({
type: RECEIVE_SINGLE_POKEMON,
payload
});
// the backend APIUtil method
export const fetchSinglePokemon = id => (
$.ajax({
method: 'GET',
url: `api/pokemon/${id}`
})
);
It's the strangest thing...when I do { JSON.stringify(pokemon.moves) } I can literally see that it is an array of strings (of Pokemon moves). And even when I just display it like this, { pokemon.moves }, it seems to be correctly grabbing the right Pokemon; the name, type, attack, and defense text is all there and the moves is also displayed (although not correctly formatted).
NOTE: Upon observing my log (it shows me the action creators that are dispatched when I interact with the page), the error with join seems to pop up before (or instead?) of the action creator that should have been dispatched (i.e. I see that the START_LOADING_SINGLE_POKEMON action is never dispatched before this error pops up)
for the first render of your app , no actions have been dispatched.
componentDidMount occurs after initial render.
hence, unless the parent component of "PokemonDetail" is passing an intial pokemon object with key "moves " whose value is of type array, it would casue this error for first render.
you can either have conditional rendering inside "PokemonDetail" component based on "Pokemon" object or have inital state of "pokemon" contain moves array in reducers.

How to wait or listen for Url has changed using rxjs and redux-observable?

I'm trying to show modal popup after I received response data and after I go to another page. Hot to implement this correctly?
Iuse React-observable and rxjs. I did create an epic using with requests to server and changing history.location. It works fine
action$.pipe(
ofType(myActionLading),
mergeMap(action => of(convertDataToServer(action.payload))),
mergeMap(data =>
from(convertToDataAPI(data)).pipe(
mergeMap(response => of(convertCustomerFromServer(response.
mergeMap(() => {
return of(
actionGetDataInfoLoading({ id }),
);
}),
tap(() => history.push('/newRoute')),
takeUntil(// I have to verify if I am already on my newRoute and then show the popUp),
finalize(() => console.log('!!! FIN !!!') ||
modals.getModalSuccess()),
),
),
I want to see the 'Success' modal popup after I am on a new route. So I want to listen history.push, or verify if I am on a new route and only then to show the modal
I don't believe it's possible to execute that logic the way you explained it (with react-router atleast).
However, if /newRoute is only reachable through the success of this action, you could just open the modal when the component corresponding to newRoute mounts.
Otherwise, provide a query param to determine whether or not you would like to open the modal.
For example:
// someEpic.js
tap(() => history.push('/newRoute?showModal=true')
...
//newRoute.jsx (The component)
export class NewRoute extends React.Component {
...
componentWillMount() {
const showModal = this.props.... // logic to get query param of showModal.
if (showModal) {
modals.getModalSuccess();
}
}
}

How to bind React state to RxJS observable stream?

can someone help me how to bind React State to RxJS Observable? I did sth like
componentDidMount() {
let source = Rx.Observable.of(this.state.val)
}
The ideal result is, whenever this.state.val updated (via this.setState(...)) source get updated too, so I can combine source with other RxJS observable stream.
However, in this case, source only get updated once, even after this.state.val is updated and component is re-rendered.
// Ideal result:
this.state.val = 1
source.subscribe(val => console.log(x)) //=> 1
this.state.val = 2
source.subscribe(val => console.log(val)) //=> 2
// Real result:
this.state.val = 1
source.subscribe(val => console.log(x)) //=> 1
this.state.val = 2
source.subscribe(val => console.log(val)) //=> 1 ???WTH
It might be because componentDidMount() only invoked once in React lifetime. so I move source to componentDidUpdate() which is invoked everytime after component is rendered. However, the result still remain the same.
So the question is how to make source updated whenever this.state.val updated?
Updated: Here is the solution I used to solve the prob, using Rx.Subject
// Component file
constructor() {
super(props)
this.source = new Rx.Subject()
_onChangeHandler(e) {
this.source.onNext(e.target.value)
}
componentDidMount() {
this.source.subscribe(x => console.log(x)) // x is updated
}
render() {
<input type='text' onChange={this._onChangeHandler} />
}
//
Update
To abstract out some of the below complexity, use recompose's mapPropsStream or componentFromStream. E.g.
const WithMouseMove = mapPropsStream((props$) => {
const { handler: mouseMove, stream: mouseMove$ } = createEventHandler();
const mousePosition$ = mouseMove$
.startWith({ x: 0, y: 0 })
.throttleTime(200)
.map(e => ({ x: e.clientX, y: e.clientY }));
return props$
.map(props => ({ ...props, mouseMove }))
.combineLatest(mousePosition$, (props, mousePosition) => ({ ...props, ...mousePosition }));
});
const DumbComponent = ({ x, y, mouseMove }) => (
<div
onMouseMove={mouseMove}
>
<span>{x}, {y}</span>
</div>
);
const DumbComponentWithMouseMove = WithMouseMove(DumbComponent);
Original Post
For a slightly updated answer to the OP's updated answer, using rxjs5, I came up with the following:
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.mouseMove$ = new Rx.Subject();
this.mouseMove$.next = this.mouseMove$.next.bind(this.mouseMove$);
this.mouseMove$
.throttleTime(1000)
.subscribe(idx => {
console.log('throttled mouse move');
});
}
componentWillUnmount() {
this.mouseMove$.unsubscribe();
}
render() {
return (
<div
onMouseMove={this.mouseMove$.next}
/>
);
}
}
Some notable additions:
onNext() is now next()
binding the observable next method allows it to be passed directly to the mouseMove handler
streams should be unsubscribed in componentWillUnmount hook
Furthermore, the subject streams initialized in the component constructor hook can be passed as properties to 1+ child component(s), which could all push to the stream using any of the observable next/error/complete methods. Here's a jsbin example I put together demonstrating multiple event streams shared between multiple components.
Curious if anyone has ideas on how to better encapsulate this logic to simplify stuff like binding and unsubscribing.
One option could be to use Rx.Observable.ofObjectChanges > cf. https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/ofobjectchanges.md.
However :
It uses Object.observe which is not a standard feature, hence will have to be polyfilled in some browsers and is actually being removed from inclusion in ecmascript (cf. http://www.infoq.com/news/2015/11/object-observe-withdrawn). Not the choice for the future, but it is easy to use, so if it is just for your own needs, why not.
Other option is to use a subject in one of the three methods at your disposal according to your use case : shouldComponentUpdate, componentWillUpdate, componentDidUpdate. Cf. https://facebook.github.io/react/docs/component-specs.html for when each function is executed. In one of these methods, you would check if this.state.val has changed, and emits its new value on the subject if it did.
I am not a reactjs specialist, so I guess they might be other options.
Although a subject will work, I think the best practice is to avoid using a subject when you can use an observable. In this case you can use Observable.fromEvent:
class MouseOverComponent extends React.Component {
componentDidMount() {
this.mouseMove$ = Rx.Observable
.fromEvent(this.mouseDiv, "mousemove")
.throttleTime(1000)
.subscribe(() => console.log("throttled mouse move"));
}
componentWillUnmount() {
this.mouseMove$.unsubscribe();
}
render() {
return (
<div ref={(ref) => this.mouseDiv = ref}>
Move the mouse...
</div>
);
}
}
ReactDOM.render(<MouseOverComponent />, document.getElementById('app'));
Here it is on codepen....
It seems to me that there are other times when a Subject like the best choice, like when a custom React component executes a function when an event occurs.
I would highly recommend reading this blog post on streaming props to a React component using RxJS:
https://medium.com/#fahad19/using-rxjs-with-react-js-part-2-streaming-props-to-component-c7792bc1f40f
It uses FrintJS, and applies the observe higher-order component for returning the props as a stream:
import React from 'react';
import { Observable } from 'rxjs';
import { observe } from 'frint-react';
function MyComponent(props) {
return <p>Interval: {props.interval}</p>;
}
export default observe(function () {
// return an Observable emitting a props-compatible object here
return Observable.interval(1000)
.map(x => ({ interval: x }));
})(MyComponent);
You can do it using hooks.
Here is a code sample
import { Observable, Subscription } from 'rxjs';
import { useState, useEffect } from 'react';
export default function useObservable<T = number | undefined>(
observable: Observable<T | undefined>,
initialState?: T): T | undefined {
const [state, setState] = useState<T | undefined>(initialState);
useEffect(() => {
const subscription: Subscription = observable.subscribe(
(next: T | undefined) => {
setState(next);
},
error => console.log(error),
() => setState(undefined));
return () => subscription.unsubscribe();
}, [observable])
return state;
}

Categories

Resources