https://docs.google.com/presentation/d/1w8HRmAK2HB5PuwOB0HdFeGbvntsvM3-rvHdNOPZ3lBs
Install all dependencies and link executables to child projects with npm i in the exercises folder.
- Exercise #1
- Exercise #2
- Exercise #3
- Exercise #4
- Exercise #5
- Exercise #6
- Exercise #7
- Exercise #8
- Exercise #9
The main purpose of this exercise is to try React and its stateful components implemented with React Hooks.
- Open the initial project
00-init - Create two function components (
HeaderandUserList)
Location: src/modules/root/components/header.tsx
HeaderProps:
{
title: string
}This component just renders a heading (h1) with a string taken from the title property.
- Create a function named
Headerthat renders the heading - Create an interface
HeaderPropsand use it in theHeadercomponent - Use this component in the
Rootcomponent, pass the title'User Management'
Location: src/modules/users/user-types.ts
This file contains definitions of user interfaces.
- Create and export two interfaces
UserNamecontains two stringsfirstNameandlastNameUseris the same asUserName, but additionally contains anid(number)
Location: src/modules/users/components/user-list.tsx
This component renders a list of the users saved in the state and two buttons to add two different users.
- Create a function named
UserListthat renders- two buttons
Add No OneandAdd Mother of Dragons - a table of users with two columns
First NameandLast Name. The table displays textNo Userswhen the list is empty
- two buttons
- The first button will add
Arya Starkand the second oneDaenerys Targaryen - Every user has
idwhich should be unique within the list (we will not implement deleting users) - Create the initial state with the call of
useStatefrom React - Use the function returned by
useStatecall to add a new user - Use correct types
- Use this component in the
Rootcomponent
The main purpose of this exercise is to try stateless components.
- Continue with your previous project or open
01-react-stateful - Modify
UserListcomponent into stateless function component
Location: src/modules/users/user-types.ts
- Add an
AddUserinterface, it's a function which takesUserNameas parameter and returnsvoid
Location: src/modules/users/components/user-list.tsx
Props:
{
users: User[],
addUser: AddUser
}The functionality is the same like in the previous exercise. The only difference is that the logic will be outside the file.
- Modify the
UserListcomponent into a function that renders users from theusersproperty (orNo Userswhen the list is empty) - Call the
addUserfunction taken from the props when the user clicks on the button - Create and use a
UserPropsinterface
Location: src/index.tsx
Move logic from the old UserList into the index file. All application data will be in a global object.
- Create a global object called
statewith 2 fields (titleandusers) - Create your own function
renderthat just callsReactDOM.renderand uses data from the global object - Create a function called
addUserthat adds the user into the list of users and calls yourrenderfunction- Please prefer immutable change of the
stateobject
- Please prefer immutable change of the
- Define necessary interfaces
Location: src/modules/root/components/root.tsx
Props:
{
title: string,
users: User[],
addUser: AddUser
}Since we moved the logic into the index file and the Root component receives all necessary props, we need to send props into Header and UserList.
Try 3 different versions of the Header component and see when they get rendered
- Created as a class that extends
React.Component - Created as a class that extends
React.PureComponent - Created as a function
The main purpose of this exercise is to try Redux.
- Continue with your previous project or open
02-react-stateless - Move the logic into a reducer
Location: src/modules/users/user-actions.ts
This file defines actions and action types.
- Create and export an enum
UserActionTypesof action types with one valueaddUser = 'users/addUser' - Create an interface
AddUserActionwhich extendsAction<T>from'redux'forusers/addUseraction. In addition to the mandatorytypeproperty this action will contain a payload with the new user details.{ payload: UserName }
- Create a function (action creator of
ActionCreator<A>type from'redux') calledaddUserthat takesUserNameobject as parameter and returns theAddUserActionaction. - Create and export a new type
UserActions, which is a union of all user actions. This is useful in the reducer when more actions are defined. Note, currently we have only one actionusers/addUser. - Create and export an object
UserActionCreators, which congregates all user action creators.
Location: src/modules/users/users-reducer.ts
State:
{
title: string,
users: User[]
}The logic from addUser function from the previous exercise will be in this reducer.
- Create a reducer function of type
Reducer<S, A>from'redux' - Use an initial state
- Modify
usersfield in the state when theusers/addUseraction is dispatched - Don't forget to return unmodified state when a different action is dispatched
Location: src/modules/root/root-reducer.ts
This is the main reducer of the whole app. The main purpose is to combine all reducers from all modules into a single reducer.
- Import your
usersReducer - Use
combineReducersfromreduxto create the root reducer- The root reducer state will be a combination of all passed reducers states, the resulting state type can be inferred from the return type:
export type RootState = ReturnType<typeof rootReducer>
Location: src/index.tsx
Configure all necessary things for redux.
- Create
storewith thecreateStorefunction fromredux- The first argument is
rootReducer - The second argument can be an
enhancer. Use the following to setup theRedux DevTools Extensiondeclare global { interface Window { __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose } } const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
- The first argument is
- Create a function called
dispatchAddUserthat callsstore.dispatchto dispatch theusers/addUseraction - Use data from
store.getState()in yourrenderfunction - Subscribe your
renderfunction withstore.subscribeto re-render the app when an action is dispatched
The main purpose of this exercise is to try React Redux and Redux Toolkit.
- Continue with your previous project or open
03-redux - Use
Providerfromreact-reduxinstead of the manual re-rendering - Use the
React Redux hooksto get Redux store data inHeaderandUserList - Rewrite actions with
Redux Toolkit
Location: src/modules/root/components/header.tsx
The same component like in the previous exercise.
Props:
{
}- Use
useSelectorfromReact Reduxto get thetitlefrom Redux store - Remove the props - they are no longer needed
Location: src/modules/users/components/user-list.tsx
The same component like in the previous exercise.
Props:
{
}- Use
useSelectorfromReact Reduxto get theuserslist from Redux store - Use
useDispatchto get thedispatchfunction of the Redux store. Use it to dispatchusers/addUseraction - Remove the props - they are no longer needed
Location: src/modules/root/components/root.tsx
Props:
{
}- Remove all props because
HeaderandUserListcomponents don't need them anymore.
Location: src/modules/users/users-slice.ts
The same actions and reducer, but created with Redux Toolkit.
- Use
createSlicefromRedux Toolkitto generate action creators and action types for user reducers. - Move
adduserreducer implementation to this file and pass it with thereduceroption tocreateSlicefunction. - Export
actionsandreducerreturned by thecreateSlicefunction asusersActionsandusersReducer. - Define necessary interfaces
Location: src/modules/users/user-actions.ts
This file is no longer needed. The user actions are now generated in src/modules/users/users-slice.ts.
Location: src/modules/users/users-reducer.ts
This file is no longer needed. The addUser reducer was moved to src/modules/users/users-slice.ts.
Location: src/index.tsx
Currently, we need only store and we need to call ReactDOM.render directly with Provider component.
- Remove own
renderfunction and directly callReactDOM.render - Change the rendered component into
Providerand putRootas its child - Remove
dispatchAddUser(the action is dispatched in theUserListcomponent) - Remove
store.subscribe(it is not necessary withProvider) - Use
configureStorefunction fromRedux Toolkitinstead of thecreateStore. This will by default enable theRedux DevTools Extensionused in previous exercise. ThecomposeEnhancersfunction is no longer needed.
The main purpose of this exercise is to try Reselect.
- Continue with your previous project or open
04-react-redux - Create selectors and use them in
HeaderandUsersList
Location: src/modules/users/users-selectors.ts
- Create a selector called
getTitlewithcreateSelectorfromreselect- This selector just returns the
titlestring from thestate
- This selector just returns the
- Create a selector called
getUserswithcreateSelector- This selector just returns the
usersarray from thestate
- This selector just returns the
- Create a selector called
getUserListwithcreateSelector- This selector uses the
getUsersselector and modifies last names to upper case
- This selector uses the
Location: src/modules/root/components/header.tsx
The same component like in the previous exercise.
- Use the
getTitleselector inuseSelector
Location: src/modules/users/components/user-list.tsx
The same component like in the previous exercise.
-
Use the
getUserListselector inuseSelectorcall -
Add a new component, which is a memoized version of
<button>. UseReact.memo.Props:
React.ButtonHTMLAttributes<HTMLButtonElement>
-
Use this memoized button in
UserListcomponent instead of the ordinary buttons -
Use
useCallbackhook fromReactto memoize the callbacks passed to memoized buttons
The main purpose of this exercise is to try Redux-Saga, axios, and Express.
- Continue with your previous project or open
05-reselect - Create a simple server that allows you to add a new user and get a list of all users
- Move the logic of adding a new user to the server
- Create sagas that handle communication with the server
Location: package.json
- Change the
startscript into the following"start": "concurrently \"npm run start-fe\" \"npm run start-be\"", "start-fe": "react-scripts start", "start-be": "cd backend && nodemon server.ts",
- Add
proxyinto the root to correctly handle CORS"proxy": "http://localhost:3001"
Location: backend/server.ts
A simple express server that has 2 routes GET /users and POST /users.
- Create a server with
express - Use
express.json()andexpress.urlencoded()middleware - Create the route
GET /usersthat returns all users from the user list- Users can be stored in an array
- Create the route
POST /usersthat- generates a new
id - adds a new user into the user list (
firstNameandlastNamecan be taken fromreq.body) - returns the new user (
idis included)
- generates a new
Location: backend/tsconfig.json
A simple TypeScript configuration file.
- Extend the
tsconfig-base.jsonlocated in theexercisesdirectory - In the compiler options setup
commonjsmodule, which is needed for Node.js
Location: src/modules/api/api-client.ts
This file contains an API client with axios that is used to make requests to the BE server.
- Use
axios.createto create the client - Set
baseURL: 'http://localhost:3000'in the config
Location: src/modules/users/users-effects.ts
This file defines all effect functions that perform the corresponding requests to API. We need only 2 effects right now - getUsers and addUser.
- Create a function
getUsersthat makes a request toGET /users - Create a function
addUserthat makes a request toPOST /usersand sends an object withfirstNameandlastNamein the request body
Location: src/modules/users/users-slice.ts
We will need a new reducer to store fetched users into the state.
- Add a new case reducer called
usersLoadedto do it
Users are added on the BE side, so the addUser reducer is not needed anymore, but the action type still is.
- Remove the
addUserreducer - Add the
addUseraction creator to theusersActionsexport object- Use
createActionfunction to create the action creator with typeusers/addUser- You can use a literal type for
action.typefor stronger typing of sagas
- You can use a literal type for
- Use
Location: src/modules/users/users-saga.ts
This file is used to create redux sagas that handle side effects to communicate with the BE server. We need 2 sagas to handle all API effects we have - getUsers and addUser.
- Create a saga called
getUsersthat- calls
UsersEffects.getUsers - dispatch the
users/usersLoadedaction withdatataken from the response
- calls
- Create a saga called
addUserthat- calls
UsersEffects.addUser - runs the
getUserssaga to refresh the user list
- calls
- Don't forget to use
try/catchin both sagas - Create a saga called
usersSaga(only this one needs to be exported - withexport default) that- runs the
getUserssaga immediately (we don't have a router currently) - runs the
addUsersaga when theusers/addUseraction is dispatched (hint: usetakeEvery)
- runs the
Location: src/modules/root/root-saga.ts
This file simply starts all sagas that are needed in the whole application. Currently, we have only our own usersSaga.
- Create and export a saga called
rootSagathat runsusersSaga(hint: usefork)
Location: src/index.tsx
Configure all necessary things for redux-saga.
- Create
sagaMiddlewarewith a functioncreateSagaMiddleware(default export fromredux-saga) - Pass the
sagaMiddlewareto theconfigureStorecall with themiddlewareproperty - Run your root saga with
sagaMiddleware.run(rootSaga)
The main purpose of this exercise is to try normalizr.
- Continue with your previous project or open
06-redux-saga - Add skills and regnal number to users
- Save users and skills into the entity repository in the normalized form
Location: backend/server.ts
The server adds skills and set the correct regnal number to every user.
The entity interfaces are
interface Skill {
id: string // e.g. skill-1
name: string
}
interface UserSkill {
skill: Skill
level: number
}
interface User extends UserName {
id: string; // e.g. user-1
regnalNumber: number // use Arabic numerals
skills: Array<UserSkill>
}- Create few skills (use
stringids for easier understanding ofnormalizr) - When a new user is added
- Set
idof the user as astring(e.g.user-1instead of1) - Compute the correct regnal number for the user
- Add some skills to the user where
levelis somehow based on the regnal number (it doesn't matter what equation you use - it can be for examplelevel = 3 * regnalNumber)
- Set
Location: src/modules/entities/entities-schema.ts
This file contains normalizr schema of our entities.
- Create schema for
Skill,UserSkill, andUserentities - If an entity doesn't have the
idfield, you need to specify one withidAttribute, which can be astring(name of theidfield) or a function that creates the value ofidfield
Location: src/modules/entities/entities-types.ts
This file contains type definitions for normalized user data.
- Add and export
Skill,UserSkillandUsertypes, which are the normalized version of types defined insrc/modules/users/user-types.ts.- The normalized version uses the
idof an entity instead of nested types:interface UserSkill { id: string skill: string level: number }
- The normalized version uses the
- Add and export the
UserEntitiestype, which describesentitiescreated by the user data normalization:{ skills: { [key: string]: Skill } userSkills: { [key: string]: UserSkill } users: { [key: string]: User } }
- Add and export the
UserIdstype (a string array), which describes the userids returned from thenormalizecall (resultproperty).
Location: src/modules/users/users-slice.ts
State:
{
title: string,
userIds: string[]
}- Update this reducer to store
userIdsinstead ofusers - Update the action payload to be of the
UserIdstype
Location: src/modules/entities/entities-slice.ts
This file contains an entities reducer, which manages the entities repository.
- Create and export an
EntitiesStatetype/interface, which is equal toUserEntities. This state would contain all normalized entities used in the application. - Let's setup the
updateEntitiescase reducer which would make a recursive merge of current state with the newly received entities. This is a common approach in the applications where the entities are fetched in small portions.- This function has two arguments:
- state of type
EntitiesState - action, which payload may contain any part of the state
- state of type
- Use the mergeWith function from the Lodash library.
- Create and use the customizer which changes the merge strategy for arrays by always choosing the new value. Our customizer will take
objValueandsrcValueas arguments and returnsrcValueif both arguments are arrays. For other types it will return undefined, which indicates no customization.
- This function has two arguments:
- Create the
entitiesslice withcreateSlicefunction- Use
EntitiesStatefor the state type. - Add an
entitiesUpdatedreducer which updates the entities repository by callingupdateEntitiescase reducer.
- Use
- Export the
entitiesReducerandentitiesActions.
Location: src/modules/root/root-reducer.ts
The created entities reducer needs to be added into the root reducer.
- Import the created
entitiesReducerand add it to the root reducer
Location: src/modules/entities/entities-saga.ts
This file contains a saga which normalizes data and stores them to the entities state.
- Create a
normalizeAndStoresaga:- Call
normalize(data, schema)to normalize the passed data.- The
normalizefunction returns an object with two properties:entitiesandresult.
- The
- Dispatch
entities/entitiesUpdatedaction with the payload containingentities. - Return the
result.
- Call
Location: src/modules/users/users-saga.ts
Currently, the same denormalized data that comes from the BE server are stored in the state. We need to normalize the data from response and store them in the entity repository.
- Call the
normalizeAndStoresaga to normalize the fetched data and store the entities - Dispatch the
users/usersLoadedaction to save the userids the store
Location: src/modules/entities/entities-selectors.ts
Since data are stored in the normalized form in the state, we need to denormalize them for easier access to values.
- Create 3 selectors (
getUsers,getSkills, andgetUserSkills) that return the corresponding entities in the denormalized form- Hint: use
mapValuesfromlodashto create a new object with keys identical to source object.
- Hint: use
Location: src/modules/users/users-selectors.ts
The users reducer doesn't store the entity data, it stores ids only.
- Create a new selector called
getUserIdsthat returnsids from the redux state - Modify the
getUsersselector to map usersids from the users reducer into denormalized users - Modify the
getUserListselector to return the users with- upper cased last names
- converted regnal number into Roman numerals (use the
roman-numeralslibrary)
Location: src/modules/users/components/user-list.ts
- Print
regnalNumbernext to the first name
The main purpose of this exercise is to try router5 and @salsita/react-crud.
- Continue with your previous project or open
07-normalizr - This exercise uses react-modules packages
- Create a page for user detail (e.g. at
/users/user-1) - Use
@salsita/react-crudto automate entity fetching
The solution of this exercise (08-router5) uses a separate set of dependencies. You can run npm install in the corresponding directory to install them.
Location: src/server.js
Add a route to fetch a single user.
- Add a route for
GET /users/:idand return the corresponding user in the response
Location: src/modules/root/root-reducer.js
We need to add all required reducers into the root reducer.
- Import
import { apiReducer as api } from '@salsita/react-api'; - Import
import { crudReducer as crud } from '@salsita/react-crud'; - Import
import { routerReducer as router } from '@salsita/react-router'; - Add all three reducers into the root reducer
Location: src/router/routes.js
This file contains names and configuration of routes.
- Create 2 routes
const USERS_LIST = 'users'for the list of all usersconst USER_DETAIL = 'users.detail'for the detail page (withidparameter)
Location: src/index.js
Use buildRouter and buildStore functions for easier configuration of redux, router5, and redux-saga.
- Import
import { buildRouter } from '@salsita/react-router'; - Import
import { buildStore } from '@salsita/react-core'; - Create a router with the
buildRouter(routes, options)function- You can specify the
defaultRoutein theoptionsargument
- You can specify the
- Create the redux store with the
buildStore(rootReducer, rootSaga, router)function - Start the router
Location: src/modules/users/components/user-detail.js
Props:
{
userDetail: {
firstName: string,
lastName: string,
regnalNumber: string,
skills: Array<{
skill: {
name: string
},
level: number
}>
}
}This component displays a user detail with skills information.
- Create
UserDetailthat displays the data
Location src/modules/users/components/users-route.js
Props:
{
route: {
name: string
}
}This component takes care about the proper routing in the users module.
- Render the
UserDetailcomponent if the current route ends withdetail(hint: useendsWithSegmentfromrouter5-helpers) - Otherwise, render the
UsersListcomponent
Location: src/modules/users/components/users-list.js
We need to add links to the detail page. Navigate to the detail page when the user clicks on the first name.
- Import
import { Link } from '@salsita/react-router'; - Use the
Linkcomponent and setnameandparamsprops on it
Location: src/modules/root/components/root.js
Use the Route component from @salsita/react-router for easier routing. You can also use ApiErrorToast and ApiLoader to display a basic error toast and loading spinner. The data for both components are automatically stored in the state from @salsita/react-api.
- Import
import { Route } from '@salsita/react-router'; - Use the
Routecomponent instead ofUsersListand setstartsWithandcomponentprops on it - Import
import { ApiErrorToast, ApiLoader } from '@salsita/react-api'; - Use
Portalfromreact-portalto renderApiErrorToastandApiLoader
Location: src/modules/crud/crud-saga.js
This file contains two important functions for the CRUD module - mapEntityToSaveParams and mapRouteToFetchParams. Both of them return params that are used for saving or fetching entities.
- Create a function called
mapEntityToSaveParams(entity, isUpdate)that- has the following arguments
entityis astringthat describes the name of the entity currently being savedisUpdateis abooleanflag to distinguish between create and update
- returns the following object
{ effect: (data: any) => void, // an effect to save the entity schema: Schema // a schema from normalizr }
- has the following arguments
- Create a function called
mapRouteToFetchParams(route)that- receives the name of the current route
- returns the following object
{ [identifier]: { // this can be any string that identifies the fetched data effect: (...effectParams: any) => data, // an effect to fetch the entity schema: Schema // a schema from normalizr effectParamsFactory: (state: RootState) => any[] // the result is used for effectParams } }
Location: src/modules/crud/crud-entities.js
This file has only string constants with entity names for mapEntityToSaveParams
- Create a constant for
USERentity
Location: src/modules/crud/crud-selectors.js
The CRUD module takes care about automatic storing of entity ids. Since the ids won't be in the usersReducer anymore, we need to slightly update UsersSelectors and move them into CrudSelectors.
- Create the following selectors that read the data from CRUD and Entities modules
getUsersListgetUserDetail
Location: src/modules/users/users-selectors.js
- Update the
getUsersListselector (hint: useCrudSelectors)
Location: src/modules/root/root-saga.js
Start crudSaga to automatically fetch entities.
- Import
import { crudSaga } from '@salsita/react-crud'; - Start
crudSaga(it needsmapRouteToFetchParamsas an argument)
Location: src/modules/users/users-effects.js
We need a new effect to fetch a single user. Also, we should use wrapApiCall from @salsita/react-api for proper error handling.
- Import
import { wrapApiCall } from '@salsita/react-api'; - Wrap all effects with
wrapApiCall(effect) - Add a new effect called
getUser
Location: src/modules/users/users-saga.js
The CRUD module handles entity fetching so we don't need the getUsers saga anymore. Use the saveEntity saga from @salsita/react-crud for better error handling and fetchEntities to refresh the user list.
- Call the
saveEntity(data, entity, mapEntityToSaveParams)saga (instead of the direct effect call) that- has the following arguments
datais an object that is sent to the BE serverentityis astringthat describes the name of the entity currently being savedmapEntityToSaveParamsis a function that defines defines save params (effectandschema) to use
- returns the response from the server (you don't have to use
.datafield)
- has the following arguments
- Call the
fetchEntities(route, mapRouteToFetchParams)saga (to refresh the user list) that- has the following arguments
routeis the name of the route you want to refreshmapRouteToFetchParamsis a function that defines fetch params (effectandschema) to use
- has the following arguments
Location: src/modules/users/users-actions.js
We don't need the USERS_LOADED action anymore.
- Delete the
usersLoadedaction creator
Location: src/modules/users/users-reducer.js
State:
{
title: string
}- Delete the unused
usersLoadedaction handler
The main purpose of this exercise is to try Redux Form.
- Continue with your previous project or open
08-router5 - This exercise uses react-modules packages
- Create forms for creating and updating users
The solution of this exercise (09-forms) uses a separate set of dependencies. You can run npm install in the corresponding directory to install them.
Location: src/server.js
Add routes that updates a user and fetches skills. Modify the route that saves a new user.
- Add a route for
PATCH /users/:idthat updates the user and returns the updated user in the response - Add a route for
GET /skillsthat returns all skills - Modify the route
POST /users/:idand addskillsfield into the request body
Location: src/modules/root/root-reducer.js
We need to add the form reducer into the root reducer.
- Import
import { formsReducer as form } from '@salsita/react-forms'; - Add the
formreducer into the root reducer
Location: src/router/routes.js
- Create a new route
const USER_CREATE = 'users.create'for the form that creates a new user
Location: src/modules/users/users-effects.js
We need new effects to update a user and fetch all skills.
- Add a new effect called
updateUser - Add a new effect called
getSkills
Location: src/modules/users/users-actions.js
Currently, we have only one action called ADD_USER that is dispatched when a user clicks on one of the buttons. Since we will use this action to create or update a user, let's rename it to SAVE_USER.
- Rename the
ADD_USERaction toSAVE_USER
Location: src/modules/crud/crud-saga.js
We will need a list of all skills to display them in the create/edit form. We also have a new effect called updateUser so we can use it in mapEntityToSaveParams.
- Fetch
skillsin theUSERS_LISTroute - Add the
updateUsereffect intomapEntityToSaveParams(hint: use theisUpdateargument to distinguish between create and update)
Location: src/modules/crud/crud-selectors.js
- Add a selector called
getSkills
Location: src/modules/users/components/user-form.js
This form component has fields for firstName, lastName, and skills where a single user can have multiple skills.
- Use
FormFieldfrom@salsita/react-formsforfirstNamelastName
- Use
FormFieldSelectfrom@salsita/react-formsforskills - Since a user can have multiple skills, implement adding and deleting of skills with the
FieldArraycomponent fromredux-form - Implement the following field-level validations
firstNamecannot be an empty stringlastNamecannot be an empty stringskillscannot be empty and must be unique
- You can use some validation functions from
@salsita/react-forms
Location: src/modules/users/components/user-create.js
Props:
{
saveUser: (formData: object) => void
}This component just renders the UserForm component to create a new user.
Location: src/modules/users/components/user-detail.js
Props:
{
userDetail: {
firstName: string,
lastName: string,
regnalNumber: string,
skills: Array<{
skill: {
name: string
},
level: number
}>
},
saveUser: (formData: object) => void
}This component just renders the UserForm component with initialValues to edit a user.
Location: src/modules/users/components/users-list.js
Props:
{
users: Array<{
id: number,
firstName: string,
lastName: string,
regnalNumber: string
}>
}- Remove both buttons
- Add a
Linkto theUSERSpage
Location src/modules/users/components/users-route.js
We want to display the UserCreate component in a modal dialog while the users list is shown in the background.
- Render both
UsersListandUserCreateon thecreateroute - Use
Portalto put theUserCreatecomponent in the modal dialog
Location: src/modules/users/users-saga.js
There are couple of things we need to update in our sagas.
- Currently, our saga handles update as well so it is good to rename it to
saveUsersince the new name of the action that starts the saga isSAVE_USER.- Update names of the action and saga
- Since the
saveUsersaga can be called from two routes now (USERS_LISTandUSER_DETAIL), we want to redirect the user intoUSERS_LISTroute after the successful submission.- Import
import { RouterActions } from '@salsita/react-router'; - Use the
RouterActions.Creators.navigateTo(routeName)action to perform the redirect
- Import
- Since we use forms for creating and updating users, we should add the 4th argument to the
saveEntitysaga, which is the name of the form that was submitted