preact/hooks#useEffect JavaScript Examples
The following examples show how to use
preact/hooks#useEffect.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: invalid.js From eslint-config-preact with MIT License | 7 votes |
export function Foo () {
const [value, setValue] = useState(0);
const increment = useCallback(() => setValue(value + 1));
useEffect(() => {
console.log(value);
}, []);
return <button onClick={increment}>{value}</button>;
}
Example #2
Source File: index.js From duolingo-solution-viewer with MIT License | 6 votes |
useLocalStorageList = (key, stateSet, initialValue) => {
const isInitialized = useRef(false);
const [ storedState, storeState ] = useLocalStorage(key, initialValue);
const { state, prevState, nextState, prev, next } = useStateList(
stateSet,
stateSet.indexOf(storedState) === -1 ? initialValue : storedState
);
useEffect(() => {
// Skip the first call to prevent overriding the stored state with a temporary default state.
if (isInitialized.current) {
storeState(state)
} else {
isInitialized.current = true;
}
}, [ state, storeState ]);
return { state, prevState, nextState, prev, next };
}
Example #3
Source File: app.js From shorts-ui with MIT License | 6 votes |
App = () => {
let [ashProperties, setAshProperties] = useState(null);
// eslint-disable-next-line no-undef
useEffect(() => setAshProperties(globalAshProperties), []);
if (ashProperties?.inAftercore === "true") {
const localBellPockets = [...bellPockets,
['Frost-rimed desk bell', 'Cold wads, nuggets, powder', 587, '/images/adventureimages/ccs_herald.gif'],
];
return (
<div id="preact_root">
<PropertiesContext.Provider value={ashProperties}>
<ButtonRow title="Bells" buttons={localBellPockets} />
<ButtonRow title="Chess Pieces" buttons={chessPockets} />
<ButtonRow title="Yeg's Stuff" buttons={yegPockets} />
<ButtonRow title="Other Items" buttons={aftercoreItemPockets} />
</PropertiesContext.Provider>
</div>
);
}
const localItemPockets = ashProperties?.lastUsername?.toLowerCase() !== 'accodorian' ? itemPockets : [...itemPockets,
['Jumbo olive', 'Oil of slipperiness', 570, '/images/itemimages/olive.gif'],
];
const ascensions = ashProperties?.knownAscensions ?? 0;
const localFightPockets = [...fightPockets, ascensions % 2 == 0 ? skinflute : camelsToe];
return (
<div id="preact_root">
<PropertiesContext.Provider value={ashProperties}>
<ButtonRow title="Stats" buttons={statPockets} />
<ButtonRow title="Fights" buttons={localFightPockets} />
<ButtonRow title="Bell Fights" buttons={bellPockets} />
<ButtonRow title="Buffs" buttons={buffPockets} />
<ButtonRow title="Items" buttons={localItemPockets} />
<ButtonRow title="Chess Pieces" buttons={chessPockets} />
</PropertiesContext.Provider>
</div>
);
}
Example #4
Source File: valid.js From eslint-config-preact with MIT License | 6 votes |
export function Foo () {
const [value, setValue] = useState(0);
const increment = useCallback(() => setValue(v => v + 1), [setValue]);
useEffect(() => {
console.log(value);
}, [value]);
return <button onClick={increment}>{value}</button>;
}
Example #5
Source File: modal.js From rctf with BSD 3-Clause "New" or "Revised" License | 6 votes |
function Modal ({
classes, open, onClose, children
}) {
const [isLinger, setIsLinger] = useState(open)
useEffect(() => {
if (open) {
setIsLinger(true)
} else {
const timer = setTimeout(() => {
setIsLinger(false)
}, ANIMATION_DURATION)
return () => clearTimeout(timer)
}
}, [open])
useEffect(() => {
function listener (e) {
if (e.key === 'Escape') {
onClose()
}
}
if (open) {
document.addEventListener('keyup', listener)
return () => document.removeEventListener('keyup', listener)
}
}, [open, onClose])
return (open || isLinger) && createPortal((
<div class={`modal shown ${classes.animated}${open ? '' : ' leaving'}`} hidden={!(open || isLinger)}>
<div class='modal-overlay' onClick={onClose} aria-label='Close' />
<div class={`modal-content ${classes.modal}`} role='document'>
{children}
</div>
</div>
), document.body)
}
Example #6
Source File: A2H.jsx From todo-pwa with MIT License | 6 votes |
A2H = () => {
const [prompt, setPrompt] = useState(false);
useEffect(() => {
// this will catch the beforeinstallprompt and prevents the native prompt from appearing
window.addEventListener('beforeinstallprompt', e => {
e.preventDefault();
setPrompt(e);
});
}, []);
if (!prompt) {
return '';
}
return (
<button
className="text-white bg-indigo-800 hover:bg-indigo-700 rounded-full shadow a2h"
onClick={() => prompt.prompt() /* fire the prompt on button click */}
>
<svg viewBox="0 0 24 24">
<path
fill="currentColor"
d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"
/>
</svg>
</button>
);
}
Example #7
Source File: pending-token.js From rctf with BSD 3-Clause "New" or "Revised" License | 6 votes |
PendingToken = ({ authToken }) => {
const [user, setUser] = useState(null)
useEffect(() => {
(async () => {
if (!authToken) {
return
}
const user = await pendingPrivateProfile({ authToken })
setUser(user)
})()
}, [authToken])
const handleLoginClick = useCallback(() => {
setAuthToken({ authToken })
}, [authToken])
if (!user) {
return null
}
return (
<Fragment>
<div class='row u-center'>
<h3>Login as {user.name}?</h3>
</div>
<div class='row u-center'>
<button class='btn-info' onClick={handleLoginClick}>Login</button>
</div>
</Fragment>
)
}
Example #8
Source File: Footer.js From vegemite with MIT License | 6 votes |
export default function (props) {
const { todos, filter, count } = props;
useEffect(() => {
addEventListener('hashchange', onhashchange);
return () => removeEventListener('hashchange', onhashchange);
});
return (
<footer class="footer">
<span class="todo-count">
<strong>{count}</strong> {pluralize(count, 'item')} left
</span>
<ul class="filters">
<li><a href="#/" class={filter == 'all' && 'selected'}>All</a></li>
<li><a href="#/active" class={filter == 'active' && 'selected'}>Active</a></li>
<li><a href="#/completed" class={filter == 'completed' && 'selected'}>Completed</a></li>
</ul>
{
(todos.length - count) > 0 && (
<button class="clear-completed" onclick={onClear}>Clear completed</button>
)
}
</footer>
);
}
Example #9
Source File: App.js From vegemite with MIT License | 6 votes |
// ---
export default function () {
const [state, setState] = useState(todomvc.state);
useEffect(() => todomvc.listen(setState));
const actives = state.todos.filter(FILTER.active).length;
const visibles = state.todos.filter(FILTER[state.filter]);
return (
<div class="todoapp">
<header class="header">
<h1>todos</h1>
<input
class="new-todo"
placeholder="What needs to be done?"
onKeyDown={onkeydown} autoFocus={true}
/>
</header>
{
state.todos.length ? [
<section class="main">
<input
type="checkbox"
class="toggle-all"
onchange={ontoggleall}
checked={!actives}
/>
<ul class="todo-list">
{ visibles.map(x => <Item key={x.id} {...x} />) }
</ul>
</section>,
<Footer {...state} count={actives} />
] : null
}
</div>
);
}
Example #10
Source File: index.js From ReactCookbook-source with MIT License | 6 votes |
Profile = ({ user }) => {
const [time, setTime] = useState(Date.now());
const [count, setCount] = useState(10);
useEffect(() => {
let timer = setInterval(() => setTime(Date.now()), 1000);
return () => clearInterval(timer);
}, []);
return (
<div class={style.profile}>
<h1>Profile: {user}</h1>
<p>This is the user profile for a user named { user }.</p>
<div>Current time: {new Date(time).toLocaleString()}</div>
<p>
<button onClick={() => setCount((count) => count + 1)}>Click Me</button>
{' '}
Clicked {count} times.
</p>
</div>
);
}
Example #11
Source File: toast.js From rctf with BSD 3-Clause "New" or "Revised" License | 6 votes |
function Toast ({ children, remove, type, id }) {
const wrappedRemove = useCallback(() => remove(id), [remove, id])
useEffect(() => {
const duration = 1000 * 5
const timeout = setTimeout(wrappedRemove, duration)
return () => clearTimeout(timeout)
}, [wrappedRemove])
return (
<div className={`toast toast--${type}`}>
{children}
<button onClick={wrappedRemove} className='btn-close' />
</div>
)
}
Example #12
Source File: FilterInput.js From duolingo-solution-viewer with MIT License | 6 votes |
SuggestionsDropdown = ({ context, classNames, children }) => {
const [ isClosed, setIsClosed ] = useState(false);
// Reopens the dropdown after a scroll / resize event when the suggestions have changed.
useEffect(() => setIsClosed(false), [ children, setIsClosed ]);
if (isClosed) {
return null;
}
return (
<div className={classNames.suggestions}>
<Dropdown
context={context}
options={children}
renderOption={identity}
onClose={() => setIsClosed(true)}
/>
</div>
);
}
Example #13
Source File: verify.js From rctf with BSD 3-Clause "New" or "Revised" License | 6 votes |
Verify = () => {
const [authToken, setAuthToken] = useState(null)
const [emailSet, setEmailSet] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
document.title = `Verify | ${config.ctfName}`
;(async () => {
const qs = new URLSearchParams(location.search)
if (qs.has('token')) {
const verifyRes = await verify({ verifyToken: qs.get('token') })
if (verifyRes.authToken) {
setAuthToken(verifyRes.authToken)
} else if (verifyRes.emailSet) {
setEmailSet(true)
} else {
setError(verifyRes.verifyToken)
}
}
})()
}, [])
if (error) {
return <Error error='401' message={error} />
}
if (emailSet) {
return (
<div class='row u-center'>
<h3>The email change has been verified. You can now close this tab.</h3>
</div>
)
}
return <PendingToken authToken={authToken} />
}
Example #14
Source File: useHashLocation.js From v8-deopt-viewer with MIT License | 6 votes |
/**
* @returns {[string, (to: string) => string]}
*/
export function useHashLocation() {
const [loc, setLoc] = useState(currentLocation());
useEffect(() => {
// this function is called whenever the hash changes
const handler = () => setLoc(currentLocation());
// subscribe to hash changes
window.addEventListener("hashchange", handler);
return () => window.removeEventListener("hashchange", handler);
}, []);
// remember to wrap your function with `useCallback` hook
// a tiny but important optimization
const navigate = useCallback((to) => (window.location.hash = to), []);
return [loc, navigate];
}
Example #15
Source File: pwaPrompt.js From domicilioBoilerplate with GNU Affero General Public License v3.0 | 6 votes |
PWAPrompt = (props) => {
const [isVisible, setIsVisible] = useState(false);
function handleClosePopup() {
setIsVisible(false);
if (typeof window !== 'undefined') {
window.localStorage.setItem('pwaPrompt', 'true')
}
}
useEffect(() => {
setIsVisible(visibilityCheks())
}, [])
return visibilityCheks() && (
<Prompt
visible={isVisible}
closePopup={handleClosePopup}
{...props}
/>
);
}
Example #16
Source File: DeoptTables.js From v8-deopt-viewer with MIT License | 6 votes |
/**
* @param {boolean} selected
* @returns {import("preact").RefObject<HTMLDivElement>}
*/
function useScrollIntoView(selected) {
/** @type {import("preact").RefObject<HTMLDivElement>} */
const ref = useRef(null);
useEffect(() => {
if (selected) {
// TODO: Why doesn't the smooth behavior always work? It seems that only
// the first or last call to scrollIntoView with behavior smooth works?
ref.current.scrollIntoView({ block: "center" });
}
}, [selected]);
return selected ? ref : null;
}
Example #17
Source File: CodePanel.js From v8-deopt-viewer with MIT License | 6 votes |
/**
* @param {import('v8-deopt-parser').Entry} entry
* @param {boolean} shouldHighlight
*/
export function useHighlightEntry(entry, shouldHighlight) {
const { setSelectedEntry } = useAppDispatch();
useEffect(() => {
if (shouldHighlight) {
setSelectedEntry(entry);
}
}, [shouldHighlight]);
}
Example #18
Source File: timer.js From rctf with BSD 3-Clause "New" or "Revised" License | 5 votes |
Timer = withStyles({
card: {
background: '#222',
margin: 'auto'
},
section: {
display: 'inline'
},
content: {
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
columnGap: '20px',
margin: '20px 40px',
textAlign: 'center'
},
time: {
fontSize: '40px'
},
absolute: {
gridColumn: 'span 4',
fontSize: '15px',
color: '#bbb'
},
sub: {
gridColumn: 'span 4',
marginTop: '10px',
fontSize: '20px'
},
over: {
margin: '20px 40px',
fontSize: '20px',
textAlign: 'center'
}
}, ({ classes }) => {
const [time, setTime] = useState(Date.now())
useEffect(() => {
const intervalId = setInterval(() => setTime(Date.now()), 1000)
return () => clearInterval(intervalId)
}, [])
if (time > config.endTime) {
return (
<div class='row'>
<div class={`card ${classes.card}`}>
<div class={classes.over}>
The CTF is over.
</div>
</div>
</div>
)
}
const targetEnd = time > config.startTime
const targetTime = targetEnd ? config.endTime : config.startTime
const timeLeft = targetTime - time
const daysLeft = Math.floor(timeLeft / (1000 * 60 * 60 * 24))
const hoursLeft = Math.floor(timeLeft / (1000 * 60 * 60)) % 24
const minutesLeft = Math.floor(timeLeft / (1000 * 60)) % 60
const secondsLeft = Math.floor(timeLeft / 1000) % 60
return (
<div class='row'>
<div class={`card ${classes.card}`}>
<div class={classes.content}>
<span class={classes.time}>{daysLeft}</span>
<span class={classes.time}>{hoursLeft}</span>
<span class={classes.time}>{minutesLeft}</span>
<span class={classes.time}>{secondsLeft}</span>
<span>Days</span>
<span>Hours</span>
<span>Minutes</span>
<span>Seconds</span>
<span class={classes.sub}>until {config.ctfName} {targetEnd ? 'ends' : 'starts'}</span>
<span class={classes.absolute}>{formatAbsoluteTimeWithTz(targetTime)}</span>
</div>
</div>
</div>
)
})
Example #19
Source File: CodePanel.js From v8-deopt-viewer with MIT License | 5 votes |
/**
* @typedef CodePanelProps
* @property {import("../").FileV8DeoptInfoWithSources} fileDeoptInfo
* @property {number} fileId
* @property {import('./CodeSettings').CodeSettingsState} settings
* @param {CodePanelProps} props
*/
export function CodePanel({ fileDeoptInfo, fileId, settings }) {
if (fileDeoptInfo.srcError) {
return <CodeError srcError={fileDeoptInfo.srcError} />;
} else if (!fileDeoptInfo.src) {
return <CodeError srcError="No sources for the file were found." />;
}
const lang = determineLanguage(fileDeoptInfo.srcPath);
const state = useAppState();
const selectedLine = state.selectedPosition?.line;
/**
* @typedef {Map<string, import('../utils/deoptMarkers').Marker>} MarkerMap
* @type {[MarkerMap, import('preact/hooks').StateUpdater<MarkerMap>]}
*/
const [markers, setMarkers] = useState(null);
/** @type {import('preact').RefObject<HTMLElement>} */
const codeRef = useRef(null);
useLayoutEffect(() => {
// Saved the new markers so we can select them when CodePanelContext changes
const markers = addDeoptMarkers(codeRef.current, fileId, fileDeoptInfo);
setMarkers(new Map(markers.map((marker) => [marker.id, marker])));
}, [fileId, fileDeoptInfo]);
useEffect(() => {
if (state.prevSelectedEntry) {
markers
.get(getMarkerId(state.prevSelectedEntry))
?.classList.remove(active);
}
/** @type {ScrollIntoViewOptions} */
const scrollIntoViewOpts = { block: "center", behavior: "smooth" };
if (state.selectedEntry) {
const target = markers.get(getMarkerId(state.selectedEntry));
target.classList.add(active);
// TODO: Why doesn't the smooth behavior always work? It seems that only
// the first or last call to scrollIntoView with behavior smooth works?
target.scrollIntoView(scrollIntoViewOpts);
} else if (state.selectedPosition) {
const lineSelector = `.line-numbers-rows > span:nth-child(${state.selectedPosition.line})`;
document.querySelector(lineSelector)?.scrollIntoView(scrollIntoViewOpts);
}
// TODO: Figure out how to scroll line number into view when
// selectedPosition is set but selectedMarkerId is not
}, [state]);
return (
<div
class={[
codePanel,
(settings.showLowSevs && showLowSevsClass) || null,
].join(" ")}
>
<PrismCode
src={fileDeoptInfo.src}
lang={lang}
class={(!settings.hideLineNums && "line-numbers") || null}
ref={codeRef}
>
<LineNumbers selectedLine={selectedLine} contents={fileDeoptInfo.src} />
</PrismCode>
</div>
);
}
Example #20
Source File: index.js From rctf with BSD 3-Clause "New" or "Revised" License | 5 votes |
makeRedir = to => () => {
useEffect(() => route(to, true), [])
return null
}
Example #21
Source File: members-card.js From rctf with BSD 3-Clause "New" or "Revised" License | 5 votes |
MembersCard = withStyles({
form: {
'& button': {
display: 'block',
marginLeft: 'auto',
marginRight: '0',
marginTop: '10px'
}
}
}, ({ classes }) => {
const { toast } = useToast()
const [email, setEmail] = useState('')
const handleEmailChange = useCallback(e => setEmail(e.target.value), [])
const [buttonDisabled, setButtonDisabled] = useState(false)
const [members, setMembers] = useState([])
const handleSubmit = useCallback(e => {
e.preventDefault()
setButtonDisabled(true)
addMember({ email })
.then(({ error, data }) => {
setButtonDisabled(false)
if (error) {
toast({ body: error, type: 'error' })
} else {
toast({ body: 'Team member successfully added' })
setMembers(members => [...members, data])
setEmail('')
}
})
}, [email, toast])
useEffect(() => {
getMembers()
.then(data => setMembers(data))
}, [])
return (
<div class='card'>
<div class='content'>
<p>Team Information</p>
<p class='font-thin u-no-margin'>Please enter a separate email for each team member. This data is collected for informational purposes only. Ensure that this section is up to date in order to remain prize eligible.</p>
<div class='row u-center'>
<Form class={`col-12 ${classes.form}`} onSubmit={handleSubmit} disabled={buttonDisabled} buttonText='Add Member'>
<input
required
autocomplete='email'
autocorrect='off'
icon={<EnvelopeOpen />}
name='email'
placeholder='Email'
type='email'
value={email}
onChange={handleEmailChange}
/>
</Form>
{
members.length !== 0 &&
<div class='row'>
{
members.map(data => <MemberRow setMembers={setMembers} { ...data } />)
}
</div>
}
</div>
</div>
</div>
)
})
Example #22
Source File: challs.js From rctf with BSD 3-Clause "New" or "Revised" License | 5 votes |
Challenges = ({ classes }) => {
const [problems, setProblems] = useState([])
// newId is the id of the new problem. this allows us to reuse code for problem creation
// eslint-disable-next-line react-hooks/exhaustive-deps
const newId = useMemo(() => uuid(), [problems])
const completeProblems = problems.concat({
...SAMPLE_PROBLEM,
id: newId
})
useEffect(() => {
document.title = `Admin Challenges | ${config.ctfName}`
}, [])
useEffect(() => {
const action = async () => {
setProblems(await getChallenges())
}
action()
}, [])
const updateProblem = useCallback(({ problem }) => {
let nextProblems = completeProblems
// If we aren't creating new problem, remove sample problem first
if (problem.id !== newId) {
nextProblems = nextProblems.filter(p => p.id !== newId)
}
setProblems(nextProblems.map(p => {
// Perform partial update by merging properties
if (p.id === problem.id) {
return {
...p,
...problem
}
}
return p
}))
}, [newId, completeProblems])
return (
<div class={`row ${classes.row}`}>
<div class='col-9'>
{
completeProblems.map(problem => {
return (
<Problem update={updateProblem} key={problem.id} problem={problem} />
)
})
}
</div>
</div>
)
}
Example #23
Source File: page.jsx From paypal-installments with Apache License 2.0 | 5 votes |
function Page({ cspNonce, content } : PageProps) : mixed {
const { data, close } = useXProps();
const [ visible, setVisible ] = useState(false);
useEffect(() => {
const hasOptions = Boolean(data && data.options && data.options.length);
setVisible(hasOptions);
}, [ data ]);
return (
<Fragment>
<style nonce={ cspNonce }>
{`
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
font-family: Helvetica, sans-serif;
font-size: 14px;
}
body {
width: 100%;
overflow:auto;
}
`}
</style>
{
(visible)
? <Installments
data={ data }
cspNonce={ cspNonce }
close={ close }
content={ content } />
: null
}
</Fragment>
);
}
Example #24
Source File: hooks.js From paypal-installments with Apache License 2.0 | 5 votes |
export function useXProps<T>() : T {
const [ xprops, setXProps ] = useState(window.xprops);
useEffect(() => xprops.onProps(newProps => {
setXProps({ ...newProps });
}), []);
return { ...xprops };
}
Example #25
Source File: Form.jsx From todo-pwa with MIT License | 5 votes |
Form = ({ className = '', itemsAdd }) => {
const input = useRef(null);
const [value, setValue] = useState(
new URL(window.location).searchParams.get('title') || ''
);
useEffect(() => {
// The shareTargetAPI creates a get Request that looks like this:
// /preact/?title={title}&text={text}&url={url}
const params = new URL(window.location).searchParams;
const v = [
...(params.get('title') ? [params.get('title')] : []),
...(params.get('text') ? [params.get('text')] : []),
...(params.get('url') ? [params.get('url')] : []),
];
setValue(v.join(' - '));
}, []);
return (
<div className={className}>
<form
className="flex items-stretch"
autocomplete="off"
onSubmit={e => {
e.preventDefault();
if (value !== '') {
itemsAdd(value);
setValue('');
input.current.focus();
}
}}
>
<input
type="text"
name="title"
id="title"
className="appearance-none border rounded rounded-r-none w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
ref={input}
value={value}
onKeyUp={e => setValue(e.target.value)}
autocomplete="off"
/>
<button
type="submit"
className="font-bold rounded rounded-l-none text-white px-4 hover:bg-indigo-700 bg-indigo-800 text-center no-underline block focus:shadow-outline focus:outline-none"
>
Add
</button>
</form>
<ContactPicker value={value} setValue={setValue} className="w-full" />
</div>
);
}
Example #26
Source File: App.jsx From todo-pwa with MIT License | 5 votes |
App = () => {
const [items, setItems] = useState([]);
const [mounted, setMounted] = useState(false);
const itemsAdd = title =>
setItems([
{
title,
id: uuid(),
done: false,
},
...items,
]);
const itemsRemove = id => setItems(items.filter(item => id !== item.id));
const itemsSet = (id, done) =>
setItems(items.map(item => (item.id === id ? { ...item, done } : item)));
// Whenever the items Array changes, the new Values should be stored in idb
useEffect(() => mounted && idb.set('items', items), [items]);
// on mount, the items from the idb should be set
useEffect(() => {
setMounted(true);
idb.get('items').then(items => setItems(items || []));
}, []);
return (
<div className="flex flex-col justify-start">
<div className="bg-indigo-800 text-white w-full shadow-lg sticky top-0">
<Header className="w-11/12 max-w-lg mx-auto" />
</div>
<Form className="w-11/12 max-w-lg mx-auto mt-10" itemsAdd={itemsAdd} />
<List
className="w-11/12 max-w-lg mx-auto my-16"
items={items}
itemsRemove={itemsRemove}
itemsSet={itemsSet}
/>
<Footer className="m-auto w-11/12 max-w-2xl" />
<A2H />
</div>
);
}
Example #27
Source File: Item.js From vegemite with MIT License | 5 votes |
// ---
export default function (props) {
const { id, title, completed } = props;
const editor = useRef(null);
const [editing, setEditing] = useState(false);
useEffect(() => {
if (editing) {
editor.current.value = title;
editor.current.focus();
}
}, [editing]);
const classname = [
editing && 'editing',
completed && 'completed',
].filter(Boolean).join(' ');
function onToggle() {
todomvc.dispatch('todo:toggle', id);
}
function onDestroy() {
todomvc.dispatch('todo:del', id);
}
function onDblClick() {
setEditing(true);
}
function onblur(ev) {
let value = ev.target.value.trim();
if (value.length > 0) {
todomvc.dispatch('todo:put', { id, value });
}
ev.target.value = null;
setEditing(false);
}
function onkeydown(ev) {
if (ev.which === 27) {
ev.target.value = null;
setEditing(false);
} else if (ev.which === 13) {
onblur(ev);
}
}
return (
<li class={classname}>
<div class="view">
<input type="checkbox" class="toggle" checked={completed} onchange={onToggle} />
<label onDblClick={onDblClick}>{title}</label>
<button class="destroy" onclick={onDestroy} />
</div>
{ editing && <input ref={editor} class="edit" onblur={onblur} onkeydown={onkeydown} /> }
</li>
);
}
Example #28
Source File: Dropdown.js From duolingo-solution-viewer with MIT License | 5 votes |
Dropdown = forwardRef(
(
{
context = CONTEXT_CHALLENGE,
getOptionKey = ((option, index) => index),
renderOption = (option => <Item {...option} context={context} />),
options = [],
onSelect = noop,
onClose = noop,
},
ref
) => {
const wrapper = useRef();
const content = useRef();
const portalContainer = usePortalContainer();
const getElementClassNames = useStyles(CLASS_NAMES, STYLE_SHEETS, [ context ]);
// Positions the content at the right spot, and closes the dropdown on any scroll or resize event.
useEffect(() => {
if (wrapper.current && content.current) {
const { left: wrapperLeft, top: wrapperTop } = wrapper.current.getBoundingClientRect();
const itemsWidth = content.current.clientWidth;
const itemsBaseLeft = wrapperLeft - Math.ceil(itemsWidth / 2);
const itemsMinLeft = 10;
const itemsMaxLeft = document.body.clientWidth - itemsWidth - itemsMinLeft;
const itemsLeft = Math.max(itemsMinLeft, Math.min(itemsBaseLeft, itemsMaxLeft));
content.current.style.setProperty('top', `${wrapperTop}px`);
content.current.style.setProperty('left', `${itemsLeft}px`);
content.current.style.setProperty('visibility', 'visible', 'important');
const scrollableAncestors = getAncestorsWithScrollOverflow(wrapper.current);
window.addEventListener('resize', onClose);
scrollableAncestors.forEach(it.addEventListener('scroll', onClose));
return () => {
window.removeEventListener('resize', onClose);
scrollableAncestors.forEach(it.removeEventListener('scroll', onClose));
}
}
}, [ onClose, wrapper, content ]);
// Renders a single option.
const renderOptionItem = (option, index) => {
const key = getOptionKey(option, index);
const onClick = event => {
discardEvent(event);
onSelect(key);
};
return (
<div key={key} onClick={onClick} className={getElementClassNames(ITEM_WRAPPER)}>
{renderOption(option)}
</div>
);
};
return (
<div ref={wrapper} className={getElementClassNames(WRAPPER)}>
{createPortal(
<div ref={useMergeRefs([ ref, content ])} className={getElementClassNames(CONTENT)}>
<div className={getElementClassNames(ITEMS)}>
{options.map(renderOptionItem)}
</div>
</div>,
portalContainer
)}
{/* Keep the arrow within the DOM hierarchy so that it follows the content. */}
<div className={getElementClassNames(ARROW)}>
<div className={getElementClassNames(ARROW_ICON)} />
</div>
</div>
);
}
)
Example #29
Source File: CorrectedAnswer.js From duolingo-solution-viewer with MIT License | 5 votes |
CorrectedAnswer = ({ diffTokens = [], result = RESULT_CORRECT }) => {
const getElementClassNames = useStyles(CLASS_NAMES, STYLE_SHEETS, [ result ]);
// Renders a diff token.
const renderToken = useCallback((token, displayMode) => {
let elementKey = null;
if (token.added) {
if (DISPLAY_MODE_CORRECTED === displayMode) {
return null;
} else if (!token.ignorable) {
elementKey = ADDED_TOKEN;
}
} else if (token.removed) {
if (DISPLAY_MODE_ORIGINAL === displayMode) {
return null;
} else if (!token.ignorable) {
elementKey = REMOVED_TOKEN;
}
}
return (
<span className={getElementClassNames(elementKey)}>
{token.value}
</span>
);
}, [ getElementClassNames ]);
const [ originalAnswer, setOriginalAnswer ] = useState([]);
const [ correctedAnswer, setCorrectedAnswer ] = useState([]);
// Refreshes both versions of the answer when the diff tokens change.
useEffect(() => {
setOriginalAnswer(diffTokens.map(renderToken(_, DISPLAY_MODE_ORIGINAL)));
setCorrectedAnswer(diffTokens.map(renderToken(_, DISPLAY_MODE_CORRECTED)));
}, [ diffTokens, renderToken ]);
if (0 === diffTokens.length) {
return null;
}
return (
<IntlProvider scope="corrected_answer">
<h2 className={getElementClassNames(WRAPPER)}>
<Text id="title">Corrected answer:</Text>
<div className={getElementClassNames(VALUE)}>
{originalAnswer}
</div>
<div className={getElementClassNames(VALUE)}>
{correctedAnswer}
</div>
</h2>
</IntlProvider>
);
}