Last active
January 26, 2020 14:33
-
-
Save kcak11/da0abfadee17499aac6d5d64ae3371e3 to your computer and use it in GitHub Desktop.
React & Redux Apps Template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- Our App will reside here --> | |
<div id="app"></div> | |
<!-- Portal Container --> | |
<div id="portalContainer"></div> | |
<!-- | |
© 2018 https://kcak11.com / https://ashishkumarkc.com | |
--> |
This template imports the CDNs for the following:
- React
- ReactDOM
- Redux
- React-Redux
- Redux-Thunk
- React-Router-DOM
- Redux-Undo
It also provides a basic scaffolding for the App
The JavaScript preprocessor is set as Babel
The template has implementation examples for:
- React
- Redux
- combineReducers
- Redux-Thunk
- React-Router-DOM
- withRouter
- compose multiple middlewares
- Redux-Undo
- Error Boundaries in React
- Context creation in React
- Function Component
- PureComponent
- createRef
- forwardRef
- callback ref
- ReactDOM.findDOMNode
- Accessing DOM element directly using "ref"
- Data Binding (UI->State->UI)
- render props
- Fragments
- Portals
- any many more . . .
A Pen by K.C.Ashish Kumar on CodePen.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
© 2018 https://kcak11.com / https://ashishkumarkc.com | |
*/ | |
let _module = function() { | |
"use strict"; | |
/* Start Dependency Imports */ | |
const { Fragment } = React; | |
const { | |
createStore, | |
bindActionCreators, | |
combineReducers, | |
applyMiddleware, | |
compose | |
} = Redux; | |
const { Provider, connect } = ReactRedux; | |
const thunk = ReduxThunk.default; | |
const Router = ReactRouterDOM.BrowserRouter; | |
const { Route, Link, Switch, withRouter } = ReactRouterDOM; | |
const undoable = ReduxUndo.default; | |
const urActionCreators = ReduxUndo.ActionCreators; | |
const excludeAction = ReduxUndo.excludeAction; | |
const logMsg = CommonUtils.logger; | |
const getUniqueId = CommonUtils.uniqueToken; | |
/*End Dependency Imports*/ | |
/* Creating a context for the App - optional */ | |
const AppContext = React.createContext(); | |
/* An example of PureComponent created using fat-arrow function instead of class */ | |
/* AppContext.Consumer is for passing in the context, it can be ommitted if context is not needed */ | |
const FunctionComponent = props => { | |
logMsg("render - FunctionComponent"); | |
return ( | |
<AppContext.Consumer> | |
{contextData => ( | |
<div | |
data-locale={contextData.locale} | |
data-developer={contextData.dev} | |
> | |
Function Component: {props.attr1} {props.attr2} (passed via | |
props) | |
</div> | |
)} | |
</AppContext.Consumer> | |
); | |
}; | |
/* Component that uses Ref Forwarding Technique */ | |
const MyComponent = React.forwardRef((props, ref) => { | |
logMsg("render - MyComponent"); | |
return ( | |
<div ref={ref} data-info="mybutton"> | |
An Inner Component that has forwarded ref | |
</div> | |
); | |
}); | |
/* Component that uses ref callback technique */ | |
const MyOtherComponent = props => { | |
logMsg("render - MyOtherComponent"); | |
return <div ref={props.myOtherComponentRef} />; | |
}; | |
/* Start demonstrating render props */ | |
class Parent extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
val1: 2, | |
val2: 4 | |
}; | |
} | |
render() { | |
logMsg("render - Parent"); | |
return <div data-type="Parent">{this.props.render(this.state)}</div>; | |
} | |
} | |
class Child extends React.Component { | |
render() { | |
logMsg("render - Child"); | |
return ( | |
<Fragment> | |
<span style={{ display: "block" }}> | |
val1: {this.props.componentInfo.val1} | |
</span> | |
<span style={{ display: "block" }}> | |
val2: {this.props.componentInfo.val2} | |
</span> | |
</Fragment> | |
); | |
} | |
} | |
/* End demonstrating render props */ | |
/* Start demonstrating findDOMNode Example */ | |
class DomRefExample extends React.Component { | |
componentDidMount() { | |
logMsg("componentDidMount - DomRefExample"); | |
this.refs.sampleText1.style.color = "#00f"; | |
ReactDOM.findDOMNode(this.refs.sampleText2).querySelector(".sampleText2_class").style.color = "#f00"; | |
} | |
render() { | |
logMsg("render - DomRefExample"); | |
return ( | |
<div> | |
<span ref="sampleText1">Text color changed by direct ref</span> | |
<br /> | |
<span ref="sampleText2"> | |
<span className="sampleText2_class"> | |
Text color changed by findDOMNode | |
</span> | |
</span> | |
</div> | |
); | |
} | |
} | |
/* End demonstrating findDOMNode Example */ | |
/* Start DataBinding Example */ | |
class DataBindingExample extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { textInput: "" }; | |
this.updateTextInputValue = this.updateTextInputValue.bind(this); | |
logMsg("constructor - DataBindingExample"); | |
} | |
updateTextInputValue(evt) { | |
this.setState({ textInput: evt.target.value }); | |
} | |
render() { | |
logMsg("render - DataBindingExample"); | |
return ( | |
<div> | |
<input | |
style={{ | |
width: "300px", | |
border: "2px solid #000", | |
padding: "5px" | |
}} | |
placeholder="type something in this box . . ." | |
type="text" | |
value={this.state.textInput} | |
onChange={this.updateTextInputValue} | |
/> | |
<span | |
style={{ | |
color: "#f00", | |
position: "relative", | |
left: "20px", | |
top: "-7px" | |
}} | |
> | |
{this.state.textInput} | |
</span> | |
</div> | |
); | |
} | |
} | |
/* End DataBinding Example */ | |
/* Start demonstrating Portals */ | |
class MyPortalComponent extends React.Component { | |
render() { | |
return ReactDOM.createPortal( | |
<Fragment> | |
<br /> | |
<div> | |
This text is rendered via MyPortalComponent using the React | |
Portals technique | |
</div> | |
</Fragment>, | |
document.getElementById("portalContainer") | |
); | |
} | |
} | |
/* End demonstrating Portals */ | |
/* The main App */ | |
class App extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = {}; | |
this.myComponentRef = React.createRef(); | |
logMsg("constructor - App"); | |
} | |
componentDidMount() { | |
logMsg("componentDidMount - App"); | |
this.setState((state, props) => { | |
return { | |
info: "Hello World - React App !!" | |
}; | |
}); | |
this.fetchPlayListData(); | |
} | |
componentDidUpdate() { | |
logMsg("componentDidUpdate - App"); | |
// using the ref and updating the UI component | |
if (this.myComponentRef.current) { | |
this.myComponentRef.current.style.color = "#f00"; | |
} | |
if (this.otherComponentRef) { | |
this.otherComponentRef.innerHTML = "Ref obtained via callback ref"; | |
} | |
} | |
componentWillUnmount() { | |
logMsg("componentWillUnmount - App"); | |
} | |
static getDerivedStateFromError(error) { | |
// Update state so the next render will show the fallback UI. | |
return { errorOccurred: true }; | |
} | |
componentDidCatch(error, info) { | |
logMsg("componentDidCatch - App\n", error, "\n\n", info); | |
} | |
renderDataLoadingUI() { | |
return <h4>Please wait, loading data . . .</h4>; | |
} | |
renderUserDataUI() { | |
let dataRows = []; | |
for (let i = 0; i < this.props.userList.present.length; i++) { | |
dataRows.push( | |
<UserDataRow | |
key={"user_" + getUniqueId()} | |
index={i} | |
removeUser={this.props.removeUser} | |
data={this.props.userList.present[i]} | |
userList={this.props.userList} | |
/> | |
); | |
} | |
return ( | |
<Fragment> | |
<AddUser addUser={this.props.addUser} /> <br /> | |
<table> | |
<thead> | |
<tr> | |
<th>FirstName</th> | |
<th>LastName</th> | |
<th className="center_align">Delete User</th> | |
</tr> | |
</thead> | |
<tbody>{dataRows}</tbody> | |
</table> | |
<hr /> | |
</Fragment> | |
); | |
} | |
render() { | |
logMsg("render - App"); | |
if (this.state.errorOccurred) { | |
return <div>Oops something failed :-(</div>; | |
} | |
return ( | |
<AppContext.Provider value={{ locale: "en_US", dev: "kcak11" }}> | |
<Header fixed={true} /> | |
<div> | |
{this.state.info} (rendered via | |
<b>{this.state.info}</b>) <br /> | |
<MyComponent ref={this.myComponentRef} /> | |
<MyOtherComponent | |
myOtherComponentRef={elem => | |
(this.otherComponentRef = elem) | |
} | |
/> | |
<MyPortalComponent /> | |
<FunctionComponent attr1="Value1" attr2="Value2" /> | |
<hr /> | |
<DomRefExample /> | |
<hr /> | |
<DataBindingExample /> | |
<hr /> | |
<h4>Demonstrate render props:</h4> | |
<Parent render={data => <Child componentInfo={data} />} /> | |
<hr />Random Number (via Redux): | |
<a href="#" onClick={this.doUpdateRandomNum.bind(this)}> | |
{this.props.randomNum} [click to refresh] | |
</a> | |
<hr /> | |
<SubComponent today={new Date()}>Test String</SubComponent> | |
<hr /> | |
{_.get(this.state, 'playlist.data.length', 0) > 0 && ( | |
<h3>PlayList Data (fetched via AJAX)</h3> | |
)} | |
{_.get(this.state, 'playlist.data', []).map((item, i) => ( | |
<div | |
key={"playlistitem_" + getUniqueId()} | |
className="playListRow" | |
> | |
{item.name} | |
</div> | |
))} | |
<hr /> | |
<ReactRouterExample /> | |
<hr /> | |
<h3>User Data (state maintained using Redux)</h3> | |
{!this.props.userDataLoaded | |
? this.renderDataLoadingUI() | |
: this.renderUserDataUI()} | |
<button | |
onClick={this.doUnmountApp.bind(this)} | |
className="unmountAppBtn" | |
type="button" | |
> | |
Unmount App | |
</button> | |
</div> | |
</AppContext.Provider> | |
); | |
} | |
doUpdateRandomNum(e) { | |
e.preventDefault(); | |
this.props.updateRandomNum(Math.random()); | |
} | |
doUnmountApp() { | |
let decision = confirm( | |
"Are you sure that you want to unmount the current App ?\nYou may have to refresh your browser to re-run the app." | |
); | |
if (decision) { | |
ReactDOM.unmountComponentAtNode(document.getElementById("app")); | |
let msgElem = | |
document.querySelector("#unmountMsgElem") || | |
document.createElement("div"); | |
msgElem.id = "unmountMsgElem"; | |
msgElem.innerHTML = | |
"<h2>App Unmounted Successfully !!</h2><h4>Please <span id='remountLink' style='color:#f00;cursor:pointer;'>click here to ReMount the App</span> or refresh your browser.</h4>"; | |
document.querySelector("body").appendChild(msgElem); | |
document.querySelector("#remountLink").addEventListener( | |
"click", | |
function(e) { | |
e.preventDefault(); | |
document.querySelector("#unmountMsgElem").innerHTML = ""; | |
new AppBootstrap(_module).run(); | |
}, | |
false | |
); | |
} | |
} | |
fetchPlayListData() { | |
let self = this; | |
$api.call({ | |
method: "get", | |
url: "https://services-kcak11.firebaseio.com/playlist.json", | |
success: response => { | |
let obj; | |
try { | |
obj = JSON.parse(response); | |
self.setState({ playlist: obj }); | |
} catch (exjs) {} | |
} | |
}); | |
} | |
} | |
/* Header Component */ | |
class Header extends React.Component { | |
render() { | |
logMsg("render - Header"); | |
let headerClass = "pageHeader"; | |
if (Boolean(this.props.fixed)) { | |
headerClass += " fixed"; | |
} | |
return ( | |
<Fragment> | |
<div className={headerClass}> | |
<h1 className="pageTitle"> | |
React & Redux App (powered by{" "} | |
<a href="https://www.kcak11.com" target="_blank"> | |
Ashish's Web | |
</a>) | |
</h1> | |
<h5 className="subTitle"> | |
Watch the browser console for lifecycle methods | |
</h5> | |
</div> | |
{Boolean(this.props.fixed) && ( | |
<div style={{ height: "100px" }} /> | |
)} | |
</Fragment> | |
); | |
} | |
} | |
/* SubComponent class */ | |
class SubComponent extends React.PureComponent { | |
constructor(props) { | |
super(props); | |
this.state = { date: props.today.toString() }; | |
logMsg("constructor - SubComponent"); | |
} | |
render() { | |
logMsg("render - SubComponent"); | |
return ( | |
<div style={{ color: "#f00" }}> | |
- - Sub Component Rendered @ {this.state.date} <br /> | |
- - - Child Content: {this.props.children} | |
<br /> | |
<input type="checkbox" id="test_checkbox" /> | |
<label htmlFor="test_checkbox" style={{ color: "#000" }}> | |
label linked using htmlFor | |
</label> | |
</div> | |
); | |
} | |
} | |
/* Component to demonstrate React Router usage */ | |
class ReactRouterExample extends React.Component { | |
render() { | |
logMsg("render - ReactRouterExample"); | |
return ( | |
<Fragment> | |
<h2>React Router Example</h2> | |
<div> | |
<ul> | |
<li> | |
<Link to="/home">Home</Link> | |
</li> | |
<li> | |
<Link to="/about">About</Link> | |
</li> | |
<li> | |
<Link to="/about/12345"> | |
About 12345 (invoking Router link with data) | |
</Link> | |
</li> | |
<li> | |
<Link to="/contact">Contact</Link> | |
</li> | |
</ul> | |
<div> | |
<Switch> | |
<Route path="/home" component={Home} /> | |
<Route exact path="/about" component={About} /> | |
<Route path="/about/:dataId" component={About} /> | |
<Route path="/contact" component={Contact} /> | |
</Switch> | |
</div> | |
</div> | |
<h6> | |
Note: The router example here is implemented only for client | |
side routing. The server side routes do not exist, hence you | |
might landup on a 404(Page not found) scenario if you refresh | |
the browser after clicking on the route links above. | |
</h6> | |
</Fragment> | |
); | |
} | |
} | |
/* A component that will render the Back link when navigating */ | |
class Back extends React.Component { | |
doBack(e) { | |
e.preventDefault(); | |
history.go(-1); | |
} | |
render() { | |
logMsg("render - Back"); | |
return ( | |
<a href="#" onClick={this.doBack.bind(this)}> | |
Back | |
</a> | |
); | |
} | |
} | |
/* Home Component */ | |
class Home extends React.Component { | |
render() { | |
logMsg("render - Home"); | |
return ( | |
<div> | |
<h3>Home...</h3> | |
<Back /> | |
</div> | |
); | |
} | |
componentWillUnmount() { | |
logMsg("componentWillUnmount - Home"); | |
} | |
} | |
/* About Component */ | |
class About extends React.Component { | |
render() { | |
logMsg("render - About"); | |
return ( | |
<div> | |
<h3>About... {this.props.match.params.dataId}</h3> | |
<Back /> | |
</div> | |
); | |
} | |
componentWillUnmount() { | |
logMsg("componentWillUnmount - About"); | |
} | |
} | |
/* Contact Component */ | |
class Contact extends React.Component { | |
render() { | |
logMsg("render - Contact"); | |
return ( | |
<div> | |
<h3>Contact...</h3> | |
<Back /> | |
</div> | |
); | |
} | |
componentWillUnmount() { | |
logMsg("componentWillUnmount - Contact"); | |
} | |
} | |
/* AddUser Component - renders the form for information input and the undo/redo buttons */ | |
class AddUser extends React.Component { | |
constructor(props) { | |
super(props); | |
} | |
handleAddUserSubmission(e) { | |
e.preventDefault(); | |
let refs = this.refs; | |
let firstName = refs.firstName.value; | |
let lastName = refs.lastName.value; | |
if (!firstName.trim() || !lastName.trim()) { | |
alert("<First Name> and <Last Name> fields are required."); | |
return; | |
} | |
// Trigger action | |
this.props.addUser(firstName, lastName); | |
// Reset form | |
refs.firstName.value = ""; | |
refs.lastName.value = ""; | |
refs.firstName.focus(); | |
} | |
doUndo() { | |
let stateObj = store.getState(); | |
let undoAvailable = _.get(stateObj, 'userList.past.length', 0) > 1; | |
if (undoAvailable) { | |
store.dispatch(urActionCreators.undo()); | |
} | |
} | |
doRedo() { | |
let stateObj = store.getState(); | |
let redoAvailable = _.get(stateObj, 'userList.future.length' ,0) > 0; | |
if (redoAvailable) { | |
store.dispatch(urActionCreators.redo()); | |
} | |
} | |
render() { | |
logMsg("render - AddUser"); | |
let stateObj = store.getState(); | |
let undoAvailable = _.get(stateObj, 'userList.past.length', 0) > 1; | |
let redoAvailable = _.get(stateObj, 'userList.future.length' ,0) > 0; | |
return ( | |
<form onSubmit={this.handleAddUserSubmission.bind(this)}> | |
<input | |
type="text" | |
placeholder="First Name" | |
ref="firstName" | |
name="firstName" | |
/> | |
<input | |
type="text" | |
placeholder="Last Name" | |
ref="lastName" | |
name="lastName" | |
/> | |
<button type="submit" className="addUserBtn"> | |
Add User | |
</button> | |
<button | |
type="button" | |
className="undoBtn" | |
onClick={this.doUndo.bind(this)} | |
disabled={!undoAvailable} | |
> | |
Undo | |
</button> | |
<button | |
type="button" | |
className="redoBtn" | |
onClick={this.doRedo.bind(this)} | |
disabled={!redoAvailable} | |
> | |
Redo | |
</button> | |
</form> | |
); | |
} | |
} | |
/* UserDataRow component to display the list of users */ | |
class UserDataRow extends React.Component { | |
static contextType = AppContext; | |
constructor(props) { | |
super(props); | |
} | |
handleDeleteUserClick() { | |
let userList = this.props.userList.present; | |
if (userList.length === 1) { | |
alert("Cannot delete all users, atleast one is required"); | |
return; | |
} | |
let index = this.props.index; | |
this.props.removeUser(index); | |
} | |
render() { | |
logMsg("render - UserDataRow"); | |
return ( | |
<tr | |
data-locale={this.context.locale} | |
data-developer={this.context.dev} | |
> | |
<td>{this.props.data.firstName}</td> | |
<td>{this.props.data.lastName}</td> | |
<td className="center_align"> | |
<button | |
type="button" | |
className="delBtn" | |
onClick={this.handleDeleteUserClick.bind(this)} | |
> | |
× Delete User | |
</button> | |
</td> | |
</tr> | |
); | |
} | |
} | |
/* App's initial state */ | |
const initialState = { | |
userList: [], | |
randomNum: 0, | |
userDataLoaded: false | |
}; | |
/* Action Creator for Thunk */ | |
const userDataLoaded = userdata => { | |
return { | |
type: "USER_DATA_LOADED", | |
data: userdata | |
}; | |
}; | |
/* Action Creator for Thunk */ | |
const initialRandomLoaded = rval => { | |
return { | |
type: "INITIAL_RANDOM_LOADED", | |
data: rval | |
}; | |
}; | |
/* load initial data here */ | |
let loadInitialStateData = () => { | |
return dispatch => { | |
dispatch(initialRandomLoaded(Math.random())); | |
$api.call({ | |
method: "get", | |
url: "https://pen.kcak11.com/mockData/users.json", | |
success: response => { | |
let userList = JSON.parse(response); | |
dispatch(userDataLoaded(userList)); | |
dispatch({ type: "USER_DATA_READY", data: true }); | |
} | |
}); | |
}; | |
}; | |
let userListReducer = (state = [], action) => { | |
let newState; | |
switch (action.type) { | |
case "USER_DATA_LOADED": | |
return action.data; | |
case "USER_DATA_LOADED_STATIC": | |
newState = Object.assign([], action.userList); | |
return newState; | |
case "ADD_USER": | |
// Return a new array with old state and added user. | |
newState = [ | |
{ | |
firstName: action.firstName, | |
lastName: action.lastName | |
}, | |
...state | |
]; | |
return newState; | |
case "REMOVE_USER": | |
newState = [ | |
// Grab state from begging to index of one to delete | |
...state.slice(0, action.index), | |
// Grab state from the one after one we want to delete | |
...state.slice(action.index + 1) | |
]; | |
return newState; | |
default: | |
return state; | |
} | |
}; | |
let randomNumReducer = (state = 0, action) => { | |
let newState; | |
switch (action.type) { | |
case "INITIAL_RANDOM_LOADED": | |
return action.data; | |
case "UPDATE_RANDOM": | |
newState = Math.random(); | |
return newState; | |
default: | |
return state; | |
} | |
}; | |
let userDataLoadedReducer = (state = false, action) => { | |
switch (action.type) { | |
case "USER_DATA_READY": | |
return action.data; | |
default: | |
return state; | |
} | |
}; | |
/* rootReducer combines the various reducers for our app */ | |
let rootReducer = combineReducers({ | |
userList: undoable(userListReducer, { | |
filter: excludeAction(["INITIAL_RANDOM_LOADED", "UPDATE_RANDOM"]) | |
}), | |
randomNum: randomNumReducer, | |
userDataLoaded: userDataLoadedReducer | |
}); | |
/* actions for our app */ | |
const actions = { | |
addUser: (firstName, lastName) => { | |
return { | |
type: "ADD_USER", | |
id: getUniqueId(), | |
firstName, | |
lastName | |
}; | |
}, | |
removeUser: index => { | |
return { | |
type: "REMOVE_USER", | |
index | |
}; | |
}, | |
updateRandomNum: rnum => { | |
return { | |
type: "UPDATE_RANDOM", | |
rnum | |
}; | |
} | |
}; | |
/* AppContainer for the App */ | |
/* withRouter is needed when we want the Router capabilities for our App, else it can be ommitted */ | |
const AppContainer = withRouter( | |
connect( | |
function mapStateToProps(state) { | |
return { | |
userList: state.userList, | |
randomNum: state.randomNum, | |
userDataLoaded: state.userDataLoaded | |
}; | |
}, | |
function mapDispatchToProps(dispatch) { | |
return bindActionCreators(actions, dispatch); | |
} | |
)(App) | |
); | |
let store; | |
/* Disabling Redux Dev Tools as it is buggy */ | |
let reduxDevToolsEnabled = false; | |
let loadInitialStateDataViaStaticCode = () => { | |
let userList = [ | |
{ | |
firstName: "Ervin", | |
lastName: "Howell", | |
id: "B34E952CA61F87D0" | |
}, | |
{ | |
firstName: "Clementine", | |
lastName: "Bauch", | |
id: "23894BEC50A716DF" | |
} | |
]; | |
let createStoreArgs = []; | |
createStoreArgs.push(rootReducer); | |
createStoreArgs.push(initialState); | |
if (reduxDevToolsEnabled && window.__REDUX_DEVTOOLS_EXTENSION__) { | |
createStoreArgs.push(window.__REDUX_DEVTOOLS_EXTENSION__()); | |
} | |
store = createStore.apply(null, createStoreArgs); | |
store.dispatch({ | |
type: "INITIAL_RANDOM_LOADED", | |
data: Math.random() | |
}); | |
store.dispatch({ | |
type: "USER_DATA_LOADED_STATIC", | |
userList: userList, | |
randomNum: Math.random() | |
}); | |
store.dispatch({ type: "USER_DATA_READY", data: true }); | |
}; | |
// use 'compose' when applying multiple middlewares, in example below we apply the thunk, devtools extension | |
let loadInitialStateDataViaAJAX = () => { | |
let middlewares = [applyMiddleware(thunk)]; | |
if (reduxDevToolsEnabled && window.__REDUX_DEVTOOLS_EXTENSION__) { | |
middlewares.push(window.__REDUX_DEVTOOLS_EXTENSION__()); | |
} | |
store = createStore(rootReducer, compose.apply(null, middlewares)); | |
store.dispatch(loadInitialStateData()); | |
}; | |
//loadInitialStateDataViaStaticCode(); | |
loadInitialStateDataViaAJAX(); | |
/* The React app gets mounted here */ | |
ReactDOM.render( | |
<Provider store={store}> | |
<Router forceRefresh={false}> | |
<AppContainer /> | |
</Router> | |
</Provider>, | |
document.getElementById("app") | |
); | |
document.addEventListener( | |
"contextmenu", | |
function(e) { | |
e.preventDefault(); | |
}, | |
false | |
); | |
document.title = "React & Redux Apps Template"; | |
}; | |
new AppBootstrap(_module).run(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script> | |
<script src="https://cdn.kcak11.com/libraries/app-bootstrap.min.js"></script> | |
<script src="https://cdn.kcak11.com/polyfills/object-assign.js"></script> | |
<script src="https://cdn.kcak11.com/libraries/ajaxify.js"></script> | |
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script> | |
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.1.1/react-redux.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-thunk/2.3.0/redux-thunk.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router/4.3.1/react-router.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router-dom/4.3.1/react-router-dom.min.js"></script> | |
<script src="https://cdn.kcak11.com/libraries/redux-undo.min.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
© 2018 https://kcak11.com / https://ashishkumarkc.com | |
*/ | |
* { | |
outline: none; | |
} | |
html { | |
overflow-x: hidden; | |
} | |
body { | |
font-family: Verdana; | |
padding: 20px 10px; | |
overflow-x: hidden; | |
} | |
.pageHeader { | |
background-color: #000; | |
color: #fff; | |
margin: 0; | |
padding: 10px; | |
margin-bottom: 30px; | |
} | |
.pageHeader.fixed { | |
position: fixed; | |
top: 0; | |
left: 0; | |
right: 0; | |
z-index: 11; | |
} | |
a { | |
color: #6db1dc; | |
} | |
hr { | |
background-color: #97c5e4; | |
border: none; | |
height: 4px; | |
margin: 22px -30px; | |
} | |
.pageTitle { | |
font-size: 20px; | |
color: #6db1dc; | |
} | |
.subTitle { | |
position: relative; | |
top: -20px; | |
height: 1px; | |
} | |
.playListRow { | |
color: orange; | |
border: 1px solid #000; | |
} | |
table { | |
width: 600px; | |
border-spacing: 0; | |
border-collapse: collapse; | |
margin-left: 5px; | |
} | |
table th, | |
table td { | |
width: 200px; | |
text-align: left; | |
border: 1px solid #000; | |
padding-left: 5px; | |
} | |
.delBtn { | |
background-color: #f00; | |
color: #fff; | |
} | |
.center_align { | |
text-align: center; | |
} | |
input[type="text"] { | |
height: 30px; | |
width: 200px; | |
font-size: 16px; | |
box-sizing: border-box; | |
padding: 0; | |
margin-left: 5px; | |
display: inline-block; | |
vertical-align: bottom; | |
} | |
.addUserBtn, | |
.undoBtn, | |
.redoBtn, | |
.unmountAppBtn { | |
background-color: #000; | |
color: #fff; | |
height: 30px; | |
width: 100px; | |
margin-left: 5px; | |
display: inline-block; | |
vertical-align: bottom; | |
border: 2px solid #f00; | |
box-sizing: border-box; | |
} | |
.undoBtn, | |
.redoBtn { | |
border: 2px solid #97c5e4; | |
background-color: #fff; | |
color: #000; | |
} | |
.undoBtn[disabled], | |
.redoBtn[disabled] { | |
border: 2px solid #000; | |
background-color: #f3f3f3; | |
opacity: 0.25; | |
} | |
.unmountAppBtn { | |
height: 50px; | |
width: 242px; | |
font-size: 30px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment