Confusion about 'create Unsubscribe() using store.subscribe() method in Redux' - javascript

I am learning how to create Unsubscribe() using store.subscribe() method.
Below is the code. I don't understand the logic: why we use store.subscribe method to declare unsubscribe constant as an unsubscribe function for the store. (literally, subscribe should subscribe 'something' for the store, right?)
import store from './store';
const unsubscribe = store.subscribe(
() => console.log("Store changed!", store.getState())
);
store.dispatch({
type: "bugAdded",
payload: {
description: "Bug1"
}
});
unsubscribe();
store.dispatch({
type: "bugRemoved",
payload: {
id: 1
}
});
Also, please look at the following modified code: even only declare the constant unsubscribe, the console still executes the store.subscribe method.
import store from './store';
const unsubscribe = store.subscribe(
() => console.log("Store changed!", store.getState())
);
store.dispatch({
type: "bugAdded",
payload: {
description: "Bug1"
}
});
// unsubscribe();
store.dispatch({
type: "bugRemoved",
payload: {
id: 1
}
});
the above code has the same output as the following code (confused, anyone could explain)
import store from './store';
store.subscribe(
() => console.log("Store changed!", store.getState())
);
store.dispatch({
type: "bugAdded",
payload: {
description: "Bug1"
}
});
store.dispatch({
type: "bugRemoved",
payload: {
id: 1
}
});

store.subscribe will just create a suscription to our store, it will be listening the store and waiting for it to change, every time the store changes it will execute the callback we passed to it. When we subscribe to the store, it returns a function to unregister that subscription later that we can call whenever we need.
You could read more about it on the redux documentation

Related

making a post request inside mapDispatchToProps in react-redux

I'm trying to make a post request inside a function whenever i click on a button.
here is the code of the button
<Button onClick={handleClick}>Add to Cart</Button>
and here is the `handleClick funcion:
const handleClick = (event) => {
event.preventDefault();
props.postCart(itemData.product_name, itemData.product_price);
}
and here i showcase the code of mapDispatchToProps function:
const mapDispatchToProps = dispatch => {
return {
postCart: (productName, productPrice) => dispatch(postAddToCart(productName, productPrice))
}
}
finally the code of postAddToCart:
export const postAddToCart = (productName, productPrice) => {
const email = sessionStorage.getItem('email');
return (dispatch) => {
dispatch(productName, productPrice);
//REST API endpoint
axios.post('http://localhost:8000/api/auth/add-to-cart', {
email:email,
})
.then(resp => {
dispatch({
type: actionTypes.ADDED_TO_CART,
status: resp.status
});
})
.catch(resp => {
dispatch({
type: actionTypes.ADDED_TO_CART,
status: "FAILED"
});
})
}
}
But whenever i click the button Add to cart i get the following error:
Error: Actions must be plain objects. Use custom middleware for async actions.
knowing that i'm using the redux-thunk middleware.
Can you please tell me what's the problem and how can i fix it ? thank you ^^. if i missed something tell me in comments to add it ^^
Your function postAddToCart() returns a "dispatcher", i.e. a function that expects dispatch as an argument.
The error is that you are trying to dispatch this "dispatcher", instead of an "action":
// wrong: calling 'dispatch()'
postCart: (productName, productPrice) => dispatch(postAddToCart( ... ))
// correct: calling the returned dispatcher and pass 'dispatch' as argument
postCart: (productName, productPrice) => postAddToCart( ... )(dispatch)

Vue3/Vuex - Where am I "mutating vuex store state outside mutation handlers"?

When calling a Vuex Action where Firebase successfully updates, followed by a Vuex Mutation on state.profile, Vue triggers the following errors upon save within watch().
[Vue warn]: Unhandled error during execution of watcher callback
Error: [vuex] do not mutate vuex store state outside mutation handlers.
I have cloned and then merged store.state.profile into formData via rfdc.
Form values are updated within it's own reactive() data object formData, versus immediately committing to store in order to later provide both save() and cancel() options.
template
<template>
<element-textarea
id="about"
title="About"
:rows="10"
v-model="about"
></element-textarea>
<element-checkbox
v-for="(specialty, i) in jSpecialties"
:key="i"
:id="specialty.toLowerCase()"
:title="specialty"
v-model="specialties[specialty]"
>
{{ specialty }}
</element-checkbox>
</template>
script
...
const clone = require('rfdc/default')
export default defineComponent({
...
setup(){
...
let specialties: { [key: string]: boolean } = {}
jSpecialties.map(name => (specialties[name] = false))
const formData = reactive({
about: '',
specialties
})
watch(
() => store.state.profile,
() => {
try {
Object.assign(formData, clone(store.state.profile))
} catch (err) {
console.log('Error Appears Here', err)
}
}
)
const save = () =>
store.dispatch('updateProfile', formData)
.catch(err => console.log(err))
return { ...toRefs(formData), save }
}
})
vuex
export default createStore({
strict: true,
state: {
profile: {}
},
mutations: {
setProfile: (state, payload) => state.profile = payload,
},
actions: {
updateProfile: ({state, commit}, data) => {
const update = () =>
db
.collection(...)
.doc(...)
.set(data, {merge: true})
.then(_ => commit('setProfile', data))
.catch(err => console.log(err))
return update()
}
}
})
When removing strict: true from vuex, I receive this error:
Maximum recursive updates exceeded. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.
Replacing watch() with watchEffect() get rid of the Maximum recursive updates exceeded error.
watchEffect(() => {
try {
Object.assign(formData, clone(store.state.profile))
} catch (error) {
console.log(error)
}
})
However, when updating Vuex with strict: true, I am still receiving the following:
Error: [vuex] do not mutate vuex store state outside mutation handlers.
I've discovered the issue. I overlooked my copying of a reference object back to state.
I needed to clone formData prior to dispatching, as the same data object was also committed to store via actions.
const save = () =>
store.dispatch('updateProfile', clone(formData))
.catch(err => console.log(err))

Use Redux store as input for Post API call

I built an input interface in React and stored the data in a Redux state.
Then, I created a new action for a Post API call. If the "data" parameter is a constant (that I created as a test), everything works fine.
In reality, I'd like to use a key from the Redux state as the data parameter.
In the example, I'm getting an error because props are not defined.
Does it makes sense to connect this action to the state? If yes, how to do it? Thanks a lot!
import axios from 'axios';
export const show_query_action = () => {
return dispatch => {
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: this.props.reducer_components
})
.then(res => {
dispatch(show_query_success_action(res));
})
.catch(err => {
dispatch(show_query_failure_action(err.message));
});
};
};
const show_query_started_action = () => ({
type: 'ADD_SHOW_QUERY_STARTED'
});
const show_query_success_action = todo => ({
type: 'ADD_SHOW_QUERY_SUCCESS',
payload: {
...todo
}
});
const show_query_failure_action = error => ({
type: 'ADD_SHOW_QUERY_FAILURE',
payload: {
error
}
});
If it needs to be callable with different parameters from a React component you can put the data parameter as a parameter to your action creator:
export const show_query_action = (data) => {
return dispatch => {
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: data
})
This is also easily testable. If you only call this action with parameters from the redux store, you can use the second parameter from redux-thunk (wich i presume you are using here):
export const show_query_action = () => {
return (dispatch, getState) => {
const data = getState().data; -> you would specify in wich part of the state your data lives, this is just an example
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: data
})
Hope this helps.

Accessing redux store in headlessJS

I need to access store in headlessJS in react native to dispatch an action.
I tried
AppRegistry.registerHeadlessTask('didEnterRegion', (beacon) => Util.didEnterRegion.bind(beacon, store));
and in Util.js
const didEnterRegion = async (beacons, store) => {
console.log(beacons);
store.dispatch({
type: FOUND_PLACES_FAIL,
payload: err
});
});
but now I receive the beacons object as an empty object. How do I pass that object too along with the store.
Sorry, so stupid of me.
I just had to pass the params in headlessJS.
AppRegistry.registerHeadlessTask('didEnterRegion', (beacon) => Util.didEnterRegion.bind(beacon, store));
const didEnterRegion = async (store, beacon) => {
console.log(beacon);
store.dispatch({
type: FOUND_PLACES_FAIL,
payload: err
});
});

How do we mock fetch in Redux Async Actions?

In the Writing Tests section of Redux,http://rackt.org/redux/docs/recipes/WritingTests.html, how does the store.dispatch(actions.fetchTodos()) not invoke the fetch method, if store.dispatch is literally calling actions.fetchTodos?
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] })
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] }, expectedActions, done)
store.dispatch(actions.fetchTodos())
})
Everytime I try to run something similar to this, I keep getting a fetch is not defined. Even if I use nock. So I have to spy my action to not get the call to fetch.
Here is my unit test:
it('should request a password reset, and then return success on 200', (done) => {
nock('http://localhost:8080/')
.post('/password-reset-requests')
.reply(200);
var email = "test#email.com";
const expectedActions=[
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
];
const store = mockStore({}, expectedActions, done);
store.dispatch(Actions.addPasswordResetRequest());
here is the action:
export default function addPasswordResetRequest(email){
return dispatch => {
dispatch(requestAddPasswordResetRequest(email));
return addPasswordResetRequestAPI(email)
.then(() =>{
dispatch(requestAddPasswordResetRequestSuccess());
})
.catch((error) => {
dispatch(requestAddPasswordResetRequestFailure(error));
});
};
}
and the function that calls fetch:
export const addPasswordResetRequestAPI = (email) => {
return fetch(
SETTINGS.API_ROOT + '/password-reset-requests',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
code: NC_SETTINGS.GROUP.code
})
}
)
.then(handleResponse);
};
I'm not sure if the way I am doing is sufficient for the purpose of just testing actions, but then I do run into the problem of store.dispatch only returning the first element of expectedActions, and it doesn't equal the list I supply in the spied addPasswordResetRequest. Below includes the spied action.
it('should request a password reset, and then return success on 200', (done) => {
nock('http://localhost:8080/')
.post('/password-reset-requests')
.reply(200);
Actions.addPasswordResetRequest = spy(() => {
return ([
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
]
);
});
var email = "test#email.com";
const expectedActions=[
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
];
const store = mockStore({}, expectedActions, done);
store.dispatch(Actions.addPasswordResetRequest());
The action "addPasswordResetRequest" isn't an action per-say.
it's a composite action with 3 sub-actions
startAction =requestAddPasswordResetRequest,
successAction =requestAddPasswordResetRequestSuccess
failAction =requestAddPasswordResetRequestFailure
I generally tests each action separately. so i would have something like
describe("requestAddPasswordResetRequest", () => {
it("shows the loading spinner or whatever", ...);
it("does some other state change maybe", ...);
});
describe("requestAddPasswordResetRequestSuccess", () => {
it("hides the loading spinner or whatever", ...);
it("changes the password state or something", ...);
});
describe("requestAddPasswordResetRequestFailure", () => {
it("hides the loading spinner or whatever", ...);
it("shows the error somehow", ...);
});
//each test would be something like
it("changes the password state or something", ()=>{
const action = requestAddPasswordResetRequestSuccess({
some : "payload from the server"
});
const newState = myReducer({ state : "somestate" }, action);
expect(newState).to.be.eql("expected result for that action");
});
Notice how in the test i don't need the store or any async logic. thats the beauty of redux (and functional stuff in general), it's simple :)
after this i would have a separate test for the whole thing and make sure that the correct simple actions get dispatched by the composite action, in which i would mock everything (including store and the "fetch" thing, since i just want to test that the actions get fired in the correct order).
if the actions are dispatched in the correct order and each action work separably i would be pretty confident that the thing works as expected.
Hope this helps.

Categories

Resources