RxJS Observable Cancellation doesn work - redux

I have the following problem with some implementation in React/Redux.
After clicking on a button, a specific redux action is call and div with notification shows on screen. You can close this notification by clicking on a (X) sign on that div (another redux action) or notification will close automatically after 5 secs. Clicking on (x) should cancell an automatic action.
actions:
const OPEN = 'show_notification';
const CLOSE = 'close_notification';
const CLOSE_AUTO = 'close_auto';
function showNotification(data) {
return {
type: 'OPEN',
data
}
}
function closeNotification(index) {
return {
type: 'CLOSE',
index
}
}
function closeAuto() {
return {
type: 'CLOSE_AUTO'
}
}
epics:
import (...)
closeNotificationAuto = action$ => action
.filter(action => action.type === OPEN)
.mergeMap(action => action
.delay(5000)
.map( () => closeAuto)
.takeUntil(action$.ofType(CLOSE))
}
Anyway, when two notifications are on screen, the action === CLOSE is closing the first one, and cancell delay() for another.
Not posting my whole code because the problem is here, in epics. Can't manage to achieve a solution:
when clicking on a (x) the specific notification is close, but another one (which time is for example 3secs) is still visible and hide automatically after another 2 secs.
Thans for any help!

The code in the epic is incomplete, so it's not totally clear (what happens inside the mergeMap?). But I did see one issue, which is that your takeUntil is on the top-level observable chain, which means it won't just cancel that particular delay, it will also stop listening for any action at all.
Instead, you need to delay and cancel the matched action individually inside something like a mergeMap, switchMap, etc. This is commonly called "isolating your observer chains".
Here's what that might look like:
const closeNotificationAuto = action$ =>
action$
.ofType(OPEN)
.mergeMap(action =>
Observable.of(action)
.delay(5000)
.map(() => closeAuto())
.takeUntil(action$.ofType(CLOSE))
);
This pattern, filter then flatMap (mergeMap, switchMap, etc), is how most of your epics will look.
Regarding your comments below, it sounds like you want to add a filter to takeUntil notifier to only take CLOSE actions that somehow uniquely identifies it.
See https://stackoverflow.com/a/48452283/1770633
const closeNotificationAuto = action$ =>
action$
.ofType(OPEN)
.mergeMap(action =>
Observable.of(action)
.delay(5000)
.map(() => closeAuto())
.takeUntil(
action$.ofType(CLOSE).filter(a => a.something === action.something)
)
);
If there isn't some sort of unique ID already available for each, you'll need to create and include one.

Related

Adding a new item when using useSWRInfinite pushes other items out of the list

I am building a comment system where new replies are added to the start (top) of the list. The pagination is cursor-based.
At the moment, I use mutate to add the newly created comment as its own page to the front of the list.:
const {
data: commentsPages,
: commentsPagesSize,
: setCommentsPagesSize,
//TODO: Not true on successive page load. But isValidating refreshes on refetches
isLoading: commentsLoading,
error: commentsLoadingError,
mutate: mutateCommentPages,
} = useSWRInfinite(
getPageKey,
([blogPostId, lastCommentId]) => BlogApi.getCommentsForBlogPost(blogPostId, lastCommentId));
<CreateCommentBox
blogPostId={blogPostId}
title="Write a comment"
onCommentCreated={(newComment) => {
const updatedPages = commentsPages?.map(page => {
const updatedPage: GetCommentsResponse = { comments: [newComment, ...page.comments], paginationEnd: page.paginationEnd };
return updatedPage;
})
mutateCommentPages(updatedPages, { revalidate: false });
}}
/>
The problem is, SWR immediately starts revalidating the list and pushes the comment at the bottom out of the data set. This behavior is kind of awkward.
Is my only choice do disable automatic revalidation completely? How would you handle this?

Nuxt 3: Problem with wildcard pages and useFetch

I have a problem with a wildcard page /pages/[...slug].vue and fetching from backend.
I have a computed called url that I use in:
const { data, refresh } = await useFetch(url.value)
Then I have a watcher to refresh the useFetch:
watch(url, (url, oldUrl) => {
console.log(url)
console.log(oldUrl)
refresh()
})
In the browser, the console log shows the correct url, but useFetch just loaded the old url again.
Any idea what's wrong here? Thanks.
[edit: clarification: this is when navigation in browser, that triggers the watch]
I believe because computed is a 'getter' behind the scenes you need to use a deep watcher on it or watch the returned value directly otherwise the watch function will not run.
You could change your watch function to to:
watch(url, (url, oldUrl) => {
console.log(url)
console.log(oldUrl)
refresh()
},
{ deep: true }
)
Alternatively you could directly watch the value of the computed property:
watch( () => url.value, (url, oldUrl) => {
console.log(url)
console.log(oldUrl)
refresh()
},
{ deep: true }
)
There is a bit more information here: https://vuejs.org/guide/essentials/watchers.html#basic-example
Hi i had the same issue 2 days ago.. i guess that the issue happens because of the default keep alive props and internal caching.
My solution was to use $fetch() instead of useFetch().
Like that you also don't need the watcher anymore.

Unit Test action dispatch using enzyme

I'm trying to write a simple unit test to make sure if the form inside a react component dispatches the expected action on submit.
Code:
Form submit action inside the component which I'm trying to test:
<form onSubmit={(values, dispatch) => {
store.dispatch(doSomething());
handleSubmit(values, dispatch);
}}>
Test:
test('Test', (t) => {
const TestForm = TestForm();//redux form
const dispatchSpy = sinon.spy();
const props = Object.assign({}, baseProps, {
handleSubmit: (callback) => {
callback({}, dispatchSpy);
},
});
t.context.form = mount(<Provider store={store}><TestForm /></Provider>);
t.context.form.find('form').simulate('submit');
//TODO - assert
The problem here is I get the following error and I'm not able to figure out the issue:
TypeError: callback is not a function
Any thoughts on this? Thanks.
Are you sure you want to test form dispatches the expected action on submit ? I feel like it's just rewriting the internal test of redux-form (which is already tested here :
https://github.com/erikras/redux-form/blob/master/src/tests/Form.spec.js#L131-L-165 )

Redux - conditional dispatching

Classic problem - I want to validate a form before submitting it.
My submit button triggers an action (simple dispatch or thunk). If the form is valid, submit it - else, trigger an error message.
The simple solution: dispatch an action e.g. VALIDATE_AND_SUBMIT, which in the reducer will validate the form, and submit or set an error state.
I feel like these are two different actions: VALIDATE
{
type: "VALIDATE",
formData
}
This should validate and set errors.
{
type: "SUBMIT",
// get form data and errors from state
}
SUBMIT - should submit provided there's no error state.
Even if I use redux-thunk, I can't get feedback from the first VALIDATE action. Is my thought process anti-pattern here? How can I validate before submitting a form?
I think part of your issue is think of actions as something that is happening rather than something has caused the state to change.
"Validate" is not an action. "Validation failed" is the action.
"Submitting" the data is not an action. "Data was submitted" is the action.
So if you structure your thunk with that in mind, for example:
export const validateAndSubmit = () => {
return (dispatch, getState) => {
let formData = getState().formData
if (isValid(formData)) {
dispatch({type: "VALIDATION_PASSED"})
dispatch({type: "SUBMISSION_STARTED"})
submit(formData)
.then(() => dispatch({type: "SUBMITTED" /* additional data */}))
.catch((e) => dispatch({type: "SUBMISSION_FAILED", e}))
}
else {
dispatch({type: "VALIDATION_FAILED" /* additional data */})
}
}
}
why don't validate form before dispatching, and dispatch the appropriate action? or you can use redux middle-ware click here
Classical and whole solution:
Hold the entire form in your state, for example:
{
valid: false,
submitAttempted: false,
username: '',
email: '',
}
Add onInput event to your form inputs and track their state validating on every change.
So when the user submits, you can check the valid state and react.
redux-form
was built just for that.
Assuming you don't want to hold the entire form state
In this case, you'll have to validate and then submit.
Here's an example assuming using react-redux with mapStateToProps and mapDispatchToProps
// action in component
submitButtonAction() {
props.validate();
if(props.formValid) props.submitForm();
}
// JSX
<FormSubmitButton onClick={this.submitButtonAction} />

Structuring a reducer for a simple CRUD application in redux

So I'm creating what is at it's core a very simple CRUD-style application, using React + Redux. There is a collection of (lets call them) posts, with an API, and I want to be able to list those and then when the user clicks on one, go into a detail page about that post.
So I have a posts reducer. Originally I started using the approach taken from the redux real-world example. This maintains a cache of objects via an index reducer, and when you do a "get post" it checks the cache and if it's there, it returns that, else it makes the appropriate API call. When components mount they try to get things from this cache, and if they're not there they wait (return false) until they are.
Whilst this worked OK, for various reasons I now need to make this non-caching i.e. everytime I load the /posts/:postId page I need to get the post via the API.
I realise in the non-redux world you would just do a fetch() in the componentDidMount, and then setState() on that. But I want the posts stored in a reducer as other parts of the app may call actions that modify those posts (say for example a websocket or just a complex redux-connected component).
One approach I've seen people use is an "active" item in their reducer, like this example: https://github.com/rajaraodv/react-redux-blog/blob/master/public/src/reducers/reducer_posts.js
Whilst this is OK, it necessitates that each component that loads the active post must have a componentWillUnmount action to reset the active post (see resetMe: https://github.com/rajaraodv/react-redux-blog/blob/master/public/src/containers/PostDetailsContainer.js). If it did not reset the active post, it will be left hanging around for when the next post is displayed (it will probably flash for a short time whilst the API call is made, but it's still not nice). Generally forcing every page that wants to look at a post to do a resetMe() in a componentWillUnmount fells like a bad-smell.
So does anyone have any ideas or seen a good example of this? It seems such a simple case, I'm a bit surprised I can't find any material on it.
How to do it depends on your already existing reducers, but i'll just make a new one
reducers/post.js
import { GET_ALL_POSTS } from './../actions/posts';
export default (state = {
posts: []
}, action) => {
switch (action.type) {
case GET_ALL_POSTS:
return Object.assign({}, state, { posts: action.posts });
default:
return state;
}
};
It is very easy to understand, just fire an action to get all your posts and replace your previous posts with the new ones in the reducer.
Use componentDidMount to fire the GET_ALL_POSTS action, and use a boolean flag in the state to know if the posts where loaded or not, so you don't reload them every single time, only when the component mounts.
components/posts.jsx
import React from 'react';
export default class Posts extends React.Component {
constructor(props) {
super(props);
this.state = {
firstLoad: false
};
}
componendDidMount() {
if (!this.state.firstLoad) {
this.props.onGetAll();
this.setState({
firstLoad: true
});
}
}
// See how easy it is to refresh the lists of posts
refresh() {
this.props.onGetAll();
}
render () {
...
// Render your posts here
{ this.props.posts.map( ... ) }
...
}
}
We're just missing the container to pass the posts and the events to the component
containers/posts.js
import { connect } from 'react-redux';
import { getPosts } from './../actions/posts';
import Posts from './../components/posts.jsx';
export default connect(
state => ({ posts: state.posts }),
dispatch => ({ onGetAll: () => dispatch(getPosts()) })
);
This is a very simple pattern and I've used it on many applications
If you use react-router you can take advantage of onEnter hook.

Resources