I'm using React JS and Redux JS.
I know that Redux actions functions should be a pure function
But, I was reading about Object Oriented Programming (OOP) and its benefits.
So, Can I use OOP in Redux action (Class) instead of pure functions ??
For example:
If I've a state named "articles", It may have actions like:
function getArtcles() { ... }
function addArtcle({data}) { ... }
function deleteArtcle(id) { ... }
......
So, can I replce that with:
class Artice {
getArtcles() {...}
addArtcle() {...}
deleteArtcle() {...}
}
??
I assume those functions are action creators? Ie, they return an object with a type property, and possibly some additional data?
function deleteArticle(id) {
return { type: 'deleteArticles', id };
}
Or they may create thunks:
function deleteArticle(id) {
return async function (dispatch) {
dispatch({ type: 'deleteStarting' });
// do something asynchronous
dispatch({ type: 'deleteSuccessful' });
}
}
If so, yes, you can put them in a class if you want. I don't see a benefit to putting these in a class, but redux doesn't even have a way to know how you created the actions, so it won't mind.
Related
This is the method I'm using, pretty simple.
DailyCountTest: function (){
this.$store.dispatch("DailyCountAction")
let NewPatientTest = this.$store.getters.NewPatientCountGET
console.log(NewPatientTest)
}
The getter gets that data from a simple action that calls a django backend API.
I'm attempting to do some charting with the data so I need to assign them to variables. The only problem is I can't access the variables.
This is what the console looks like
And this is what it looks like expanded.
You can see the contents, but I also see empty brackets. Would anyone know how I could access those values? I've tried a bunch of map.(Object) examples and couldn't get any success with them.
Would anyone have any recommendation on how I can manipulate this array to get the contents?
Thanks!
Here is the Vuex path for the API data
Action:
DailyCountAction ({ commit }) {
axios({
method: "get",
url: "http://127.0.0.1:8000/MonthlyCountByDay/",
auth: {
username: "test",
password: "test"
}
}).then(response => {
commit('DailyCountMutation', response.data)
})
},
Mutation:
DailyCountMutation(state, DailyCount) {
const NewPatientMap = new Map(Object.entries(DailyCount));
NewPatientMap.forEach((value, key) => {
var NewPatientCycle = value['Current_Cycle_Date']
state.DailyCount.push(NewPatientCycle)
});
}
Getter:
NewPatientCountGET : state => {
return state.DailyCount
}
State:
DailyCount: []
This particular description of your problem caught my eye:
The getter gets that data from a simple action that calls a django backend API
That, to me, implies an asynchronous action and you might be getting a race condition. Would you be able to post a sample of your getter function to confirm my suspicion?
If that getter does indeed rely on an action to populate its contents, perhaps something to the effect of the following might do?
DailyCountTest: async () => {
await this.$store.dispatch('DailyCountAction')
await this.$store.dispatch('ActionThatPopulatesNewPatientCount')
let NewPatientTest = this.$store.getters.NewPatientCountGET
// ... do whatever with resulting array
}
You can also try with a computer property. You can import mapGetters
import { mapGetters } from 'vuex'
and later in computed properties:
computed: {
...mapGetters(['NewPatientCountGET'])
}
then you can use your NewPatientCountGET and it will update whenever the value changes in the store. (for example when the api returns a new value)
Hope that makes sense
My React code keeps triggering EventEmitter callbacks that should have been removed. When I inspect my React class that extends EventEmiiter, I found that the callbacks are still present in the events fields, even though removeListener has already been called on those callbacks.
I suspect this is happening because I add callbacks to the EventEmitter as arrow functions, but when I remove them, I do not. Removing them as arrow functions do not work. In my code below, FirebaseStore extends EventEmitter:
_onFirebaseChange() {
this.setState({
refId: this.getRefId()
});
}
componentDidMount() {
FirebaseStore.addChangeListener(() => this._onFirebaseChange());
}
componentWillUnmount() {
FirebaseStore.removeChangeListener(this._onFirebaseChange);
}
I need to use arrow functions because the change events (_onFirebaseChange), need access to this.state.
When I look at my source from Chrome dev tools, it's difficult for my to determine whether addChangeListener and removeChangleListener refer to the same callback:
key: 'componentDidMount',
value: function componentDidMount() {
var _this2 = this;
_FirebaseStore2.default.addChangeListener(function () {
return _this2._onFirebaseChange();
});
}
},{
key: 'componentWillUnmount',
value: function componentWillUnmount() {
_FirebaseStore2.default.removeChangeListener(this._onFirebaseChange);
}}
Here is the relevant code from Firebase store:
addChangeListener(callback) {
this.on(FIREBASE_CHANGE_EVENT, callback);
}
removeChangeListener(callback) {
this.removeListener(FIREBASE_CHANGE_EVENT, callback);
}
}
It would be possible to fix this with a closure, but I think that's overkill.
Update your componentDidMount method.
componentDidMount() {
FirebaseStore.addChangeListener(this._onFirebaseChange);
}
The function needs to be binded directly.
This was a simple fix that I should have caught earlier. The closure solution Alexi posted would have also worked, but all that was missing was to bind * _onFirebaseChange* to this in the constructor.
So in the constructor of my React class, I added this line:
this._onFirebaseChange = this._onFirebaseChange.bind(this);
Then my componentDidMount function was able to refer to the same callback as componentWillUnmount, while also being able to access this.state.
componentDidMount() {
FirebaseStore.addChangeListener(this._onFirebaseChange);
}
componentWillUnmount() {
FirebaseStore.removeChangeListener(this._onFirebaseChange);
}
is using the same reducer to update different parts of state an anti pattern?
Like my data reducer has a GET_DATA_DONE action, updates state.data and then in another instance you use fetch something else and call GET_DATA_DONE to update state.somethingElse?
Or would you do something like GET_SOMETHING_DATA_DONE & so on.. multiple diff actions doing the same thing? (Hardly DRY)
reducers.js
export const reducer = (state, action) => {
switch (action.type) {
case actions.GET_DATA_REQUESTED:
return { ...state, isLoading: true };
case actions.GET_DATA_DONE:
return { ...state, isLoading: false, data: action.payload };
case actions.GET_DATA_FAILED:
return { ...state, isLoading: false, isError: true }
default:
return state;
}
};
actions.js
export function getDataRequested() {
return {
type: 'GET_DATA_REQUESTED'
};
}
export function getDataDone(data) {
return {
type: 'GET_DATA_DONE',
payload: data
};
}
export function getDataFailed(error) {
return {
type: 'GET_DATA_FAILED',
payload: error
};
};
export function getDataEpic(action$) {
return action$.ofType(GET_DATA_REQUESTED)
.mergeMap(action =>
ajax.getJSON(action.url)
.map(response => getDataDone(response))
.catch(error => getDataFailed(error))
);
}
What't the best way to structure the app such that , getDataEpic acts like a api factory and the data returned from getDataDone(response) can be passed to another reducer to update a part of state based on the action, for example a cities action using getDataDone reducer dispatches another action which updates state.cities with the response?
Edit: I've made an app with rx-observable & redux calling 3 different api's but I ended up with a lot of duplicate code and just not happy with the solution, so i want to build a properly architectured app
Hope I was clear enough.
Thanks a lot!
I don't think it's an anti-pattern if deals with state of it's own children, but if used too much, it could certainly get you in trouble. Having it modify totally unrelated state is a definite anti-pattern. According to the docs the first line hits the nail on the head.
For any meaningful application, putting all your update logic into a single reducer function is quickly going to become unmaintainable.
We're not talking about 'all our data' but we are talking about all the data from a single API call.
It's not too complicated in regards to setting a loading flag but in a real world app would get significantly more complicated for a reducer that is both setting a loading flag, error flag and 'data'. And that is assuming we even know what the data being requested is.
In your example, if the intention is to create an API factory through reducers, we have to assume an API could return any number of different data structures, right now it could be a string or an int but what if it's a deeply nested Object? How would you access this data and differentiate it between another piece of data?
Let's say we have an app with this data structure for just the errors:
{
"errors": {
"byId": {
"1": {
"code": 500,
"message": "There was an internal server error"
},
"2": {
"code": 400,
"message": "There was another error."
},
"3": {
"code": 999,
"message": "Wow! weird error."
},
},
"all": ["1", "2", "3"]
}
}
I might have a byId reducer that returns a computed key with another reducer as the value.
byId = (state={}, action) => {
if (action.type === 'ADD_ERROR') {
...state,
[action.id]:error_reducer(state[action.id], action)
} else {
return state
}
}
error_reducer might look like
errorReducer = (state={}, action) => {
if (action.type === 'ADD_ERROR') {
code: action.code,
message: action.message
} else {
return state
}
}
I think it makes more sense to have errorReducer handle both code and message because we know that they are both mutually inclusive pieces of data where as each error is mutually exclusive (different ids) and so require their own reducer.
Another major advantage of this when dealing with real-world applications is that when the data is separated one action can update state across MANY different areas of our app. When reducers handle multiple pieces of state these pieces of tied state become harder to update.
There are many different patterns you can employ with your reducers and none of them are wrong however I have found this pattern to work very well for me and i've used it successfully in a quite complex production app.
Having said all that, one possible approach for your AJAX function is to write a generic action that accepts an object that contains your dispatches.
By using a library like redux-thunk you can execute multiple dispatches to update different parts of your state with different pieces of data. I won't explain redux-thunk here as I think it's beyond the scope of the question.
an example object might look like this:
{
getDataRequested: function () {
return {
type: 'GET_DATA_REQUESTED'
};
},
getDataFailed: function (error) {
return {
type: 'GET_DATA_FAILED',
payload: error
};
},
getDataDone: function (data) {
return {
type: 'GET_DATA_DONE',
payload: data
};
}
}
then you can pass this object of callbacks to your main AJAX function along with your API endpoint, REST request type etc.
I hope this helps.
This works, but I need to use mounted(){} to initiate the function which I think can be avoided but not sure how.
<script>
export default {
data () {
return {
domains: [],
}
},
methods: {
fetchDomains() {
let _this = this;
api._get({url: 'api/domains'})
.then(function (response) {
_this.domains = response.data;
})
}
},
mounted() {
this.fetchDomains()
}
}
</script>
This code doesn't work, but I like to do something like this. Initiating the function in data(){} itself.
<script>
export default {
data () {
return {
domains: this.fetchDomains(),
}
},
methods: {
fetchDomains() {
let data = [];
api._get({url: 'api/domains'})
.then(function (response) {
data = response.data;
})
return data
}
}
}
</script>
Thanks in advance.
Your first code snippet is the correct way to do it.
You can't initialize domains with the data from the API response because it is an async operation which may or may not be resolved successfully at some point in the future, well after the component is mounted. You might also want to do other things like keeping track of the async operation with a loading property which you set to true for the duration of the request.
Your component will initially be in a loading state which does not yet have any domains data, and you need to account for this. Show a loading spinner or something during this time.
I agree with Decade Moon that your first approach is the better way to do it (though you could use created instead of mounted).
The reason your second approach doesn't work is that you return an array and then replace the local variable's value with a different array. What you need to do is populate the array you returned.
new Vue({
el: '#app',
data() {
return {item: this.getItem()}
},
methods: {
getItem() {
let val = [];
setTimeout(() => {
const result = ['first','second','third'];
val.push(...result);
}, 800);
return val;
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">{{item}}</div>
I might be deviating slightly from the question (since it explicitly mentions the data property), but I think this might be helpful. Personally, if I want to provide some data with more complex logic I use the computed property. This is great in my opinion and you can read more about it in the docs. The problem in this case is that it doesn't work entirely as expected with asynchronous operations...
However, there is a lovely little module called vue-async-computed which can be found here. It solves this specific problem by providing an asyncComputed property and keeps the code really clean!
I really like the concept of having actions written like this:
function signUp(data, callback) {
return {
[CALL_API]: {
type: 'SOME_TYPE',
url: `/api/account/signup`,
method: 'POST',
data: data
}
}
}
But for thing like signUp I don't want to modify/touch the store to get a callback from server
In my React component I have an action that calls the api through an action and changes the state.
this.signUp($(form).serialize(), function(data) { this.setState({response: data}); }.bind(this))
and action signUp looks like this
function signUp(data, callback) {
postRequest(
'/api/account/signup',
data,
'POST',
callback)
}
function postRequest(url, data, method, callback) {
callback(true); //// just testing
}
As you can see the syntax and the length of code isn't pretty compared to the first one
Question: Is there a way to modify the redux middleware OR have an alternative JSON function (similar to CALL_API) to accept callbacks to component without touching the store? I really want to use the CALL_API syntax :)
You can write middleware to intercept actions and do some work without modifying the store's state.
// Action creator
function signUp(data, callback) {
return {
type: CALL_API
url: '/api/account/signup',
method: 'POST',
data: data,
callback: callback
}
}
// Middleware
const actionInterceptor = store => next => action => {
if (action.type === CALL_API) {
postRequest(action.url, action.method, action.data, action.callback);
}
else {
return next(action);
}
}
...
const store = createStore(reducer, initialState, applyMiddleware(actionInterceptor));
I really like the concept of having actions written like this:
...
But for thing like signUp I don't want to modify/touch the store to get a callback from server
Ok, but whats the point of using redux for this?
What I understood:
you would like to call dispatch(signUp(data, callback)) (simplified)
Your signUp actionCreator should look like this:
function signUp(data, callback) {
return function(dispatch) {
//.. do your stuff with data..
//.. and call your callback or pass it to another function
}
}
Sorry if misunderstood your problem, please write a comment if something is still unclear.