what are the best practices to use pubnub using generators in redux-saga. To dispatch actions on Connect, on disconnect, on events, to retrieve history messages and so on?
The problem is that PubNub provides a callback API instead of promises, and some functions have more than one callback, like connect, disconnect.
For publish with have this function:
pubnub.publish({
channel,
message,
callback: (message) => {
// dispatch action with new message/event
},
error: (err) => {
// dispatch action of error
}
});
For subscribe with have this one:
pubnub.subscribe({
channel,
connect: () => //dispatch action of connect
callback: (data) => // dispatch action of new data
error: (err) => // dispatch action of error
}
Related
From the tutorial located here, I have a question regarding this section of the code:
export function fetchPosts(subreddit) {
// Thunk middleware knows how to handle functions.
// It passes the dispatch method as an argument to the function,
// thus making it able to dispatch actions itself.
return function (dispatch) {
// First dispatch: the app state is updated to inform
// that the API call is starting.
dispatch(requestPosts(subreddit))
// The function called by the thunk middleware can return a value,
// that is passed on as the return value of the dispatch method.
// In this case, we return a promise to wait for.
// This is not required by thunk middleware, but it is convenient for us.
return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(
response => response.json(),
// Do not use catch, because that will also catch
// any errors in the dispatch and resulting render,
// causing an loop of 'Unexpected batch number' errors.
// https://github.com/facebook/react/issues/6895
error => console.log('An error occured.', error)
)
.then(json =>
// We can dispatch many times!
// Here, we update the app state with the results of the API call.
dispatch(receivePosts(subreddit, json))
)
}
}
Let's assume I wanted to use the async/await syntax instead of the "then" syntax, how would I get the error object if something fails?
e.g.
let response = await fetch(`https://www.reddit.com/r/${subreddit}.json`)
let json = await response.json();
I can surround these lines of code with a try/catch, but the author has a stern warning not to use catch here (refer to snippet above).
So is there a proper way to use the async/await pattern with this code?
In the link you provided the note to avoid using catch is regarding the promise .catch statement. This is because it would catch errors in both the then blocks. Instead of just errors caused via fetch or response.json() it would also catch errors caused via dispatch(receivePosts(subreddit, json))
You should be able to use async await as you describe in your post whilst avoiding catching errors caused by dispatch. e.g.
export function fetchPosts(subreddit) {
return async function (dispatch) {
dispatch(requestPosts(subreddit));
let response;
let json;
try {
response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
json = await response.json();
} catch(e) {
// handle fetch or json error here e.g.
dispatch(receivePostsError(subreddit, e.message));
}
if (json) {
dispatch(receivePosts(subreddit, json));
}
}
}
New to Jest and Redux and I'm having trouble with testing functions that are dispatching to the store but don't yield a return value. I'm trying to follow the example from the Redux website does this
return store.dispatch(actions.fetchTodos()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
however I have several "fetchtodos" functions that don't return anything which causes the error TypeError:
Cannot read property 'then' of undefined due to returning undefined
I'm wondering what I can do to test that my mock store is correctly updating. Is there a way to dispatch the function, wait for it to finish and then compare the mock store with expected results?
Thanks
Edit: We're using typescript
action from tsx
export function selectTopic(topic: Topic | undefined): (dispatch: Redux.Dispatch<TopicState>) => void {
return (dispatch: Redux.Dispatch<TopicState>): void => {
dispatch({
type: SELECT_Topic,
payload: topic,
});
dispatch(reset(topic));
};
}
test.tsx
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('Select Topic action', () => {
it('should create an action to select .', () => {
const topic: Topic = mockdata.example[0];
const expectedAction = {
type: actions.SELECT_TOPIC,
payload: topic,
};
const store = mockStore(mockdata.defaultState);
return store.dispatch(actions.selectTopic(topic)).then(() => {
expect(store.getState()).toEqual(expectedAction);
});
});
});
The action is what I'm given to test(and there are many other functions similar to it. I'm getting that undefined error when running the test code, as the function isn't returning anything.
In Redux, the store's dispatch method is synchronous unless you attach middleware that changes that behavior, ie: returns a promise.
So this is likely a redux configuration problem. Be sure you are setting up your test store with the same middleware that allows you to use the promise pattern in production.
And as always, be sure to mock any network requests to avoid making api calls in test.
I've used redux saga before but i'm still fairly new to it. Anyways, I seem to be running into a problem in the code below.
At the top, you will see the action creator I am using to fire off this AJAX request. Redux is properly dispatching this action and is logging the LOAD_USER_REQUEST type in my console however the function chain stops there. In the code below, you will see that LOAD_USER_REQUEST should call the loadUserDetails generator which should then call the userLogin with the payload received in my action creator.
Let me know if I can supply any additional info that may help. Thanks in advance :)
// Action Creator for LOAD_USER_REQUEST.
export const getExistingUser = ({email = 'tt#gmail.com', password = '12345'} = {}) => ({
type: LOAD_USER_REQUEST,
payload: {email, password}
})
// API call being used in loadUserDetails Saga
export const userLogin = ({email = 'tt#gmail.com', password = '12345'} = {}) => {
return axios.post(`${API}auth/login`, {
email,
password
})
.then(res => {
console.log(res);
localStorage.setItem('token', res.data.token);
let user = res.data.user;
console.log(user);
return user;
})
.catch(err => new Error('userLogin err', err));
}
// Sagas
// loadUserDetails Saga - Should call fn above userLogin with payload from action creator
function* loadUserDetails(payload) {
const user = yield call(userLogin(payload));
yield put({type: LOAD_USER_SUCCESS, user}); // Yields effect to the reducer specifying the action type and user details
}
export function* watchRequest() {
yield* takeLatest(LOAD_USER_REQUEST, loadUserDetails);
}
At first, does your entry point to saga configured well? You should add saga-middleware in store creation, and don't forget to invoke saga process manager by runSaga method.
At second, why you re-delegate generator instance to up-level? Maybe it's meant to yield takeLatest(LOAD_USER_REQUEST, loadUserDetails); without yield* operator? Is has quite different semantics.
At third, by API reference, call effect takes function or generator reference, but you provide promise object. Maybe it's meant const user = yield call(() => userLogin(payload)); or const user = yield call(userLogin, payload);?
I'm designing a messaging application with redux as my state manager and firebase to store my data. I've started writing my database listeners in this fashion:
const fetchMessages = roomKey => async dispatch => {
const db = firebase.database();
let { messages } = await db.ref(`messages/${roomKey}`).on('value');
dispatch({
type: SET_MESSAGES,
payload: messages,
})
};
All this basically does is fetch messages by a room key and then dispatch an action that sets the messages in the redux state.
Traditionally, this would be written as such:
db.ref(`messages/${roomKey}`).on('value', snapshot => {
const messages = snapshot.messages;
dispatch({
type: SET_MESSAGES,
payload: messages,
})
});
And everytime something changes in messages/${roomKey}, my dispatch function would be executed. I'm wondering if this will work the same using the async await syntax, and if not, how I could make it work.
Hope this was enough detail!
The reference's on method does not return a promise. The callback it's passed can be invoked multiple times, so a promise does not fit with the method's contract.
However, the reference's once method method does return a promise, as the (optional) callback it's passed is invoked only once - after which the promise resolves. The once method is likely the one you want to use.
I'm using redux-thunk and I also want to dispatch some actions with timeout. Because of some reasons (i want all timeouts in an object, i want to able to cancel them, doesnt really matter now) I want to have custom 'timeout middleware' and 'action enchancer'
enchancer just emits special type of action:
const addTimeoutToAction = (delay, action) => ({
type: 'TIMEOUT'
, delay
, action
})
middleware just catches it and should dispatch action after timeout ends
({dispatch, getState}) => next => action => {
if (action && action.type === 'TIMEOUT') {
setTimeout(() => {
dispatch(action.action);
}, action.delay)
}
next(action);
}
So my expectation is that dispatch function in the middleware will send action back to the middleware chain, where it will start to go through all again.
My example code works with plain action, however thunked action is not. please help me understand how to reroute delayed action back to middleware chain.
Example code:
http://codepen.io/Fen1kz/pen/zKadmL?editors=0010
You code should look like this
const action3 = () => (dispatch, getState) => {
dispatch({
type: 'action3'
});
}
Whenever you use thunk middleware, you MUST call dispatch to dispatch actions, you cannot return an object.
Here is the corrected codepen: http://codepen.io/anon/pen/pEKWRK?editors=0010
Hope this helps.