Some parts of my initial state will never change during the whole lifecycle of my app. Now I wonder if this kind of data also belong into the store?
If yes:
Is there a way to put this data in the initial state when calling createStore(), without having an (empty) corresponding reducer function? Because since the data never changes there's no need for a reducer, but combineReducers() is pushing me to have one, otherwise it throws this error:
Unexpected key "keyName" found in initialState argument passed
to createStore. Expected to find one of the known reducer keys
instead: "otherKey1", "otherKey2". Unexpected keys will be ignored.
Example of what I'm looking for:
var dataThatWillChange = function(state, action) { /* reduce */ };
var myApp = Redux.combineReducers({
dataThatWillChange: dataThatWillChange,
dataThatWillNeverChange: Redux.dummyReducer // <-- something like this?
});
var store = Redux.createStore(myApp, {
dataThatWillChange: [0, 1, 2],
dataThatWillNeverChange: { createdBy: "me" } // <-- no need for a reducer
});
you could just have a reducer that always returns the initial state, there is nothing wrong with that:
var initialState = { createdBy: "me" }
var dataThatWillNeverChange = function(state=initialState, action) {
return state;
};
var store = Redux.createStore(myApp, {
dataThatWillChange: DataThatWillChange,
dataThatWillNeverChange: dataThatWillNeverChange
});
or more compactly:
var initialState = { createdBy: "me" }
var store = Redux.createStore(myApp, {
dataThatWillChange: DataThatWillChange,
dataThatWillNeverChange: () => initialState
});
If your constant data doesn't really belong to the "application state", you could also consider exporting it from some module and importing it whenever you need it, like
export default {
createdBy: "me",
...
}
Related
I am using the aurelia-store state management library for managing state. This question is not specific to Aurelia store, but actually to redux best practices in general since Aurelia store is very much the same thing.
I have an action that fetches unit updates from an API like so:
export const fetchNewUnits = async (state: State): Promise<State> => {
const fetchedUnits = await apiClient.getUnitsMarkers();
// no new updates so don't trigger change in units
// IS THIS ACCEPTABLE?
if (fetchedUnits.length === 0) {
return {
...state,
highwaterMark: new Date()
};
}
const units: UnitMarker[] = state.units.slice();
_.forEach(fetchedUnits, (newUnit) => {
// look for matching unit in store
const idx = _.findIndex(units, {
imei: newUnit.imei
});
// unit was found in store, do update
if (idx !== -1) {
// replace the unit in the store
const replacement = new UnitMarker({...newUnit});
units.splice(idx, 1, replacement);
}
});
// OR SHOULD I ALWAYS DEEP COPY THE ARRAY REFERENCE AND IT'S OBJECTS
return {
...state,
highwaterMark: new Date(),
units: [...units]
};
};
If I do not have any unit changes (i.e. my store is up to date) can I simply return the state with the spread operator as shown in the first return statement? Is this fine since I did not modify the objects?
Or do I always have to do deep replacements such as:
return {
...state,
highwaterMark: new Date(),
units: [...state.units]
};
even if the objects in the array did not change?
The reason why you’re supposed to create a new object is because React components check for prop changes in order to know when to re-render.
If you simply modify an object and pass it in as a prop again, React won’t know that something changed and will fail to rerender.
So in your case, the question is: do you want to rerender, or not? If you don’t, returning the same object is fine and a simple ‘return state’ will let React know that no rerenders are necessary.
See: Why is the requirement to always return new object with new internal references
How can I assign an entire array to my intialState object using RTK?
Doing state = payload or state = [...state, ...payload] doesn't update anything.
Example:
const slice = createSlice({
name: 'usersLikedPosts',
initialState: [],
reducers: {
getUsersLikedPosts: (state, { payload }) => {
if (payload.length > 0) {
state = payload
}
},
},
})
payload looks like this:
[
0: {
uid: '1',
title: 'testpost'
}
]
update
Doing this works but I don't know if this is a correct approach. Can anyone comment?
payload.forEach((item) => state.push(item))
immer can only observe modifications to the object that was initially passed into your function through the state argument. It is not possible to observe from outside the function if that variable was reassigned, as it only exists in the scope within the function.
You can, however, just return a new value instead of modifying the old one, if you like that better. (And in this case, it is probably a bit more performant than doing a bunch of .push calls)
So
return [...state, ...payload]
should do what you want.
I have hashMap in my redux store, I want change isChecked value for children id: 2. Is it good to make it on state like this (operating on state)?
My hashMap
const childrens = {
1: { name: "Test", isChecked: false },
2: { name: "test2", isChecked: false }
};
Here is my reducer
export const childrensReducer = (state = childrens, action) => {
switch (action.type) {
case "SELECT_CHILDREN":
const id = 2;
state[id].isChecked = !state[id].isChecked;
return { ...state };
}
};
The problem is that you are mutating the state in the reducer with this line:
state[id].isChecked = !state[id].isChecked;
Why immutability is required by redux can be found in official docs:
https://redux.js.org/faq/immutable-data
One way to do is: ( I expect you send id through action.id )
case "SELECT_CHILDREN":
return {
...state,
[action.id]: {
...state[action.id],
isChecked: !state[action.id].isChecked
}
};
These kind of state operations are easier when an array is used for state.
It's not a good practice to mutate the state like you did.
There are different approaches of changing the state. Take a look at the below link to get some more information and examples.
https://www.freecodecamp.org/news/handling-state-in-react-four-immutable-approaches-to-consider-d1f5c00249d5/
Its not a good practise to mutate state, since react depends on immutability for a lot of its features.
Consider for example lifecycle methods or rerender after comparing state/props(PureComponents)
The problem with mutating state is that when these values are passed as props to children and you try to take some decision on them based on whether the state has updated, the previous props and the current props both will hold the same value and hence the comparisons may fail leading to buggy application
The correct way to update state is
case "SELECT_CHILDREN":
const id = 2;
return {
...state,
[id]: {
...state[id],
isChecked: !state[id].isChecked
}
};
I've got one view displaying some pictures published by users with some data (let's image Instagram).
I already have these pictures as non-reactive data (otherwise you could see many updates) but these images have one button to like the picture. If I have this as non-reactive data I can't see when I click on "Like" the filled heart (I need to refresh).
This is my subscribe function:
this.subscribe('food', () => [{
limit: parseInt(this.getReactively('perPage')),
//skip: parseInt((this.getReactively('page') - 1) * this.perPage),
sort: this.getReactively('sort')
}, this.getReactively('filters'), this.getReactively('searchText'), this.getReactively('user.following')
]);
And this is my helper:
food() {
const food = Food.find({}, {reactive: true}, {
sort: this.sort
}).fetch().map(food => {
const owner = Meteor.users.findOne(food.owner, {fields: {username: 1, avatarS: 1, following: 1}});
food.avatarS = owner && owner.avatarS;
food.username = owner && owner.username;
if (food.likes.indexOf(Meteor.userId()) == -1) {
// user did not like this plate
food.liked = false;
} else {
// user liked this plate
food.liked = true;
}
return food;
});
}
Is possible to have a non-reactive model but with some reactive properties on it?
I'm using Angular 1.X with TS btw
Thanks in advance!
PS: is it normal that this works as non-reactive when I change reactive to true?
Modification to your code:
//console.log(food.likes);
this.subscribe('reactiveFoodData', {ownerId: food.owner, userId: Meteor.userId()}).subscribe(()=>{
console.log(this.user);
});
// THIS IS THE PUBLISH METHOD LOCATED IN THE SERVER SIDE:
Meteor.publish('reactiveFoodData', function(params: {ownerId:string, userId:string) {
const owner = Meteor.users.findOne(params.ownerId);
if (!owner) {
throw new Meteor.Error('404', 'Owner does not exist');
}
let result = {};
result.avatarS = owner.avatarS;
result.username = owner.username;
const food = Food.find({});
result.liked = !(food.likes.indexOf(params.userId) == -1);
return result;
});
You have few problems:
1. The reactive flag is true by default, you do not need to set it.
2. The function find is accepting only two arguments, not 3.
Should be:
const food = Food.find({}, {reactive: true, sort: this.sort})
If you need some, subset of data to be reactive only (from some collection). You could create a specific Method (which udpates only "likes").
https://guide.meteor.com/methods.html
UPDATE:
Here is how you write a method with return parameter (check two examples, with Future and without):
How to invoke a function in Meteor.methods and return the value
UPDATE2:
You have lost reactivity when you used fetch(). Because you moved from reactive cursor to just simple array over which you map values. Do not expect reactivity after fetch(). If you want fetch or do not want to use Cursors, you could wrap the find inside Tracker.autorun(()=>{}) or utilize publish/subscribe.
Note: But be careful, if you somehow manage to get "empty" cursor in find(), your Tracker.autorun will stop react reactively. Autorun works only if it has something to watch over.
The main point with method, is that if you want to have one time non-reactive action for something. You define the method on server:
Meteor.methods({
myMethod: ()=> {
return "hello";
}
});
And you can call it from client with:
Meteor.call('myMethod', (error, result) => {
console.log(result); // "hello"
});
Instead of working with pure collections. You could start using publish/subscribe. On server you publish 'likes' and on client you just listens to this new reactive view. E.g.,
Meteor.publish('likes', (options: {owner: string, likes: Array<any>}) => {
let result: any = {}
const owner = Meteor.users.findOne(options.owner, username: 1, avatarS: 1, following: 1}});
result.avatarS = options.owner && options.owner.avatarS;
result.username = options.owner && options.owner.username;
result.liked = !(options.likes.indexOf(Meteor.userId()) == -1)
return result;
});
On client side: Meteor.subscibe('likes', {food.owner, food.likes}).subscribe(()=>{});
This is just off the top of my head.
Have you tried looking at Tracker ? https://docs.meteor.com/api/tracker.html
But more specifically the method Tracker.nonreactive
https://docs.meteor.com/api/tracker.html#Tracker-nonreactive
I'm new to using redux, and I'm trying to set up redux-promise as middleware. I have this case I can't seem to get to work (things work for me when I'm just trying to do one async call without chaining)
Say I have two API calls:
1) getItem(someId) -> {attr1: something, attr2: something, tagIds: [...]}
2) getTags() -> [{someTagObject1}, {someTagObject2}]
I need to call the first one, and get an item, then get all the tags, and then return an object that contains both the item and the tags relating to that item.
Right now, my action creator is like this:
export function fetchTagsForItem(id = null, params = new Map()) {
return {
type: FETCH_ITEM_INFO,
payload: getItem(...) // some axios call
.then(item => getTags() // gets all tags
.then(tags => toItemDetails(tags.data, item.data)))
}
}
I have a console.log in toItemDetails, and I can see that when the calls are completed, we eventually get into toItemDetails and result in the right information. However, it looks like we're getting to the reducer before the calls are completed, and I'm just getting an undefined payload from the reducer (and it doesn't try again). The reducer is just trying to return action.payload for this case.
I know the chained calls aren't great, but I'd at least like to see it working. Is this something that can be done with just redux-promise? If not, any examples of how to get this functioning would be greatly appreciated!
I filled in your missing code with placeholder functions and it worked for me - my payload ended up containing a promise which resolved to the return value of toItemDetails. So maybe it's something in the code you haven't included here.
function getItem(id) {
return Promise.resolve({
attr1: 'hello',
data: 'data inside item',
tagIds: [1, 3, 5]
});
}
function getTags(tagIds) {
return Promise.resolve({ data: 'abc' });
}
function toItemDetails(tagData, itemData) {
return { itemDetails: { tagData, itemData } };
}
function fetchTagsForItem(id = null) {
let itemFromAxios;
return {
type: 'FETCH_ITEM_INFO',
payload: getItem(id)
.then(item => {
itemFromAxios = item;
return getTags(item.tagIds);
})
.then(tags => toItemDetails(tags.data, itemFromAxios.data))
};
}
const action = fetchTagsForItem(1);
action.payload.then(result => {
console.log(`result: ${JSON.stringify(result)}`);
});
Output:
result: {"itemDetails":{"tagData":"abc","itemData":"data inside item"}}
In order to access item in the second step, you'll need to store it in a variable that is declared in the function scope of fetchTagsForItem, because the two .thens are essentially siblings: both can access the enclosing scope, but the second call to .then won't have access to vars declared in the first one.
Separation of concerns
The code that creates the action you send to Redux is also making multiple Axios calls and massaging the returned data. This makes it more complicated to read and understand, and will make it harder to do things like handle errors in your Axios calls. I suggest splitting things up. One option:
Put any code that calls Axios in its own function
Set payload to the return value of that function.
Move that function, and all other funcs that call Axios, into a separate file (or set of files). That file becomes your API client.
This would look something like:
// apiclient.js
const BASE_URL = 'https://yourapiserver.com/';
const makeUrl = (relativeUrl) => BASE_URL + relativeUrl;
function getItemById(id) {
return axios.get(makeUrl(GET_ITEM_URL) + id);
}
function fetchTagsForItemWithId(id) {
...
}
// Other client calls and helper funcs here
export default {
fetchTagsForItemWithId
};
Your actions file:
// items-actions.js
import ApiClient from './api-client';
function fetchItemTags(id) {
const itemInfoPromise = ApiClient.fetchTagsForItemWithId(id);
return {
type: 'FETCH_ITEM_INFO',
payload: itemInfoPromise
};
}