- download node and npm
- install the react boiler plate CLI
npm i -g create-react-app
- create your add
create-react-app <app-name>
- run the app for the first time.
cd <app-name> yarn start
- in order to make the ui more friendly and neat i will use a third party css package call
bootstrap - add
bootstrapto your application by run:yarn add bootstrap
- on the
App.jsadd thebootstrapcss:import 'bootstrap/dist/css/bootstrap.min.css';
- under
srccreate new folder,users. on that folder create new foldercomponents(src/users/components). - in this folder create
User.jsfile:
/* src/users/components/User.js */
import React, { Component } from 'react'
export default class User extends React.Component {
render() {
return (
<div className="item">
<div>
id:1
</div>
<div>
username:johndoe
</div>
<div>
email:johndoe@gmail.com
</div>
</div>
);
}
}- inside
User.jscreate aconstructorfunction. paste thereconsole.log('User created!')
don't forget to call to
super().
- on the
src/userscreate new folderstyles. - in this folder create new
cssfileuser.css.
/* src/users/styles/item.css */
.item{
width: 300px;
border-radius: 10px;
border: solid 1px lightgray;
padding: 10px;
}- in
Usercomponent, add the itemcss
/* src/users/components/User.js */
import React, { Component } from 'react'
import '../style/item.css'
...- in
App.jsfile, import theUserelement.
/* src/App.js */
import User from 'users/components/User'- remove all the
jsxthat return from therenderfunction and return theUserElement
/* src/App.js */
...
render() {
return (
<User/>
)
}
...- declare the properties that
Usercomponent going to use by addingpropTypes.- add at the beginning of
Userfile:import PropTypes from 'prop-types';
- and on the ending of the file:
User.propTypes = { id: PropTypes.number.isRequired, username: PropTypes.string.isRequired, email: PropTypes.string.isRequired }
note: this process isn't necessary but it's best practice.
- add at the beginning of
- on the
User.jsfile change the hard coded id, username and email to the one that came from the prop
render() {
return (
<div className="user-item">
<div>
id:{this.props.id}
</div>
<div>
username:{this.props.username}
</div>
<div>
email:{this.props.email}
</div>
</div>
);
}- on the
App.jsfile add the id, username and email to theUserelement.
<User id={1} username={'johndoe'} email={'johndoe@gmail.com'}/>- copy the
User.jsfile toEditUser.jsin the same folder (src/components). - change the class name, in the
EditUser.jsfromUsertoEditUser. - on
EditUsercomponent declarepropin theconstructorand pass it to thesuper. after that assign tothis.statethe prop that you gonna use in the component
constructor(prop) {
super(prop);
this.state = {
id: prop.defaultId,
username: prop.defaultUsername,
email: prop.defaultEmail,
}
}DON'T assign
this.state = propthis will made problem when we try to change the prop form the parent component.
- change the
PropTypesdefinition.
EditUser.propTypes = {
defaultId: PropTypes.number,
defaultUsername: PropTypes.string,
defaultEmail: PropTypes.string,
}- add
<button>element to the returningrenderfunctionjsx
<button className={'btn btn-primary'}>Save</button>- in that
<button>element addonClickproperty and assign it to thethis.save
<button className={'btn btn-primary'} onClick={this.save}>Save</button>- add new function to the
EditUserclass component named itsave - on that function call to
this.setState({ userName: 'New User Name'})
...
save() {
this.setState({
userName: 'New User Name'
})
}
...warning: when calling from
jsxelement to class function the scope changing, that why you need to bind the function to the class. i'm using the ES6fat arrow functionlike so... save = () => { this.setState({ userName: 'New User Name' }) } ...
- change the render
jsxto point to thethis.stateand not tothis.prop
return (
<div className="user-item">
...
<div>
id:{this.state.id}
</div>
<div>
username:{this.state.username}
</div>
<div>
email:{this.state.email}
</div>
...
</div>
)- add the
EditUserjsx element to theAppcomponent
<EditUser id={1} username={'johndoe'} email={'johndoe@gmail.com'} />- on
EditUsercomponent change the renderjsxusername and email to input element. - assign the value of the email element to
value={this.state.email}. - assign
updatefunction toonChangeproperty. - add
nameattributename='email' - add className '
className=form-control'. - do this also to the
usernameas well.
render() {
return (
<div className="user-item">
<div>
id:{this.state.id}
</div>
<div>
username: <input value={this.state.username} className='form-control' name='username' onChange={this.update} />
</div>
<div>
email: <input value={this.state.email} className='form-control' name='email' onChange={this.update} />
</div>
<button className={'btn btn-primary'} onClick={this.save}>Save</button>
</div>
);
}- add
updatefunction to theEditUsercomponent class.
...
update = (event) => {
let change = {};
change[event.target.name] = event.target.value
this.setState({
...change
});
}
...- copy the
renderreturn function. - delete the
Userclass. - create new function name
Userand paste therenderreturn function. - in the class, on the argument of the function, declare
props. - remove all of the
thisfrom from the pastedrenderreturn.
export default function User(props) {
return (
<div className="user-item">
<div>
id:{props.id}
</div>
<div>
username:{props.username}
</div>
<div>
email:{props.email}
</div>
</div>
);
}Pro Tip: you can still define the
propTypesjust like you did in the class.
- on the
EditUsercomponent create newpropTypescallsaveof type ofPropTypes.func.isRequired - continue in the
EditUsercomponent change the functionsaveto callthis.prop.savewith the id, username and the email that in the state.
save = () => {
this.props.save({
id: this.state.id,
username: this.state.username,
email: this.state.email,
})
}info: don't use spread operator in this case case there is the
savefunction property.
- in the constructor of the
Appcomponent add a new state, focusUser.
...
constructor() {
super();
this.state = {
focusUser: {
id: 1,
username: 'johndoe',
email: 'johndoe@gmail.com',
}
}
}
...- create new function on the
Appclass callsave. this function will get a new user andsetStatethefocusUserwith the user using ES6 spread operator.
save = (user) => {
this.setState({
focusUser: {...user}
})
}info: it's important to use spread operator to lose ref object with the incoming argument.
- on the
renderfunction change theEditUserandUserelements to get the id, username and email fromthis.state.focusUser.
<EditUser id={this.state.focusUser.id} username={this.state.focusUser.username} email={this.state.focusUser.email} />
<User id={this.state.focusUser.id} username={this.state.focusUser.username} email={this.state.focusUser.email} />- in the
EditUserelement add save property that call{this.save}
<EditUser ... save={this.save} ... />- go to
Appcomponent and in the constructor clear thefocusUservalue and createusersstate with users.
constructor() {
super();
this.state = {
focusUser: void 0,
users: [
{
id: 1,
username: 'johndoe',
email: 'johndoe@gmail.com',
},
{
id: 2,
username: 'janedoe',
email: 'janedoe@gmail.com',
},
{
id: 3,
username: 'johnsmith',
email: 'johnsmith@gmail.com',
},
{
id: 4,
username: 'janesmith',
email: 'janesmith@gmail.com',
}
]
};
}- in the
renderfunction create newletvariable calleditTag. - check if
this.state.focusUserexist, if so assign theeditTagto newEditUserwith the properties fromthis.state.focusUserif not assign empty string.
let focusUser = this.state.focusUser;
let editTag = focusUser ?
<EditUser key={focusUser.id} save={this.save} defaultId={focusUser.id} defaultUsername={focusUser.username} defaultEmail={focusUser.email} />
: '';- in the
renderreturn function replace theEditUserelement with carly braces and theeditTagvariable.
<div>
<h1>User Admin</h1>
<div className='container-fluid'>
{editTag}
</div>
</div>- continue, using curly braces creating
Userelement base onthis.state.users.
<div>
<h1>User Admin</h1>
<div className='container-fluid'>
{editTag}
{this.state
.users
.map((user, i) =>
<User
key={i}
id={user.id}
username={user.username}
email={user.email} />)
}
</div>
</div>note: both practices are fine. you can do what is more readable for you.
- on
Appcomponent addfocusfunction with id as parameter, find the user from thethis.state.usersandsetStatethefocusUserwith the user that found in theusersarray
focus = (id) => {
let focusUser = this.state.users.filter((u) => u.id === id)[0]
this.setState({
focusUser: { ...focusUser }
})
}- go to
Usercomponent and add in thepropTypesselectedandonClickproperties.
User.propTypes = {
id: PropTypes.number.isRequired,
username: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
selected: PropTypes.bool,
onClick: PropTypes.func.isRequired,
}- stay on the
Usercomponent and on the return function add to the wrapper divonClickwith theprops.onClick.
return (
<div onClick={props.onClick.bind(this, props.id)} className="user-item">
...
);note: this time using the function
bindfunction to define the context, also passingprops.idso when the function will call it will pass the user id in the arguments
- add to the
classNameproperty a condition, whenprops.selectedistruethen addselectedcss class.
return (
<div onClick={props.onClick.bind(this, props.id)}
className={'user-item ' + (props.selected ? 'selected' : '')}>
...
);- go back to
Appcomponent add to theUserelement theonClickandselectedproperties
return (
<div>
<h1>User Admin</h1>
<div className='container-fluid'>
{editTag}
{this.state
.users
.map((user, i) =>
<User
onClick={this.focus}
selected={focusUser && focusUser.id === user.id}
...
/>)
}
</div>
</div>
);- on the
savefunction find the user, that send to update, from thethis.state.usersand update the user in the array.
save = (updateUser) => {
let users = this.state.users.map((u) => {
if (u.id === updateUser.id) {
return {
...updateUser
}
} else {
return u;
}
})
this.setState({
users: users
})
}- go to
App.cssand add css style for.user-item.selected
.user-item.selected {
background-color: lightblue;
}- on
src/componentscreate new file callUserList.js. - on that file import
ReactfromreactandexportfunctionUserList
import React from 'react';
export default function UserList(props) {
}- return in this function new empty component
React.Fragment. - on the beginning of add
<hr/>tag - after that add a
ptag that print the number of user, do that usingReact.Children.count. - continue by mapping, using curly braces, the
props.childrenso that even user will wrap by div with classevenand odd with classodd.
return (
<React.Fragment>
<hr/>
<p>there are {React.Children.count(props.children)} users.</p>
<div className='list-group'>
{props.children.map((user, i) =>
<div key={i} className={(i % 2 === 0 ? 'even' : 'odd')}>
{user}
</div>
)}
</div>
</React.Fragment>
)- on
Appcomponent import the newUserListcomponent.
import UserList from './components/UserList';- wrap the users mapping with the new
UserListtag.
<UserList>
{this.state
.users
.map((user, i) =>
<User
onClick={this.focus}
selected={focusUser && focusUser.id === user.id}
key={i}
id={user.id}
username={user.username}
email={user.email}/>)
}
</UserList>- on the
App.cssfile add the css for odd class
.odd > div {
background: lightgrey;
}- install
react-router-domand save it on thepackage.json
npm i -S react-router-dom- on the
Appcomponent addBrowserRouter,RouteandSwitchto the file
Pro Tip: use
BrowserRouter as Routerto map theBrowserRoutertoRoutervariable (best practice).
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';- on the
renderfunction remove theeditTag, copy the<UserList>tag (with it's nested element). - under the
<div className='container-fluid'>add the<Router>,<Switch>. - create two
<Route>element. one for the<EditUser>element and one for the<User>element. - on the
<Route>element addpath
<div className='container-fluid'>
<Router>
<Switch>
<Route path='/edit/:id'/>
<Route path='/users'>
</Route>
</Switch>
</Router>
</div>- open and close the users
<Route>and take the<UserList>you copy and paste as nested element.
<div className='container-fluid'>
<Router>
<Switch>
<Route path='/edit/:id'/>
<Route path='/users'>
<UserList>
{this.state
.users
.map((user, i) =>
<User
onClick={this.focus}
selected={focusUser && focusUser.id === user.id}
key={i}
id={user.id}
username={user.username}
email={user.email}/>)
}
</UserList>
</Route>
</Switch>
</Router>
</div>- create new function in the
Appcomponent callbindEditUserById, passpropsas argument.
bindEditUserById = (props) => {
}- check if
props.match.params.idis aNumber. if not return empty string. - filter, from
this.state.users, the user with that id. if there isn't return empty string.
bindEditUserById = (props) => {
let id = Number(props.match.params.id;
if (!Number(id)) return ''
let focusUser = this.state.users.filter((u) => u.id === id)[0];
if (!focusUser) return ''
}- create new function call
afterSaveand pass argumentupdateUser. on that function call tothis.save(updateUser)and after that call toprops.history.push('/users').
bindEditUserById = (props) => {
let id = Number(props.match.params.id;
if (!Number(id)) return ''
let focusUser = this.state.users.filter((u) => u.id === id)[0];
if (!focusUser) return ''
let afterSave = (updateUser) => {
this.save(updateUser);
props.history.push('/users');
}
}- return the
<EditUser>element with thekey,defaultId,defaultUsernameanddefaultEmailproperties from the filter user and for thesaveproperty pass theafterSave.
bindEditUserById = (props) => {
let id = Number(props.match.params.id;
if (!Number(id)) return ''
let focusUser = this.state.users.filter((u) => u.id === id)[0];
if (!focusUser) return ''
let afterSave = (updateUser) => {
this.save(updateUser);
props.history.push('/users');
}
return <EditUser key={focusUser.id} save={afterSave} defaultId={focusUser.id} defaultUsername={focusUser.username} defaultEmail={focusUser.email} />
}- on the
renderreturn function in the edit<Route>add new property callcomponentand pass it thethis.bindEditUserById
<div className='container-fluid'>
<Router>
<Switch>
<Route path='/edit/:id' component={this.bindEditUserById} />
...
</Switch>
</Router>
</div>- import
Linkcomponent fromreact-router-dom.
import { ..., Link } from 'react-router-dom';- wrap the
<User>element inside the<UserList>component with<Link>and addtoproperty toeditplus the user id
<Link to={'/edit/' + user.id} key={i}>
<User ... />
</Link>warning: don't forget to change the
keyproperty from the<User>to the<Link>element.
- import
Redirectcomponent fromreact-router-dom.
import { ..., Redirect } from 'react-router-dom';- add
<Redirect>component to the<Switch>element. - in the
<Redirect>add two properties,from='/'andto='users'
<Redirect from='/' to='/users' />- create new component in
src/componentsfolder callNotFound
import React from 'react';
export default function NotFound(props) {
return (
<React.Fragment>
<h2>404 page not found</h2>
</React.Fragment>
);
}- import
Linkcomponent fromreact-router-dom.
import { Link } from 'react-router-dom';- add
<Link>element withtoproperty to theuserspage
import React from 'react';
import { Link } from 'react-router-dom';
export default function NotFound(props) {
return (
<React.Fragment>
<h2>404 page not found</h2>
<Link to={'/users'}> Go Back</Link>
</React.Fragment>
);
}- back to the
Appcomponent importNotFoundcomponent.
import NotFound from './components/NotFound';- on the
bindEditUserByIdfunction, replace the return empty string with the<NotFound>component
bindEditUserById = (props) => {
let id = Number(props.match.params.id);
if (!Number(id)) return <NotFound />
let focusUser = this.state.users.filter((u) => u.id === id)[0];
if (!focusUser) return <NotFound />
...
}- add to the
<Switch>element in the return of the render function<Route>to the<NotFound>component
<Switch>
...
<Route component={NotFound} />
</Switch>warning: put that route as the last element (the
<Switch>component related on the ordering of his children).