The billing page has an input number module

Requirements:

  • Click the numeric keypad to display the corresponding number
  • Click Clear to clear the display area
  • Click OK to save the current record

Keyboard layout

1. Layout and style

<Wrapper>
    <div className="output">{output}</div>
    <div className="pad clearfix" onClick={onClickButtonWrapper}>
        <button >1</button>
        <button >2</button>
        <button >3</button>
        <button>delete</button>
        <button >4</button>
        <button >5</button>
        <button>6</button>
        <button >empty</button>
        <button >7</button>
        <button >8</button>
        <button >9</button>
        <button className="ok">ok</button>
        <button className="zero">0</button>
        <button className="dot">.</button>

    </div>
</Wrapper>
Copy the code

2.Wrapper

  • Includes 2 parts, Output and keyboard, using flex layout
  • Keyboard part, each button fixed height, accounting for 25%, float left
  • Note the use of selectors, such as&:nth-child(1)
import styled from "styled-components";

const  Wrapper = styled.section`
    display: flex;
    flex-direction: column;
    > .output{
        background: white;
        font-size:36px;
        line-height:72px;
        text-align: right;
        padding:0 16px;
        box-shadow:inset 0 -5px 5px -5px rgba(0.0.0.0.25),inset 0 5px 5px -5px rgba(0.0.0.0.25);
    }
    > .pad{>button{
        font-size:18px;
        float:left;
        width:25%;
        height:64px;
        border:none;
        &.ok{
        height: 128px;
        float: right;
        }
        &.zero{
          width:50%;
        }
        &:nth-child(1) {background: #f2f2f2;
        }
        &:nth-child(2),
        &:nth-child(5) {background: #e0e0e0;
        }
        
        &:nth-child(3),
        &:nth-child(6),
        &:nth-child(9) {background: #d3d3d3;
        }
        &:nth-child(4),
        &:nth-child(7),
        &:nth-child(10) {background: #c1c1c1;
        }
        &:nth-child(8),
        &:nth-child(11),
        &:nth-child(13) {background: #b8b8b8;
        }
        &:nth-child(12) {background: #a9a9a9;
        }
        &:nth-child(14) {background: #9a9a9a;
        }
 
      }
    }

`
export default Wrapper

Copy the code

Decide what button the user clicks and do different things

1. Specify the output type

  • Output is a string,
  • The record saved to localStorage is a number

2. Event delegation

OnClick ={onClickButtonWrapper}

  • OnClickButtonWrapper function:
    • Click OK to call the OK function
    • Click the number, clear, delete, ‘.’ callsetOutput.generateOutput
    const onClickButtonWrapper=(e:React.MouseEvent) = >{
        const text = (e.target as HTMLButtonElement).textContent;
        if(text === null) {return; }if(text === 'ok') {//todo
            if( props.onOk){
                props.onOk()
            }

            return ;
        }
        if('0123456789.'.split(' ').concat(['delete'.'empty']).indexOf(text)>=0){
            setOutput(generateOutput(text,output))
        }

    }
Copy the code
  • GenerateOutput:
    • Click number: Concatenate String
    • Click ‘.’: if there is already ‘.’, do not splice, do not splice
    • Click Delete: Returns an empty string if output length is 1, otherwise deletes one bit
    • Click Clear: Returns an empty string
type InputString = '0' |'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'. '|'delete'|'empty'
const generateOutput = (text:string,output='0') = >{
    switch(text){
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            if(output === '0') {return  text;
            }else{
                return output+text;
            }
        case '. ':
            if(output.indexOf('. ') > =0) {return output;
            }
            return output + '. ';
        case 'delete':
            if(output.length === 1) {return ' ';
            } else{
                return output.slice(0, -1) | |'0'
            }
        case 'empty':
            return ' ';
        default:
            return ' '}}export {generateOutput}

Copy the code
  • SetOutput: Process the output returned by generateOutput
    • If the length is greater than 16 bits: Only 16 bits are reserved
    • For internal output echo, use string, because string can hold “.”
    • Used to pass back to the money component, reserved into localStorage, usedparseFloat(newOutput)Is converted to a number
const setOutput= (output:string) = >{
    let newOutput
    if(output.length > 16){
        newOutput= output.slice(0.16)}else if(output.length === 0){
        newOutput = '0'
    }
    else{
        newOutput = output
    }
    _setOutput(newOutput) // The internal display is a string, to ensure that ".
    props.onChange(parseFloat(newOutput)) // Return number\ to the money component
    console.log(parseFloat(newOutput),newOutput)
   // props.onChange(newOutput)
}
Copy the code

Bugs encountered during development

The phenomenon of

Unable to input “.”, such as input 10.XX, it is found that “.” is not displayed in output after clicking

why

  1. In previous versions, the value returned to the Money component for record was the same as the value displayed in its output
  • Because parseFloat is used, parseFloat(10.) is converted to 10
  • The following code
    const output=props.value.toString() // Output is used to display the value using the number passed in by the Money component, which is converted to string
    const setOutput=(output:string) = >{
        let value
        if(output.length > 16){
            value= parseFloat(output.slice(0.16))}else if(output.length === 0){
            value = 0
        }
        else{
            value = parseFloat(output)
        }
        props.onChange(value)
    }
Copy the code
  • Logic:
    • The Money component (parent) passes the initialization to the Number component (child)Value props. The value
    • The number component converts this value tostringAnd assign a value tooutputUsed to display
    • The setOutput event is emitted when the user clicks different keys in number
    • setOutputThe output of string is converted to number, so the “.” is removed.
    • Pass the number to the Money component and trigger the correspondingThe onChnageEvent, insidesetStateUpdate the value, which triggers an update of the Number component
    • The number component displays outputs that have been stripped of “.”

Solution 1: Return a string to the Money component

  • Set value to string
  • But the amount defined in Money is number(note: When saving lcoalStorage, use the number type. And number can handle invalid numbers.
  • Methods impassability

The output displayed in number and the value given to the money component are two values

  • A string (setState directly in number), a number
  • The value passed by the Money component is used onlyuseStateInitialization, that is, only the first time
  • When the output of the number component is updated, setState internally, update the output, and then display the output. So this output is a string that says “.”
 const [output, _setOutput]= useState(props.value.toString()) // The initialized value is passed in from the Money component

    const setOutput= (output:string) = >{
        let newOutput
        if(output.length > 16){
            newOutput= output.slice(0.16)}else if(output.length === 0){
            newOutput = '0'
        }
        else{
            newOutput = output
        }
        _setOutput(newOutput) // The internal display is a string, to ensure that "."
        props.onChange(parseFloat(newOutput)) // The money component is returned with the number obtained by parseFloat
    }
Copy the code
  • If you changemoneyThe component’samount.NumberpageSectionThe display value won’t change, because we only have the originaluseState, using theprops.value. This is an uncontrolled component.
  • So you send me a value at the beginning, how do I change the value in the middle and I don’t tell you, I just send you the value at the end