technologies: using react, Redux, Redux Form (FieldsArray), MongoDB.
I have a list of entities and I want to do the following:
I want to create an entity -> get back from server an entity with _id --> update store with then entity and the _id.
How do I do that?
actions.js
export function createEntity(entity) {
return function (dispatch) {
dispatch(createEntityStart());
return axios.post(
'http://localhost:3000/api/users',
entity,
)
.then(function (response) {
dispatch(createEntitySuccess(response.entityWithId));
}).catch(function (response) {
dispatch(createEntityError(response.data));
});
};
}
I have done the fields.push({}) - Got a new entity in the component.
Now I would like to post the new entity which follow a returned entity (with id).
I now need to save the entity with Id somewhere in the store.
How it should be done?
I got in the store:
form
MyFormName
values
initial
registeredFields
If you are looking to store it in redux-form's state, there are a couple options available. You can look at using redux-form's Action Creators to manipulate its state. Or you can add a plugin into its form reducer (this is a super simplified example):
const reducers = {
form: formReducer.plugin({
entityForm: (state, action) => {
switch(action.type) {
case CREATE_ENTITY:
return {
...state,
values: {
action.payload
},
registeredFields: {
...state.registeredFields,
}
}
default:
return state
}
}
})
}
You can find more details about redux-form plugins at http://redux-form.com/7.0.3/docs/api/ReducerPlugin.md/
Another option, if you don't want to save it in redux-form's state, is to just create your own state object within redux that stores the entity information.
Related
I have a Trip form that contains an antD Select component. One of the options in this list of Venues has a value of -1 and is labelled * New *. When this is selected a couple of inputs are then displayed and when their Form is submitted I use Redux to populate the DB via an API call. This all works well.
However, once a new Venue is created, I want the Venue Select component to automatically change to the newly created Venue. My code is as follows:
When the Venue Management Save button is clicked, this method is called in a container component:
function handleVenueSave(event) {
setSaving(true);
props.actions
.saveVenue(venue) // 'venue' is the object we are creating
.then(() => {
setSaving(false);
handleLoadVenuesByUser(); // Reloads the list in the Select
})
}
saveMenu is called from the venuesActions and looks as follows:
export function saveVenue(venue) {
//eslint-disable-next-line no-unused-vars
return function (dispatch, getState) {
dispatch(beginApiCall());
return venuesApi
.saveVenue(venue)
.then((savedVenue) => {
dispatch({ type: types.CREATE_VENUE_SUCCESS, savedVenue});
})
.catch((error) => {
dispatch(apiCallError(error));
throw error;
});
};
}
The API call is made via venuesApi.saveVenue(venue) and it returns the newly created venue along with its new Id from the database.
Then in the reducer:
const saveNewVenueSuccess = (state, action) => {
return { ...state, newVenueId: action.venue[0].Id };
};
export default function venuesReducer(state = initialState.venue, action) {
switch (action.type) {
case types.CREATE_VENUE_SUCCESS:
return saveNewVenueSuccess(state, action);
default:
return state;
}
}
As you can see, I am passing the newly created Id to state.venue.newVenueId (which is defulted to -1).
However, when I try to reference this state as part of the save process it is always one value behind. For example, in mapStateToProps I am getting this state value:
newVenueId: state.venues.newVenueId,
and if I try refferencing it in the original Save function that called the action, it will first come back as -1:
At this point, I check the database and it has been inserted with an Id of 61, however I am still getting -1. If I do a subsequent save of a new Venue that state value comes in as 61 but the DB saved a venue with Id: 62... So on a so forth.
I am still learning a lot of this redux so cant help but feel I am missing something obvious . Is there a quick/easy way of doing what I am looking for...? Any help would be greatly appreciated.
I'm working on an online streaming app where the user can create, edit, delete, and host streams. The problem is when I try deleting the stream, it deletes it in the database but my main component where the streams are being displayed doesn't reload, although it reloads itself when the user creates or edits the stream.
Here's my action creator:
export const deleteStream = (id) => async dispatch => {
await streams.delete(`/streams/${id}`);
dispatch({ type:'DELETE_STREAMS', payload: id });
history.push('/');
}
And here's the reducer:
const streamReducer = (state = {}, action) => {
switch(action.type){
case 'DELETE_STREAM':
return {...state, [action.payload]: undefined};
//I also used lodash to delete it alternatively as- return _.omit(state, action.payload);
default:
return state;
}
}
Also, not to forget that the objects are key interpolated in my server i.e., instead of having an array of objects, I have an object of objects.
Plural problem! DELETE_STREAMS vs DELETE_STREAM. You dispatch the former and reduce on the latter.
This is why it's always a good idea to have your actions defined somewhere even of its just export const X = "X". Then always reference them instead of using string literals.
It wasn't an error updating your React rendering, but that redux was never updated. A great tool to debug this is redux devtools https://github.com/reduxjs/redux-devtools, you can see the state of redux and every dispatched action and it's impact.
I'm building an app in React Native, and using Redux with redux-persist to act as on device database.
The crux of the issue is, how do I return the result of a redux action, to then dispatch another action with this data? Read on for more specifics.
The user can create custom habit types. When doing so, I dispatch an action to create a habit type in the store (e.g. "running"). This action generates a new unique UUID for the habit type. I then want to add this newly created habit type to a routine (e.g. "morning routine"), and so I need to receive back the UUID of the habit type and call another dispatch to add it to the routine.
I'm using immer to make manipulating the state in my reducers simpler, and have this code (simplified example):
import produce from "immer";
const userReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_CUSTOM_HABIT_TYPE: {
return produce(state, draftState => {
const newHabitType = {
id: generateUuid(),
name,
};
draftState.customHabitTypes.push(newHabitType);
return draftState;
});
}
}
};
I'm then dispatching it in my component, like so (simplified):
dispatch({
type: ADD_CUSTOM_HABIT_TYPE,
name: "running",
});
How can I then say, after creating this new habit type, to dispatch another action and add it to my routine?
I've looked at redux-thunk and redux-saga, and spent hours reading about these and trying to get redux-thunk to work, but to no avail. I'm sure this must be simple, but I'm coming up blank, and so maybe others are too, hence this post.
A very simple solution would be to generate the unique id before dispatching the action.
Example
const newHabitType = {
id: generateUuid(),
name,
};
dispatch({
type: ADD_CUSTOM_HABIT_TYPE,
habit: newHabitType,
});
dispatch({
type: ADD_CUSTOM_HABIT_TO_ROUTINE,
habit: newHabitType.id,
});
Pros
You no longer need to chain actions per se, you just need to dispatch them in order.
This preserves one of the most important Redux guidelines: your reducer should not have any side effects (in your case, generating a random id). reference
Cons
If you create the new habits in multiple places, you will have to generate the unique ids in every place where you dispatch the action. This might lead to repeated code. The solution to this would be to encapsulate the whole logic for creating the habits to a single component and then reuse this component everywhere.
Actions do not return data per se, the are simply objects which mutate the store based on the rules defined in the reducer. Two possible solutions:
Option A, create a composite action.
const compositeAction = args => {
return dispatch => {
return someAsyncCall(args).then(response => {
dispatch(addCustomHabitat(response))
dispatch(followUpAction())
}
}
}
const addCustomHabitat = response => {
return {
type: "ADD_CUSTOM_HABIT_TYPE",
data: response
}
}
const followUpAction = () => {
...another action...
}
Option B, connect the results of the first action to the dispatching component through react-redux and pass them to the second action.
import {connect} from 'react-redux';
const MyReactComponent = props => {
dispatch(addCustomHabitatTypeAction());
if(props.customHabitatType !== undefined)
dispatch(followUpAction(props.customHabitatType());
return (
...JSX here...
);
}
const mapStateToProps = state => {
return {
customHabitatType: state.userReducer.customHabitatType
}
}
connect(mapStateToProps)(MyReactComponent);
I hope this helps! Please excuse my abbreviated code and let me know if you have any questions.
In my app I have to make some order. Order has shape, about 20 properties, some of them are static, some are computed.
In my CartComponent.jsx i have method saveOrder which saves order for further use. It uses plain object as order model.
Now I can either confirm order or cancel it. After confirmation I dispatch authorizeOrder action to the store. Then user has to confirm order in some way (sms, token etc, whatever) and then order will be made. So flow is:
Save order -> Confirm order -> Authorize order (in other component) -> Send order (after authorization).
My question is: where should I keep shape of my order? It means - order model? Should it be created in authorizeOrder action? Or component is fine for that (sic!)? Or in orderModel.js which should expose order factory or order class?
Since you are using Redux, why not keep it there?
CreateOrder will then accept parameters for the object to be created and added to the store in turn.
The React component should dispatch to Redux which feeds back to React.
So for example (if I understand the question correctly):
MyReactComponent = React.createElement({
...
saveOrder: function(e) { //assuming it is a button
var myOrderObject = {
//properties...
}
this.props.createOrder(myOrderObject);
}
.....
});
var mapStateToProps = function (state) {
return {
orders: state.orders
};
}
var mapDispatchToProps = function (dispatch) {
return {
createOrder: function (properties) {
dispatch(myService.create(properties));
}
}
};
module.exports = ReactRedux.connect(mapStateToProps, mapDispatchToProps)(MyReactComponent );
Next in the store, you create the object on the dispatch function with the key that compares to what you sent in the service
//myService
var load = function (data) {
return function (dispatch, getState) {
dispatch({
type: "createOrderKey",
payload: data
});
};
};
in your reducer
function updateReducer(state, action) {
var newstate;
switch (action.type) {
case "createOrderKey:
//clone state to newState
//create and add the object
return newstate;
default:
return state;
}
};
I have a react-redux app with 3 reducers: clientPrivileges, userFilter and userData acting on my store.
A table component is used to present the data for each user and a drop down component is used to filter this table for specific users. When the drop down is selected for a specific user I need to call the backend to retrieve the data. This is the action associated with this:
selectUser(userId, dispatch) {
api.getUserData(userId, accessTypeId, (data) => {
dispatch(userActions.update(data));
});
return{
type: selectUser,
userId
}
}
However you can see that I have an argument called accessTypeId which needs to be sent to the backend as well as the userId. This value has been set in the store using the clientPrivileges reducer on login to the app.
I can't see any other way other than setting accessTypeId as a prop for the drop-down component in its mapStateToProps. And then in the component itself:
this.props.users.map(u => {
function onClick()
{
selectEndUserGroup(u.id, this.props.accessTypeId);
}
return <div id={"filter_group_"+u.id} key={u.id} onClick={onClick}>{name}</div>
But now I've destroyed my generic drop-down component with an accessTypeId property. How should I be doing this?
If I'm understanding correctly, you want your action to have access to a value stored in the Redux state, yes?
Redux-Thunk handles this nicely. The code would look something like this;
selectUser(userId) {
return function(dispatch, getState){
var accessTypeId = getState().[PATH TO REDUX STATE ELEMENT]
api.getUserData(userId, accessTypeId, (data) => {
dispatch(userActions.update(data));
});
dispatch({
type: selectUser,
userId
})
}
}