<html>
<head>
<meta name="description" content="redux-saga login example">
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser-polyfill.min.js"></script>
<script src="https://npmcdn.com/redux/dist/redux.min.js"></script>
<script src="https://npmcdn.com/redux-saga/dist/redux-saga.js"></script>
<meta charset="utf-8">
<title>react redux example</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
// noprotect
//jshint esnext:true, asi:true
console.clear()
const {
createStore,
combineReducers,
applyMiddleware,
bindActionCreators
} = Redux
const {
default: createSagaMiddleware,
effects: {take, put, call, fork, join, cancel, race} } = ReduxSaga
///////////////////////////////////////////////////////////////////
//
// Utils
//
const log = v => console.log(v)
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))
const action = type => (payload={}) => ({type, payload})
function createRequestTypes(base) {
return {
REQUEST : `${base}_REQUEST`,
SUCCESS : `${base}_SUCCESS`,
ERROR : `${base}_ERROR`
}
}
function apify(fn) {
return (args) =>
new Promise((resolve, reject) => {
setTimeout(() => {
try {
resolve(fn(args))
} catch(e) {
reject(e)
}
}, 1000)
})
}
///////////////////////////////////////////////////////////////
//
// API
//
const users = [
{name: 'admin', password: 'admin'},
{name: 'guest', password: 'guest'}
]
let tokenId = 0
function authFn({token, name, password}) {
if(token) {
return refreshTokenFn(token)
}
else {
const valid = users.some(
u => u.name === name && u.password === password
)
if(valid)
return {expires_in: 2000, id: ++tokenId, $$token: true}
else
throw 'Invalid credentials'
}
}
function refreshTokenFn(token) {
if(!token.$$token)
throw 'Invalid token'
return {token, id: ++tokenId}
}
const api = {
authorize: apify(authFn),
refreshToken: apify(refreshTokenFn)
}
///////////////////////////////////////////////////////////////
//
// Actions
//
const LOGIN = createRequestTypes('LOGIN')
const login = {
request : (name, password) => action(LOGIN.REQUEST)({name, password}),
success : (token) => action(LOGIN.SUCCESS)({token}),
error : (error) => action(LOGIN.ERROR)({error})
}
const LOGOUT = 'LOGOUT'
const logout = action(LOGOUT)
///////////////////////////////////////////////////////////////
//
// Reducers
//
const PENDING = 'PENDING'
const IN = 'IN'
const OUT = 'OUT'
function user(state = null, action) {
switch(action.type) {
case LOGIN.REQUEST:
return {
name: action.name,
password: action.password,
status: PENDING
}
case LOGIN.SUCCESS:
return {
state,
status: IN,
token: action.token
}
case LOGIN.ERROR:
return {
state,
status: OUT,
token: null,
error: action.error
}
case LOGOUT:
return null
default:
return state
}
}
const rootReducer = combineReducers({
user
})
///////////////////////////////////////////////////////////////
//
// Sagas
//
function* authorize(credentials) {
const token = yield call(api.authorize, credentials)
yield put( login.success(token) )
return token
}
function* authAndRefreshTokenOnExpiry(name, password) {
let token = yield call(authorize, {name, password})
while(true) {
yield call(delay, token.expires_in)
token = yield call(authorize, {token})
}
}
function* watchAuth() {
while(true) {
const {name, password} = yield take(LOGIN.REQUEST)
yield race([
take(LOGOUT),
call(authAndRefreshTokenOnExpiry, name, password)
])
}
}
///////////////////////////////////////////////////////////////
//
// Create the store. Log states into the console
//
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
rootReducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(watchAuth)
let lastState
store.subscribe(() => {
const state = store.getState()
if(state !== lastState) {
lastState = state
console.log('user:', lastState.user)
}
})
/* test */
store.dispatch(login.request('admin', 'admin'))
setTimeout(() => {
store.dispatch(logout())
}, 14333)
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |