Where should I keep data models in React and Redux? - reactjs

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;
}
};

Related

Calling an action within another one in Redux using TypeScript

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>

How to get the value as props in a different component

In my react application, I have three parallel components. In my first component, I am doing an API call and based on the response I am routing the flow to Validated or Non-Validated Component.
Once the user is routed to validated component, there is a button on click of which user should be redirected to another component which should display the data in API response (first component) as key value pair. I am using Redux for state management.
The issue I am facing is the data is dispatched as an empty object from the store. I am not sure where I am going wrong but when I am debugging the app, I see the the action is not getting dispatched to the store and it's always returning me the initial state.
action.js-
export const setPoiData = (poiData) => dispatch => {
console.log('inside actions');
dispatch({
type: SET_POI_DATA,
payload: poiData
})
}
Reducer.js-
const initialState = {
poiData: {},
}
const reducerFunc = (state = initialState, action) => {
switch (action.type) {
case SET_POI_DATA:
console.log('inside poi reducers');
return {...state,poiData: action.payload}
default: return {...state}
}
}
Component 1-
//API call
Detail Component-
To get the data from store I am doing something like below-
componentDidMount() {
console.log(this.props.poiData)
}
function mapStateToProps(state) {
return {
poiData: state.poiData,
}
}
const mapDispatchToProps = dispatch => ({
setPoiData(data) {
dispatch(setPoiData(data));
}
})
I am not sure where I am going wrong. Can someone suggest me how to proceed ahead on this?
inside componentDidMount() you must call action like this this.props.setPoiData(<your data here>);

React Redux Load the list in the table after Adding an item with Async Api

I'm trying to learn the React / Redux and build the simple app by using .Net core Api as the backend service.
My Requirements
Add item when the user clicks Save button
Redirect to the List page and load the data again to retrieve the newly added data
I have to reload the data and cannot just append the new item in the list because I need to get the keyId of the newly added item which is just generated
So, I do the chaining of promises in my action file to call LoadItems after AddItem is successful.
If I don't chain these promises, I could not see the newly created item in the List page. The redirection to '/todo' occurs so quickly even before
the AddItem() is not completed yet. If I added 2 seconds delay for the redirection, I could see the new item.
Actions
export const addTodoItemSuccess = todoItem => ({ type: actionTypes.ADD_TODO_ITEM_SUCCESS, payload: todoItem });
export const loadTodoItemsSuccess = items => ({ type: actionTypes.LOAD_TODO_SUCCESS, payload: items });
export const loadTodoItems = () => {
return function (dispatch) {
return TodoService.getAll().then(items => {
dispatch(loadTodoItemsSuccess(items));
});
};
}
export const addTodoItem = (item) => {
return function (dispatch) {
return TodoService.add(item).then(() => {
return TodoService.getAll().then(items => {
dispatch(loadTodoItemsSuccess(items));
});
});
};
}
Reducer
import * as actionTypes from '../actions/actionTypes';
const todoReducer = (state = [], action) => {
switch (action.type) {
case actionTypes.LOAD_TODO_SUCCESS:
return action.payload;
default:
return state;
}
}
export default todoReducer;
AddTodoPage Container
submitNewTodo = event => {
event.preventDefault();
this.props.addTodoItem(this.state.item);
//redirect to Todo List Page after saving
this.context.router.history.push(`/todo`);
}
TodoListPage Container
componentDidMount = () => {
this.props.dispatch(loadTodoItems());
}
It works as expected and I can see the new item in the list. But the problem is that it's sending TWO GetAll() queries to the Api.
First call comes from Actions.js and Second call comes from componentDidMount in TodoListPage.js.
If I removed the loadTodoItems() from the componentDidMount, I could not view any items when I just navigate to TodoListPage '/todo' because the items are not loaded yet.
In tutorials, they usually do store.dispatch(loadTodoItems()); in index.js to make it available. I feel wrong to load the data even before the user hasn't navigated to that page (except lookup data).
Could you please suggest me what's the best way to achieve my requirements above? I don't want to call the Api twice to refresh the data.
The complete set of code can be found here: https://github.com/ttcg/react-redux-todo/tree/todocrud/src
I managed to sort out this problem in the following way.
The main problem is that I was trying to develop the redirection in the traditional way rather than React-Redux way.
Normally, we do the redirection to another route after a button click or some actions because we assume that it won't execute the redirection until the execution of the action has completed.
However, Javascript execution is async and they don't wait the completion of the previous line. So, it will always redirect without waiting for the previous action. So many tutorials online are using that way. It might work for the tutorials because there is no delay and they are manipulating the objects in the memory.
Wrong way to redirect after button click
submitNewTodo = event => {
event.preventDefault();
this.props.addTodoItem(this.state.item);
//redirect to Todo List Page after saving
this.context.router.history.push(`/todo`);
}
But in React-Redux, the data flow is Unidirectional and we need to implement our codes with that flow in mind.
Changed in Reducer to return the mark addTodoSuccess value
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ADD_TODO_ITEM_SUCCESS: {
return { ...state, addTodoSuccess: true };
}
...
default:
return state;
}
}
Map the value in the AddTodoPage container to catch the flag
const mapStateToProps = (state) => {
let item = { id: uuidv4(), taskItem: '', doneBy: '' };
return {
item: item,
addTodoSuccess: state.todo.addTodoSuccess
};
}
Do the redirection in the Render() method to check the value returned by reducer
{
addTodoSuccess
? (<Redirect to={"/todo"} />)
:
<Container>
<h4>Add New Todo</h4>
....
</Container>
}
Notes:
It can be solved by using Promises but it breaks the Redux flow of UniDirectional.
You do not have to use Redux for this kind of feature. But, I'm learning Redux and trying to follow the tutorials.
Some people might do the redirection in Actions / Reducers. There are debates about that way too.

Redux Form - sync form with Create Operation

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.

Why is my Redux State nested ?

I am writing my first bigger React/Redux/Meteor App. I know that Redux is not necessarily needed in an Meteor App, but I want to use it.
I load a record from a MongoDB with Meteor and then I want to store this object in my Redux store. But the object gets nested in the store and I do not know why this is the case.
Here is my code so far:
Action loads the remote record
export const loadRecord = (id) => {
return dispatch => {
Meteor.call('loadRecord', id, (error, result) => {
if (!error) {
dispatch({
type: TYPE,
result
});
} else {
dispatch({
type: TYPE_ERROR,
error,
});
}
});
};
};
Reducer should update my store
const initialState = {
singleRecord: {}
};
export function singleRecord(state = initialState, action) {
switch (action.type) {
case TYPE:
return {
...state,
singleRecord: action.result
};
default:
return state;
}
}
In more store I expect something like this:
singleRecord: {
id:"1223",
text:"abcde"
}
But what I get is:
singleRecord: {
singleRecord {
id:"1223",
text:"abcde"
}
}
So my store gets updated and everything is working as expected beside the fact, that my record is nested somehow.
I think I am missing a fundamental thing, or I implemented it wrong. It would be very nice if someone can explain me if this is the intended behavior or if not, can tell me why my code is not working as intended.
Thanks in advance
You want to unwrap the payload of the action:
return {
...state,
...action.result
};
or, in other words:
return Object.assign({}, state, action.result);
I am not sure what else you want to save in singleRecord but it's entirely possible you want to do this:
return action.result;
Also, your initial state should be just const initialState = {};
The object returned from your singleRecord reducer is what is stored into singleRecord state.

Resources