Tags TAB

Target preview:

  • The TAB page canTo viewAll tags, andincreaseThe label
  • After clicking on a specific label, you canThe editorordeleteThe label

Encapsulation useTags TSX

  • Custom hooks, which put in all related tags operations
  • Start with useState tags and setTags
  • And then return out
import {useState} from "react";


const useTags = () = >{
    const [tags,setTags] = useState<{id:number; name:string}[]>([ {id:1.name:'clothes'},
        {id:2.name:'food'},
        {id:3.name:'live'},
        {id:4.name:'row'}]);return {
        tags:tags,
        setTags:setTags
    }
}
export default useTags;

Copy the code

One line of text is too long, redundant display…

  • If the number of words is too large and you want to use the extra words… What about the representation?
.oneLine{
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
Copy the code

Tags click jump

  • Routing order
  • exact={true}Do precise matching, don’t write fuzzy matching
       <Router>
            <Switch >
              <Route path="/tags" exact={true}>
                <Tags />
              </Route>
            <Route path="/tags/:id" exact={true}>
                <Tag/>
            </Route>
              <Route exact path="/money">
                <Money />
              </Route>
              <Route exact path="/statistics">
                <Statistics />
              </Route>
              <Redirect exact from='/' to='/money'></Redirect>
              <Route path="*">
                <NoMatch />
              </Route>
            </Switch>
          {/* A 
      
        looks through its children 
       
        s and renders the first one that matches the current URL. */
       
      }
        </Router>
Copy the code

Tag data structure

  1. Tag should be an object for later modification
tags:{id:number, name:string}
Copy the code
  1. Any other use of tag should be usedtag.idortag.name
  2. How do I add a tag to my tag?
    • New createdId TSX
    • Read the current maximum ID, then increment by 1 each time, and return
let id = parseInt(window.localStorage.getItem('idMax') | |'0');
const createId=() = >{
    id+=1;
    window.localStorage.setItem('idMax',id.toString())
    return id
}
export {createId}
Copy the code

Problems encountered:

  • Problem: Every time the Comfortable Tags page generates 5 new ids (the ID increases by 5), but the ID should remain the same every time we refresh
  • The start of the useTags
import {useState} from "react";
import {createId} from "lib/createId";


const useTags = () = >{
    const [tags,setTags] = useState<{id:number; name:string}[]>([ {id:createId(), name:'clothes'},
        {id:createId(), name:'food'},
        {id:createId(), name:'live'},
        {id:createId(), name:'row'}]);return {
        tags:tags,
        setTags:setTags
    }
}
export default useTags;
// tag : string=> tag: {id:number,name:string}

Copy the code
  • The reason: useTags is called with each refresh, and useTags initializes tags in useState each time, increasing the number of ids
  • Solution: Put the initialized value outside so that it is initialized only the first time
import {useState} from "react";
import {createId} from "lib/createId";
const defaultTags = [
    {id:createId(), name:'clothes'},
    {id:createId(), name:'food'},
    {id:createId(), name:'live'},
    {id:createId(), name:'row'}]const useTags = () = >{
    const [tags,setTags] = useState<{id:number; name:string}[]>(defaultTags);const findTag = (id:number) = >tags.filter(tag= >tag.id === id)[0]
    return {
        tags:tags,
        setTags:setTags,
        findTag:findTag
    }
}
export default useTags;
// tag : string=> tag: {id:number,name:string}

Copy the code

FindTag: Tags page Clicking on a tag leads to the corresponding tag

1. Read the ID from the route

 let {id:idString} = useParams<Params>() // Route id, rename to idString
Copy the code

2. Read the tag based on the tagId

  • Note the data type conversion
    const {findTag,updateTag,deleteTag} = useTags()

    const tag = findTag(parseInt(idString)) // Find a tag based on idString. Tag is an object, including id and name
Copy the code
  • FindTag is also packaged into useTags hooks
 const findTag = (id:number) =>tags.filter(tag=>tag.id === id)[0]
Copy the code

Encapsulate button.tsX components

Styled – Component encapsulates the button style as a component for easy invocation

import styled from "styled-components";

const Button=styled.button` font-size:18px; border:none; padding:8px 12px; border-radius:4px; background: #FF6F00; color:white; `
export {Button}

Copy the code
  • Direct introduction when used

Encapsulating input components

  • Ts inheritance
    • Props inherits the attributes of InputHTMLAttributes plus its own attributes
type Props={
    label:string;
    //ref:any;
} & React.InputHTMLAttributes<HTMLInputElement>;
Copy the code
import React from "react";
import styled from "styled-components";
import Icon from "./Icon";

const Label = styled.label` display: flex; align-items:center; font-weight: bold; > span{ margin-right:16px; white-space:nowrap; } > input{ display:block; width: 100%; height:44px; border:none; background: none; } `
type Props={
    label:string;
    //ref:any;
} & React.InputHTMLAttributes<HTMLInputElement>; / / inheritance

const Input:React.FC<Props> =(props ) = >{
    const{label, children,... rest} = props;// Destruct attributes
    return (
        <Label>
            <span>{props.label}</span>{/ *<input type={props.type} placeholder={props.placeholder}*/}{/ * / /ref={refInput}*/}{/ *defaultValue={props.defaultValue}*/}{/ *onBlur={props.onBlur}*/}{/ * / >* /}<input {. rest} / >
        </Label>)}export  default Input

Copy the code

The tag page

1. The Topbar layout

  • Flex layout, divided into 3 columns, icon on the right is not set to name
<Topbar>
    <Icon name="left" className='frank' onClick={onClickBack}/>
    <span>Edit the label</span>
    <Icon/>
</Topbar>
Copy the code
const Topbar = styled.header` display:flex; justify-content: space-between; align-items: center; line-height:20px; padding:14px; background: white; `
Copy the code

2. Input section

  • Use the input that was encapsulated earlier
   const tagContent= (tag:{id:number,name:string}) = > (<div>
        <InputWrapper>
            <Input label="Label name" type="text" placeholder="Label name"
                   value={tag.name}
            ></Input>
        </InputWrapper>
    </div>
    )
Copy the code
  • throughInputWrapperforinputAdd the style

3. Modify the input value on the edit page

  • Add onChange event
  <Input label="Label name" type="text" placeholder="Label name"
           value={tag.name}
           onChange={(e) = >{
           tag.name = e.target.value;
           console.log(tag.name)
           // updateTag(tag.id,{name:e.target.value})
           }}
></Input>
Copy the code
  • There is no update on the UI, but the console has printed the latest value. Why?
    • Because controlled components do not automatically setState, setState is required to update the UI
    • Where do I setState? You can write an updateTag, in useTags hooks
// Set an object (unchanged tag+ new tag) by iterating through tags to find an id equal to the current id passed in.
    const updateTag =(id:number,obj:{name:string}) = >{
        setTags(tags.map(tag= >tag.id === id? {id,name:obj.name} :tag))
    };
Copy the code

Remove the tag

The idea is to pass in a tagID and delete it

1. Write a method in hooks

    const deleteTag=(id:number) = >{
        setTags(tags.filter(tag= >tag.id ! == id)) }Copy the code
  • use
<Button onClick={()=> deleteTag(tag.id)}>Copy the code

2. Do not display the tag after deletion

  • If the tag exists, display, otherwise display does not exist
<Layout>
    <Topbar>
        <Icon name="left" className='frank' onClick={onClickBack}/>
        <span>Edit the label</span>
        <Icon/>
    </Topbar>
    {tag ?  tagContent(tag) : <Center>The tag does not exist</Center>}
</Layout>
Copy the code
  • tagContent
const tagContent= (tag:{id:number,name:string}) = > (<div>
        <InputWrapper>
            <Input label="Label name" type="text" placeholder="Label name"
                   value={tag.name}
                   onChange={(e)= >{
                       updateTag(tag.id,{name:e.target.value})
                   }}
            ></Input>
        </InputWrapper>
        <Center>
            <Space/>
            <Space/>
            <Button onClick={()= >DeleteTag (tag.id)}> Delete the tag</Button>
            <Space/>
            <Space/>
        </Center>
    </div>
)
Copy the code

Backward function

Click Icon to return to the Tags page

1. The demand

  <Icon name="left" className='frank' onClick={onClickBack}/>
Copy the code
    const history = useHistory() // React encapsulates the API
    const onClickBack=() = >{
        history.goBack()
    }
Copy the code

2. Icon. TSX components need to be upgraded to supportSVGAttributesAll attributes of

import React from "react"; import CS from 'classnames'; let importAll = (requireContext: __WebpackModuleApi.RequireContext) => requireContext.keys().forEach(requireContext); try {importAll(require.context('icons', true, /\.svg$/)); } catch (error) {console.log(error); } // Props = {name? :string } & React.SVGAttributes<SVGElement> const Icon =(props:Props)=>{ const {name,children,className,... rest} = props; return ( <svg className={CS('icon',className)} {... rest}> {props.name && <use xlinkHref={'#'+props.name}></use>} </svg> ) }; export default IconCopy the code

3. Problem: Multiple classNames overwrite

  • if. restThere’s a className in there, and the controller will override itclassName="icon"You need to display all the classnames and merge the classes
    • Using a libraryyarn add classnames
    • Ts support:yarn add --dev @types/classnames
<svg className={CS('icon',className)} {... rest}>Copy the code

4. When using hash mode, there are no network requests, just state changes