I'm trying to pass two variables (strings) into a redux action. The purpose of this is the component knows the ID of the Company Profile it's trying to look up and we are getting that data using redux because this pattern is used in a couple places.
In the component:
componentWillMount() {
this.props.actions.getObject('Company', '16747fce-f0b1-422b-aa60-220c2dccac58')
}
In the action:
export function getObject(dataClass, dataId) {
return {
'BAQEND': {
types: [
GET_OBJECT,
GET_OBJECT_SUCCESS,
GET_OBJECT_FAILURE
],
payload: (db) => db.(dataClass).load(dataId)
}
}
}
The dataClass is Company and the dataID is a guid.
My issue is I can't get the dataClass variable in there w/o throwing an error. dataID works great. If I were to change payload: (db) => db.Company.load(dataId) it works.
I've tried a couple things that did not work:
payload: (db) => db.dataClass.load(dataId)
payload: (db) => db.`${dataClass}`.load(dataId)
`payload: (db) => db.${dataClass}.load(dataId)`
If i understand correctly, db is an object which holds a key (property) of Company
db = {
Company: 'someGuid'
}
If this is the case, then you can use the Property accessors like this:
db['Company'] Or db[dataClass]
Related
Suppose that I have these 2 actions (as an example) for "creating category" and "loading all categories". I need to load all categories every time I create a new category successfully, so, I need to call "loadAllCategories" action within "createCategory". I usually do that like this while using TypeScript with Redux:
// Loading all categories
export const loadAllCategories = () => async (dispatch: Dispatch) => {
try {
// omitted for brevity
dispatch<ILoadAntdTreeSelectCompatibleCategoriesAction>( {
type: TaxonomyActionTypes.LOAD_ANTD_TREESELECT_COMPATIBLE_CATEGORIES,
payload: {
catTreeSelectLoading: false,
catTreeSelectRegistry
}
})
} catch (error) {
// omitted for brevity
}
}
// Creating a category
export const createCategory = (taxonomy: ITaxonomy) => async (dispatch: Dispatch) => {
try {
await agent.Taxonomies.create(taxonomy);
dispatch<any>(loadAllCategories()); <--- Dispatching above action within this one
dispatch<ICreateCategoryAction>({
type: TaxonomyActionTypes.CREATE_CATEGORY,
payload: {
loadingInitial: false
},
})
} catch (error) {
// omitted for brevity
}
}
I wanted to know, using dispatch with "any" type is the only way to call another action within the current one or there is a better way of doing that?
Could I use a more specific type instead of "any"?
Needless to say without using dispatch(action), just by calling the action's name it doesn't change the state so we have to use dispatch.
What is the best practice for doing that?
There is a simpler way to do this when you create a category lets say you use an API for that, make that API return the value you added, in response, then add that category to category list in Redux. use the following function in the reducer.
const addToList = (oldList:any, doc:any) => {
let newList:any = oldList;
newList.push(doc);
return newList;
}
and in the reducer function call it like
case TaxonomyActionTypes.CREATE_CATEGORY:
return { ...state, categories: addToList(state.categories, action.payload) }
Edit
The Answer to your question is
dispatch<Array>
Example
interface Category {
name: String,
}
let x:Array<Category>
I have this Redux action, which looks up a user and returns all the items they have:
export const itemsFetch = () => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items`)
.on('value', snapshot => {
dispatch({ type: ITEMS_FETCH_SUCCESS, payload: snapshot.val() });
});
};
};
Works great, and each item returned has a unique key associated with it.
I want to modify this action to look up specific items, which I've done. That works fine too:
export const itemLookup = uid => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items/${uid}`)
.on('value', snapshot => {
dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: snapshot.val() });
});
};
};
This also works fine, but I can only use this to lookup a single item.
I want to loop over an array of item ids, and lookup details for each. Doing this from a component, and using mapStateToProps, causes the component to rerender each time, losing the previous lookup in the process.
Is it best to loop over the ids I have at a component level, and make multiple calls. Or should I pass the array to the action, and somehow loop over them within the action?
Thanks
I feel like I'm doing something dumb, or misunderstanding Redux completely.
In my opinion, this is one of the few limitations that firebase has (along side with queries) that sometimes make me want to grow hair again and lose it (I am bald).
I am more experienced with Firestore although I have used Database, but I think you are correct that you can only request one item in Firebase. What I would do to solve this, is to create a new action that receives an array of IDs and then executes and array of promises that will query each doc.
Something like (pseudo code, and you might need to wrap your firebase call into a promise):
let promises = [];
arrayIds.forEach(id => {
promises.push(firebase.database().ref.get(id))
})
return Promise.all(promises).then(dispatch(results))
Now, if you find that the amount of results are usually not a lot, it is totally fine (and usually the way Firebase requires you to) to complete the data filtering in the client.
Using the response from sfratini, I managed to work it out. Here's the action:
export const itemLookup = oid => {
const { currentUser } = firebase.auth();
const items = []
return dispatch => {
new Promise(function (res, rej) {
oid.forEach(id => {
firebase.database().ref(`users/${currentUser.uid}/items/${id}`).on('value', snapshot => {
items.push(snapshot.val())
})
})
}).then(dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: items }))
}
I used the same reducer, and now the items array makes it down to component level. Perfect!
I'm developing a react web application with firebase and Now I'm stuck with this problem. So what I want is update the collection with existing data. For example let's say that the following details are already in the collection
org_details: {
general: {
org_name: "ane",
founder: "fng"
},
group:{
admin:{
<details of the admin grp>
},
standard:{
<grp details>
}
}
}
So what I want is add another group details with the existing groups. For an example I need to add a group called "fun" by also having admin and standard group. So this is the firebase query that I've tried.
export const createGroup = (data, history) => async (
dispatch,
getState,
{ getFirestore }
) => {
const firestore = getFirestore();
const { email: userEmail } = getState().firebase.auth;
const groupName = data.groupName;
dispatch({ type: actions.CREATE_GROUP_START });
try {
firestore
.collection("org")
.doc(userEmail)
.update({
"group": {
groupName: {
org_grp_admin: data.org_grp_admin,
org_grp_users: data.org_grp_users
}
}
})
.then(() => {
getOrgData(dispatch, getState, { getFirestore });
history.push("/groupmanagement");
});
dispatch({ type: actions.PROFILE_EDIT_SUCCESS });
} catch (err) {
dispatch({ type: actions.CREATE_GROUP_FAILS, payload: err.message });
}
};
But this query doesn't seem to behave like that I want. It always create a group called "groupName" instead of the group name that is passed from the parameter and always replace the existing data. How should I change the query to get the result that I want?
And I'm using firestore in firebase as the database.
As you are trying to dynamically assign the property key you need to wrap it in [groupName]:{...} to pick up the variable groupName instead of just the string 'groupName'
"group": {
[groupName]: {
org_grp_admin: data.org_grp_admin,
org_grp_users: data.org_grp_users
}
}
As for performing a deep merge (e.g. just update the subgroup if it exists, and if not create the new group without deleting others), this is not possible with the current api however you could either
option 1) Read the data from the database first yourself and manually handle the merge before writing (you could either write your own function or use a package like deepmerge
option 2) Restructure your data to be flatter, for example using a subcollection to store your groups
I am creating a Graph a React.Component. When componentWillMount, this graph will be in charge of loading a configuration file using an Async Actions. Configuration file also contains a query information that needs to be fetched.
For now, I am chaining the two requests (configuration + query) and store their results in the current Redux state.
{
configurations: [{
"my-configuration":
{
// information about the configuration
query: "my-query"
}
}],
queries: [{
"my-query" : {
// information about the query
}
}]
}
I'd like my Graph component to be connected to both variables. However, I am not aware of the query name before the configuration is fetched.
const mapStateToProps = (state, ownProps) => ({
configuration: state.getIn([
"configurations",
ownProps.configurationName
]),
query: state.getIn([
"queries",
queryName // the name of the query comes is known in the configuration
]),
});
I might face a design issue, but I wanted to have your feedback. How would you deal this situation ?
For now, I created a state for the component but it would need to be in sync with the redux state.
Environment
react#15.3.1
redux#3.6.0
redux-thunk#2.1.0
Edit: what if myConfig is null and waiting to be fetched from server?
Selector becomes
const mapStateToProps = (state, ownProps) => {
const myConfig = state.getIn([
"configurations",
ownProps.configurationName
]);
return {
configuration: myConfig,
query: myConfig ? state.getIn([
"queries",
myConfig.queryName
]) : *defaultQuery*,
};
};
And you should handle getting myConfig in async action.
const getMyConfig = (...args) => (dispatch) => {
dispatch(GET_MY_CONFIG_REQUEST);
api.getMyConfig(...args)
.then((res) => dispatch({ type: GET_MY_CONFIG_SUCCESS, res }))
.catch((err) => dispatch({ type: GET_MY_CONFIG_FAIL, err }));
}
And in reducer need to update myConfig upon GET_MY_CONFIG_SUCCESS action
...
case GET_MY_CONFIG_SUCCESS:
return { ...state, myConfig: action.res };
...
Original Answer
something like this?
const mapStateToProps = (state, ownProps) => {
const myConfig = state.getIn([
"configurations",
ownProps.configurationName
]);
return {
configuration: myConfig,
query: state.getIn([
"queries",
myConfig.queryName
]),
};
};
Reading this nice article about things you should know about Redux, here is what you will find:
Denormalizing data in mapStateToProps is normal. When I first started using Redux, I wasn’t sure if it was kosher to do “computation” in mapStateToProps functions. I didn’t see examples of this in the Redux repo or docs. It took a few weeks for me to find somebody else using a “selector” in mapStateToProps. You have no idea how excited I was to discover somebody else doing this!
Hope this could help someone else!
For last two weeks I have been working with redux and I'm facing an issue where I want to access/change a state value of another reducer. How can I achieve that?
For example: I have two components 'A-Component' and 'Message-component'
which has 'A-actions', 'Message-actions' and 'A-reducer', 'Message-reducer' respectively
When an action of 'A-Component' is called it will call the corresponding reducer function where I need to update the Message-reducer state value which will display the message box
A-action
export function add(data) {
return {
types: [types.ONADD, types.ONADDSUCCESS, types.ONADDFAIL],
payload: {
response: api.add(data).then(response => response),
data
}
};
}
A-reducer
export default createReducer(initialState, {
[types.ONADD](state) {
return {
...state,
message: 'Updating Records'
};
}
});
The above mentioned message state value is message reducer's state value. I want to update the message state value from A-reducer
which in turn updates the message component. Is this possible in redux?
I tried with various middleware but failed.
Thank in advance!
I think you're approaching this the wrong way. You should normalize your data as much as you can, and then maybe use the connect decorator to compose the state you need for your UI. For example, Messages could be nested under a "Friend"'s node, but it's better to have them in their own store, and then make a function that selects the messages from a friend based on a relationship. This gives you aggregations (You have 3 unread messages) for free. Take a look at reselect for a way to do this in a nice (and cached) way.
Edit:
You could write middleware which dispatches multiple actions:
export default (store) => (next) => (action) => {
if(!action.types){
return next(action);
}
action.types.forEach(type => {
next({
type,
payload: action.payload
})
});
}
Then call it from an Action Creator like so:
export function addMessage(message){
return {
types: ['ADD_MESSAGE', 'UPDATE_USER'],
payload: message
}
}
If you already have a update action in Message-actions
I think you can just directly dispatch the update action when ONADDSUCCESS is triggered.
// Message action
export function MessageUpdate (data) {
return {
type: ...,
data,
}
}
// A action
export function add(data) {
return dispatch => {
dispatch({
type: types.ONADD
});
// code for your add event
api.add(data).then( response => {
(() => {
dispatch(MessageUpdate(response));
return dispatch({
type: types.ONADDSUCCESS,
})
})()
});
}
}
Hope this answer to your question.