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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
// ---

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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
/**
 * @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 vote down vote up
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 vote down vote up
/**
 * @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 vote down vote up
/**
 * @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 vote down vote up
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 vote down vote up
/**
 * @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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
// ---

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 vote down vote up
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 vote down vote up
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>
  );
}