I know the answer to this question might be obvious. But, I still want to have a clear answer.
In Redux, it says: "The state of your whole application is stored in an object tree within a single store".
Let's say we have the following data:
According to the Redux doc, all the data of "todos" should be placed in the store. In the section "Designing state shape", it also says "the minimal representation" of the app.
{
todos: [
{id: 1, description: "Seal the wall"}
{id: 2, description: "Wash the car"}
]
}
If there are only two operations on "todos" like "Add" and "Delete", should the redux store only keep the following data?
{
todos: [1, 2]
}
Operations(Actions) that you perform on your state are completely separate from the state store. The store does not and should not care about what actions are going to happen on it.
So no, the store should not be:
{
todos: [1, 2]
}
and should be:
{
todos: [
{id: 1, description: "Seal the wall"}
{id: 2, description: "Wash the car"}
]
}
Related
I'm building a program in Angular/Firestore that will allow users to create forms with questions, (similar to typeform) and then other people can fill out those questions.
The issue I'm having is that admin needs to be able to filter by the dynamically created question answer values.
Here is an example of the data structure:
Form collection:
{
id: 123,
questions: [
{
id: 0,
value: "How many students did you teach today?"
},
{
id: 1,
value: "What school were you at?"
},
{
id: 2,
value: "What was the date?"
},
]
}
UserSubmissions
{
formId: 123,
questions: [
{
id: 0,
value: 10
},
{
id: 1,
value: 'Random school name'
},
{
id: 2,
value: 2021/07/12
},
]
}
So let's say we had 100 form submissions and admin wanted to order by how many students were taught, how could I do something like .orderBy("formSubmissions.questions[0].value") or .orderBy(formSubmissions.questions.0.value").
The second one works, but there would need to be a composite index created for an unknown amount of questions. I could technically have it so you can only order by the first 10 questions or something but I feel like there has to be a better way to doing this. I don't mind restructuring data if I have to.
If you want to order on specific questions, consider creating a subcollection of questions under each form submission. Trying to order/filter on specific array elements is going to either be difficult, not scale beyond 10 items, or both.
I have a functional component, that is passed instructions on what to pull from the redux store.
Using mapStateToProps=(state, ownProps), I can happily pull the required items from state (store) - but, at a cost of any changes in the entire state tree triggering rerunning mapStateToProps and a gazillion rerenders.
Let me unpack.
Here's a snapshot of part of the store:
{
settings: {...stuff...},
projects: [...stuff...],
definitions: [...stuff...],
themes: [...stuff...],
surfaces: {
'6': { <--- VARIABLE PASSED TO COMPONENT
surface: {
STRIP: [..stuff..],
GLOBAL: { <--- CATEGORY PASSED TO COMPONENT
DISPLAY: {...stuff...},
ASSIGNMENT: { <--- LIST OF REQUIRED OBJECTS HAS
A_TRACK: { SUBCATEGORY AND TARGET (A_TRACK etc...)
value: 0,
type: 'switch',
label: 'TRACK'
},
A_SEND: { <--- ANOTHER OBJECT I NEED TO GET
value: 0,
type: 'switch',
label: 'SEND'
},
A_PAN: {
value: 0,
type: 'switch',
label: 'PAN'
},
},
FADER_BANKS: {...stuff...},
STATUS: {...stuff...},
LOTS_MORE_STUFF
My parent component passes the required instructions to the child.
<RefMixerGroup
portId = {this.props.portId}
items={[
{parent: 'GLOBAL', group: "ASSIGNMENT", target: "A_TRACK"},
{parent: 'GLOBAL', group: "ASSIGNMENT", target: "A_SEND"},
]
}
/>
mapStateToProps is pretty simple:
const mapStateToPropy = (state, ownProps) => {
return {
groupItems: getItemsFromState(state.surfaces[ownProps.portId].surface, ownProps.items)
}
}
and the work is done in a simple function:
const getItemsFromState = (subState, items)=>{
let groupItems=[]
for (let i = 0; i < items.length; i++) {
const item = items[i];
const base = subState[item.parent];
let groupItem = base[item.group][item.target]
groupItems.push({...groupItem, target: item.target})
}
return groupItems
}
But because I am creating this array of matches, I think redux thinks I should be subscribing to every item in the tree...when I only want changes on the found elements, in this case:
surfaces[6].surface[GLOBAL][ASSIGNMENT][A_TRACK]
surfaces[6].surface[GLOBAL][ASSIGNMENT][A_SEND]
I tried using reselect and the rereselect instead of my getItemsFromState function above,
but all with the same result. Any change in that tree, starting with surfaces[6] triggers mapsStateToProps and a rerender.
There must be way around this, but I can't figure it out. I tried using areStatesEqual but it only provides nextState and prevState, and I need ownProps to compute equality. I possibly could use areStatePropsEqual, but that only works AFTER recomputing mapStateToProps unnecessarily.
There must be a way!
getItemsFromState is creating a new groupItems array reference every time it runs. It will be called after every dispatched action. Since connect re-renders any time any of the fields returned by mapState have changed to a new reference, your code is forcing React-Redux to re-render every time.
This is specifically why you should use memoized selectors to only return new derived data references if the input references have changed, typically with Reselect's createSelector. If your use of Reselect isn't helping here, it's likely that your selectors aren't being set up correctly, but I'd need to see specific examples to give advice there.
It's also why components should subscribe to the smallest amount of data that they actually need.
If you are using a function component, I'd suggest using useSelector instead of connect as well.
I have 2 actions that have the same payload and only differ in the type. Is there a way to combine the two actions such that I don't have redundant code?
let getData = {type: 'GET_DATA', payload: {id: 1, name: 'sam'}}
let dataSuccess = {type: 'DATA_SUCCESS', payload: {id: 1, name: 'sam'}}
You should ask question to yourself first that why did you create two separate actions if they are doing the same thing. type is only a string to distinguish different actions. If you really need to create two different actions in your case, you can't combine them of course. The only thing you can do probably is to generalize the payload.
I have a Redux application that shows a list of posts. The state is more or less this:
{
posts: [
{id: 1, title: 'My Post'},
{id: 2, title: 'Also this one'},
{id: 3, title: 'Other Post'}
],
visible_post_ids: [1, 2]
}
Whenever I load some posts I add them to posts, then I replace the content of visible_post_ids.
This is my action creator for loading posts:
function loadPosts (filters) {
return function (dispatch, getState) {
return fetch(`/posts.json?filters=${filters}`)
.then((response) => response.json())
.then((posts) => {
dispatch(postsLoaded(posts)) // Will update `posts`
const postIds = posts.map((post) => post.id)
dispatch(updateVisiblePosts(postIds)) // Will update `visible_post_ids`
})
}
}
My question is: is it idiomatic to dispatch two (or more) events from a thunk? Or should I dispatch only one and handle it in various reducers?
Quick answer : there is no problem to dispatch two or more actions from a thunk, I think it's a good practice,especially if API Call response contains answers to two completely different concerns.
I think it depends what you are trying to represent, in your case you can have one action that represent an add of new posts and two different reducers can catch it and do different tasks with it.
But you can see that as two different actions (your example) and it's great too.
As Sergey L said, in your case with a unique action (for your case) it can create an interesting "dependency"
If you don't consider scenario when it is possible to postsLoaded without calling updateVisiblePosts, it is better to handle the state change just in postsLoaded.
Especially if you need them to be in sync. For example, if you need a grantee that visible_post_ids does not contains Ids from not existing/loaded posts. Besides it minimizes the updates as each dispatch will cause processing in React.
On the other hand, having these actions separate can make code more clear as you have very simple implementation for each action.
I set up the aggregation rule:
{{ object.experienceId }}
on a notification feed in getstream.io expecting it to aggregate based on the object.experienceId, but instead it seems to aggregate everything into one, regardless of object.experienceId. Am I mis-understanding how aggregation works? What could be the issue?
var activity = {
time: new Date(),
verb: 'created',
actor: { id: 1, name: 'User One' },
object: {
id: 2,
experienceId: 12,
caption: 'Moment 1',
photo:
{ id: '314e00a2-2455-11e5-b696-feff819cdc9f',
mainColor: 'ff3333',
width: 1000,
height: 400 },
createdBy: {
id: 1, name: 'User One'
},
type: 'Moment' },
context: 'http://derbyapp.co'
};
notifications.addActivity(activity,
The reason why this is not working is because the object field is expected to be a string (http://getstream.io/docs/#add-remove-activities), thus within the aggregation rule you can not reference properties of activities object field. There are multiple solutions to this problem.
First you could supply the experienceId as a separate property of the activity object, so you can use the aggregation template {{ experienceId }}, since all the additional properties provided to an activity can be used in the aggregation rule (http://getstream.io/docs/#aggregated-feeds).
Second you could supply an object on any additional field of the activity, for instance item. Additional fields can reference their child properties thus you could use aggregation rule {{ item.experienceId }}. But beware not to send data to the getstream.io API that is not actually needed at getstream.io's end, in this example you could also send the object's id field, instead of the entire object, and retrieve the object from your local database once you retrieve activities from the API (The same holds for the actor field). If you do not want to take care of the logic needed for this you could use one of getstream's integration libraries (there are libraries for rails/django/laravel etc.).
var activity = {
time: new Date(),
verb: 'created',
actor: 1,
object: '1',
experienceId: 12
};