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 (
Header
andUserList
)
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
Header
that renders the heading - Create an interface
HeaderProps
and use it in theHeader
component - Use this component in the
Root
component, pass the title'User Management'
Location: src/modules/users/user-types.ts
This file contains definitions of user interfaces.
- Create and export two interfaces
UserName
contains two stringsfirstName
andlastName
User
is 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
UserList
that renders- two buttons
Add No One
andAdd Mother of Dragons
- a table of users with two columns
First Name
andLast Name
. The table displays textNo Users
when the list is empty
- two buttons
- The first button will add
Arya Stark
and the second oneDaenerys Targaryen
- Every user has
id
which should be unique within the list (we will not implement deleting users) - Create the initial state with the call of
useState
from React - Use the function returned by
useState
call to add a new user - Use correct types
- Use this component in the
Root
component
The main purpose of this exercise is to try stateless components.
- Continue with your previous project or open
01-react-stateful
- Modify
UserList
component into stateless function component
Location: src/modules/users/user-types.ts
- Add an
AddUser
interface, it's a function which takesUserName
as 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
UserList
component into a function that renders users from theusers
property (orNo Users
when the list is empty) - Call the
addUser
function taken from the props when the user clicks on the button - Create and use a
UserProps
interface
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
state
with 2 fields (title
andusers
) - Create your own function
render
that just callsReactDOM.render
and uses data from the global object - Create a function called
addUser
that adds the user into the list of users and calls yourrender
function- Please prefer immutable change of the
state
object
- 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
UserActionTypes
of action types with one valueaddUser = 'users/addUser'
- Create an interface
AddUserAction
which extendsAction<T>
from'redux'
forusers/addUser
action. In addition to the mandatorytype
property this action will contain a payload with the new user details.{ payload: UserName }
- Create a function (action creator of
ActionCreator<A>
type from'redux'
) calledaddUser
that takesUserName
object as parameter and returns theAddUserAction
action. - 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
users
field in the state when theusers/addUser
action 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
combineReducers
fromredux
to 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
store
with thecreateStore
function fromredux
- The first argument is
rootReducer
- The second argument can be an
enhancer
. Use the following to setup theRedux DevTools Extension
declare 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
dispatchAddUser
that callsstore.dispatch
to dispatch theusers/addUser
action - Use data from
store.getState()
in yourrender
function - Subscribe your
render
function withstore.subscribe
to 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
Provider
fromreact-redux
instead of the manual re-rendering - Use the
React Redux hooks
to get Redux store data inHeader
andUserList
- Rewrite actions with
Redux Toolkit
Location: src/modules/root/components/header.tsx
The same component like in the previous exercise.
Props:
{
}
- Use
useSelector
fromReact Redux
to get thetitle
from 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
useSelector
fromReact Redux
to get theusers
list from Redux store - Use
useDispatch
to get thedispatch
function of the Redux store. Use it to dispatchusers/addUser
action - Remove the props - they are no longer needed
Location: src/modules/root/components/root.tsx
Props:
{
}
- Remove all props because
Header
andUserList
components don't need them anymore.
Location: src/modules/users/users-slice.ts
The same actions and reducer, but created with Redux Toolkit
.
- Use
createSlice
fromRedux Toolkit
to generate action creators and action types for user reducers. - Move
adduser
reducer implementation to this file and pass it with thereducer
option tocreateSlice
function. - Export
actions
andreducer
returned by thecreateSlice
function asusersActions
andusersReducer
. - 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
render
function and directly callReactDOM.render
- Change the rendered component into
Provider
and putRoot
as its child - Remove
dispatchAddUser
(the action is dispatched in theUserList
component) - Remove
store.subscribe
(it is not necessary withProvider
) - Use
configureStore
function fromRedux Toolkit
instead of thecreateStore
. This will by default enable theRedux DevTools Extension
used in previous exercise. ThecomposeEnhancers
function 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
Header
andUsersList
Location: src/modules/users/users-selectors.ts
- Create a selector called
getTitle
withcreateSelector
fromreselect
- This selector just returns the
title
string from thestate
- This selector just returns the
- Create a selector called
getUsers
withcreateSelector
- This selector just returns the
users
array from thestate
- This selector just returns the
- Create a selector called
getUserList
withcreateSelector
- This selector uses the
getUsers
selector 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
getTitle
selector inuseSelector
Location: src/modules/users/components/user-list.tsx
The same component like in the previous exercise.
-
Use the
getUserList
selector inuseSelector
call -
Add a new component, which is a memoized version of
<button>
. UseReact.memo
.Props:
React.ButtonHTMLAttributes<HTMLButtonElement>
-
Use this memoized button in
UserList
component instead of the ordinary buttons -
Use
useCallback
hook fromReact
to 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
start
script 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
proxy
into 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 /users
that returns all users from the user list- Users can be stored in an array
- Create the route
POST /users
that- generates a new
id
- adds a new user into the user list (
firstName
andlastName
can be taken fromreq.body
) - returns the new user (
id
is included)
- generates a new
Location: backend/tsconfig.json
A simple TypeScript configuration file.
- Extend the
tsconfig-base.json
located in theexercises
directory - In the compiler options setup
commonjs
module, 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.create
to 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
getUsers
that makes a request toGET /users
- Create a function
addUser
that makes a request toPOST /users
and sends an object withfirstName
andlastName
in 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
usersLoaded
to 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
addUser
reducer - Add the
addUser
action creator to theusersActions
export object- Use
createAction
function to create the action creator with typeusers/addUser
- You can use a literal type for
action.type
for 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
getUsers
that- calls
UsersEffects.getUsers
- dispatch the
users/usersLoaded
action withdata
taken from the response
- calls
- Create a saga called
addUser
that- calls
UsersEffects.addUser
- runs the
getUsers
saga to refresh the user list
- calls
- Don't forget to use
try/catch
in both sagas - Create a saga called
usersSaga
(only this one needs to be exported - withexport default
) that- runs the
getUsers
saga immediately (we don't have a router currently) - runs the
addUser
saga when theusers/addUser
action 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
rootSaga
that runsusersSaga
(hint: usefork
)
Location: src/index.tsx
Configure all necessary things for redux-saga
.
- Create
sagaMiddleware
with a functioncreateSagaMiddleware
(default export fromredux-saga
) - Pass the
sagaMiddleware
to theconfigureStore
call with themiddleware
property - 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
string
ids for easier understanding ofnormalizr
) - When a new user is added
- Set
id
of the user as astring
(e.g.user-1
instead of1
) - Compute the correct regnal number for the user
- Add some skills to the user where
level
is 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
, andUser
entities - If an entity doesn't have the
id
field, you need to specify one withidAttribute
, which can be astring
(name of theid
field) or a function that creates the value ofid
field
Location: src/modules/entities/entities-types.ts
This file contains type definitions for normalized user data.
- Add and export
Skill
,UserSkill
andUser
types, which are the normalized version of types defined insrc/modules/users/user-types.ts
.- The normalized version uses the
id
of an entity instead of nested types:interface UserSkill { id: string skill: string level: number }
- The normalized version uses the
- Add and export the
UserEntities
type, which describesentities
created by the user data normalization:{ skills: { [key: string]: Skill } userSkills: { [key: string]: UserSkill } users: { [key: string]: User } }
- Add and export the
UserIds
type (a string array), which describes the userid
s returned from thenormalize
call (result
property).
Location: src/modules/users/users-slice.ts
State:
{
title: string,
userIds: string[]
}
- Update this reducer to store
userIds
instead ofusers
- Update the action payload to be of the
UserIds
type
Location: src/modules/entities/entities-slice.ts
This file contains an entities reducer, which manages the entities repository.
- Create and export an
EntitiesState
type/interface, which is equal toUserEntities
. This state would contain all normalized entities used in the application. - Let's setup the
updateEntities
case 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
objValue
andsrcValue
as arguments and returnsrcValue
if both arguments are arrays. For other types it will return undefined, which indicates no customization.
- This function has two arguments:
- Create the
entities
slice withcreateSlice
function- Use
EntitiesState
for the state type. - Add an
entitiesUpdated
reducer which updates the entities repository by callingupdateEntities
case reducer.
- Use
- Export the
entitiesReducer
andentitiesActions
.
Location: src/modules/root/root-reducer.ts
The created entities reducer needs to be added into the root reducer.
- Import the created
entitiesReducer
and 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
normalizeAndStore
saga:- Call
normalize(data, schema)
to normalize the passed data.- The
normalize
function returns an object with two properties:entities
andresult
.
- The
- Dispatch
entities/entitiesUpdated
action 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
normalizeAndStore
saga to normalize the fetched data and store the entities - Dispatch the
users/usersLoaded
action to save the userid
s 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
mapValues
fromlodash
to 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 id
s only.
- Create a new selector called
getUserIds
that returnsid
s from the redux state - Modify the
getUsers
selector to map usersid
s from the users reducer into denormalized users - Modify the
getUserList
selector to return the users with- upper cased last names
- converted regnal number into Roman numerals (use the
roman-numerals
library)
Location: src/modules/users/components/user-list.ts
- Print
regnalNumber
next 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-crud
to 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/:id
and 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 (withid
parameter)
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
defaultRoute
in theoptions
argument
- 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
UserDetail
that 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
UserDetail
component if the current route ends withdetail
(hint: useendsWithSegment
fromrouter5-helpers
) - Otherwise, render the
UsersList
component
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
Link
component and setname
andparams
props 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
Route
component instead ofUsersList
and setstartsWith
andcomponent
props on it - Import
import { ApiErrorToast, ApiLoader } from '@salsita/react-api';
- Use
Portal
fromreact-portal
to renderApiErrorToast
andApiLoader
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
entity
is astring
that describes the name of the entity currently being savedisUpdate
is aboolean
flag 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
USER
entity
Location: src/modules/crud/crud-selectors.js
The CRUD module takes care about automatic storing of entity id
s. Since the id
s 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
getUsersList
getUserDetail
Location: src/modules/users/users-selectors.js
- Update the
getUsersList
selector (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 needsmapRouteToFetchParams
as 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
data
is an object that is sent to the BE serverentity
is astring
that describes the name of the entity currently being savedmapEntityToSaveParams
is a function that defines defines save params (effect
andschema
) to use
- returns the response from the server (you don't have to use
.data
field)
- has the following arguments
- Call the
fetchEntities(route, mapRouteToFetchParams)
saga (to refresh the user list) that- has the following arguments
route
is the name of the route you want to refreshmapRouteToFetchParams
is a function that defines fetch params (effect
andschema
) to use
- has the following arguments
Location: src/modules/users/users-actions.js
We don't need the USERS_LOADED
action anymore.
- Delete the
usersLoaded
action creator
Location: src/modules/users/users-reducer.js
State:
{
title: string
}
- Delete the unused
usersLoaded
action 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/:id
that updates the user and returns the updated user in the response - Add a route for
GET /skills
that returns all skills - Modify the route
POST /users/:id
and addskills
field 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
form
reducer 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_USER
action 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
skills
in theUSERS_LIST
route - Add the
updateUser
effect intomapEntityToSaveParams
(hint: use theisUpdate
argument 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
FormField
from@salsita/react-forms
forfirstName
lastName
- Use
FormFieldSelect
from@salsita/react-forms
forskills
- Since a user can have multiple skills, implement adding and deleting of skills with the
FieldArray
component fromredux-form
- Implement the following field-level validations
firstName
cannot be an empty stringlastName
cannot be an empty stringskills
cannot 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
Link
to theUSERS
page
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
UsersList
andUserCreate
on thecreate
route - Use
Portal
to put theUserCreate
component 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
saveUser
since the new name of the action that starts the saga isSAVE_USER
.- Update names of the action and saga
- Since the
saveUser
saga can be called from two routes now (USERS_LIST
andUSER_DETAIL
), we want to redirect the user intoUSERS_LIST
route 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
saveEntity
saga, which is the name of the form that was submitted