Skip to content

Commit

Permalink
Extra credit 01 for exercise 6
Browse files Browse the repository at this point in the history
  • Loading branch information
BorisPT committed Sep 12, 2022
1 parent 19fa5a3 commit b725bcc
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 34 deletions.
35 changes: 1 addition & 34 deletions src/exercise/06.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,30 +30,14 @@ function useToggle({
reducer = toggleReducer,
onChange,
on : controlledOn
// 🐨 add an `onChange` prop.
// 🐨 add an `on` option here
// 💰 you can alias it to `controlledOn` to avoid "variable shadowing."

} = {}) {
const {current: initialState} = React.useRef({on: initialOn})
const [state, dispatch] = React.useReducer(reducer, initialState)
// 🐨 determine whether on is controlled and assign that to `onIsControlled`
// 💰 `controlledOn != null`
const onIsControlled = controlledOn != null;

// 🐨 Replace the next line with assigning `on` to `controlledOn` if
// `onIsControlled`, otherwise, it should be `state.on`.
// const {on} = state
const on = onIsControlled ? controlledOn : state.on;

// We want to call `onChange` any time we need to make a state change, but we
// only want to call `dispatch` if `!onIsControlled` (otherwise we could get
// unnecessary renders).
// 🐨 To simplify things a bit, let's make a `dispatchWithOnChange` function
// right here. This will:
// 1. accept an action
// 2. if onIsControlled is false, call dispatch with that action
// 3. Then call `onChange` with our "suggested changes" and the action.

function dispatchWithOnChange(action) {
if (!onIsControlled){
dispatch(action);
Expand All @@ -62,28 +46,11 @@ function useToggle({

if (onChange)
{
// interessante : use all the properties of the state object and override with the "on" property.
onChange(toggleReducer({...state, on}, action), action);
}

}

// 🦉 "Suggested changes" refers to: the changes we would make if we were
// managing the state ourselves. This is similar to how a controlled <input />
// `onChange` callback works. When your handler is called, you get an event
// which has information about the value input that _would_ be set to if that
// state were managed internally.
// So how do we determine our suggested changes? What code do we have to
// calculate the changes based on the `action` we have here? That's right!
// The reducer! So if we pass it the current state and the action, then it
// should return these "suggested changes!"
//
// 💰 Sorry if Olivia the Owl is cryptic. Here's what you need to do for that onChange call:
// `onChange(reducer({...state, on}, action), action)`
// 💰 Also note that user's don't *have* to pass an `onChange` prop (it's not required)
// so keep that in mind when you call it! How could you avoid calling it if it's not passed?

// make these call `dispatchWithOnChange` instead
const toggle = () => dispatchWithOnChange({type: actionTypes.toggle})
const reset = () => dispatchWithOnChange({type: actionTypes.reset, initialState})

Expand Down
172 changes: 172 additions & 0 deletions src/exercise/06_original.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Control Props
// http://localhost:3000/isolated/exercise/06.js

import * as React from 'react'
import {Switch} from '../switch'

const callAll = (...fns) => (...args) => fns.forEach(fn => fn?.(...args))

const actionTypes = {
toggle: 'toggle',
reset: 'reset',
}

function toggleReducer(state, {type, initialState}) {
switch (type) {
case actionTypes.toggle: {
return {on: !state.on}
}
case actionTypes.reset: {
return initialState
}
default: {
throw new Error(`Unsupported type: ${type}`)
}
}
}

function useToggle({
initialOn = false,
reducer = toggleReducer,
onChange,
on : controlledOn
// 🐨 add an `onChange` prop.
// 🐨 add an `on` option here
// 💰 you can alias it to `controlledOn` to avoid "variable shadowing."
} = {}) {
const {current: initialState} = React.useRef({on: initialOn})
const [state, dispatch] = React.useReducer(reducer, initialState)
// 🐨 determine whether on is controlled and assign that to `onIsControlled`
// 💰 `controlledOn != null`
const onIsControlled = controlledOn != null;

// 🐨 Replace the next line with assigning `on` to `controlledOn` if
// `onIsControlled`, otherwise, it should be `state.on`.
// const {on} = state
const on = onIsControlled ? controlledOn : state.on;

// We want to call `onChange` any time we need to make a state change, but we
// only want to call `dispatch` if `!onIsControlled` (otherwise we could get
// unnecessary renders).
// 🐨 To simplify things a bit, let's make a `dispatchWithOnChange` function
// right here. This will:
// 1. accept an action
// 2. if onIsControlled is false, call dispatch with that action
// 3. Then call `onChange` with our "suggested changes" and the action.

function dispatchWithOnChange(action) {
if (!onIsControlled){
dispatch(action);
return;
}

if (onChange)
{
// interessante : use all the properties of the state object and override with the "on" property.
onChange(toggleReducer({...state, on}, action), action);
}

}

// 🦉 "Suggested changes" refers to: the changes we would make if we were
// managing the state ourselves. This is similar to how a controlled <input />
// `onChange` callback works. When your handler is called, you get an event
// which has information about the value input that _would_ be set to if that
// state were managed internally.
// So how do we determine our suggested changes? What code do we have to
// calculate the changes based on the `action` we have here? That's right!
// The reducer! So if we pass it the current state and the action, then it
// should return these "suggested changes!"
//
// 💰 Sorry if Olivia the Owl is cryptic. Here's what you need to do for that onChange call:
// `onChange(reducer({...state, on}, action), action)`
// 💰 Also note that user's don't *have* to pass an `onChange` prop (it's not required)
// so keep that in mind when you call it! How could you avoid calling it if it's not passed?

// make these call `dispatchWithOnChange` instead
const toggle = () => dispatchWithOnChange({type: actionTypes.toggle})
const reset = () => dispatchWithOnChange({type: actionTypes.reset, initialState})

function getTogglerProps({onClick, ...props} = {}) {
return {
'aria-pressed': on,
onClick: callAll(onClick, toggle),
...props,
}
}

function getResetterProps({onClick, ...props} = {}) {
return {
onClick: callAll(onClick, reset),
...props,
}
}

return {
on,
reset,
toggle,
getTogglerProps,
getResetterProps,
}
}

function Toggle({on: controlledOn, onChange}) {
const {on, getTogglerProps} = useToggle({on: controlledOn, onChange})
const props = getTogglerProps({on})
return <Switch {...props} />
}

function App() {
const [bothOn, setBothOn] = React.useState(false)
const [timesClicked, setTimesClicked] = React.useState(0)

function handleToggleChange(state, action) {
if (action.type === actionTypes.toggle && timesClicked > 4) {
return
}
setBothOn(state.on)
setTimesClicked(c => c + 1)
}

function handleResetClick() {
setBothOn(false)
setTimesClicked(0)
}

return (
<div>
<div>
<Toggle on={bothOn} onChange={handleToggleChange} />
<Toggle on={bothOn} onChange={handleToggleChange} />
</div>
{timesClicked > 4 ? (
<div data-testid="notice">
Whoa, you clicked too much!
<br />
</div>
) : (
<div data-testid="click-count">Click count: {timesClicked}</div>
)}
<button onClick={handleResetClick}>Reset</button>
<hr />
<div>
<div>Uncontrolled Toggle:</div>
<Toggle
onChange={(...args) =>
console.info('Uncontrolled Toggle onChange', ...args)
}
/>
</div>
</div>
)
}

export default App
// we're adding the Toggle export for tests
export {Toggle}

/*
eslint
no-unused-vars: "off",
*/

0 comments on commit b725bcc

Please sign in to comment.