Fragments, Portals & Refs

김민경·2023년 2월 2일
0

FE(WEB) - React

목록 보기
8/13

Fragments

Limitations of JSX

  • we need to have only one root element per one component
    (can't store more than one "root" JSX element in a variable)
    (should return only one React.createElement)

=> how about wrapping with an array? ([])
... it can trigger a "key" warning
... then... how about providing a unique key for each element?
... it might be cumbersom

=> we can solve this problem by wrapping with div element
... we can end-up with div soup (in a final HTML page)

... it results in unnecessary div elements(no semantic meaning or structure to the page)

Solutions

  • make a Wrapper component
const Wrapper = props => {
	return props.children;
};

export default Wrapper;

// can use <Wrapper></Wrapper> instead of <div></div>
  • there is a React provided Wrapper component
    which is <React.Fragment><React.Fragment /> or <></>
    (<></> depends on project-setup
    but <React.Fragment><React.Fragment /> always work)

Portals

We use portals to get a cleaner DOM with Portals
Like by the name literally Portal,
it moves rendered HTML content to other place in the DOM structure

  • even though the modal should be an overlay over all the other components, it is nested inside other components

  • let's try refactoring the code like the code above

index.html

// add a code
// can also manage each components seperately 
<div id="backdrop-root"></div> // all kids of backdrops
<div id="overlay-root"></div> // all kinds of overlays: modal, side-drawbars etc...
<div id="root"></div>

ErrorModal.js

// import ReactDOM
import ReactDOM from 'react-dom'

const Backdrop = props => {
	return (
    // Code related to backdrops
    <div className = {classes.backdrop} onClick={props.onConfirm} />
    )
}

const ModalOverlay = props => {
	return (
    // Code related to modal
    <Card className={classes.modal}>
      <header className={classes.header}>
        <h2>{props.title}</h2>
      </header>
      <div className={classes.content}>
        <p>{props.message}</p>
      </div>
      <footer className={classes.actions}>
        <Button onClick={props.onConfirm}>Okay</Button>
      </footer>
    </Card>
    )
}

const ErrorModal = (props) => {
	return (
    	<React.Fragment>
        	⭐{ReactDOM.createPortal(<BackDrop onConfirm = {props.onConfirm})/>
            , document.getElementById('backdrop-root')}
            ⭐{ReactDOM.createPortal(
        <ModalOverlay
          title={props.title}
          message={props.message}
          onConfirm={props.onConfirm}
        />,
        document.getElementById('overlay-root')
      )}
        </React.Fragment>
    );

Refs

ref(reference)

Thinking about input tags in form,
we used useState() to manage the input contents(two-way binding)
But since we actually neet the content only when we submit the form
, this way seems a bit unnecessary

In this situation, we can use useRef

We also use useRef in a situation where
we only want to read a value

controlled component vs un-controlled component

controlled component
: when the internal state is managed by react
(useState)

un-controlled component
: when the internal state is not managed by react
(useRef)

// import useRef
import React, { useState, useRef } from 'react';

const AddUser = (props) => {
	const nameInputRef = useRef();
	const ageInputRef = useRef();
    // if checking it with console.log(),
    // it returns an object with 'current' property
    // in the 'current' property,
    // it containts the actual DOM node.
    
    const addUserHandler = (event) => {
    	event.preventDefault();
        // can read the input value by .current.value
    	const enteredName = nameInputRef.current.value;
    	const enteredUserAge = ageInputRef.current.value;
    
    	props.onAddUser(enteredName, enteredUserAge);
        
        // resetting an input value
    	nameInputRef.current.value = '';
    	ageInputRef.current.value = '';
  };
  
   return (
    <Wrapper>
      <Card className={classes.input}>
        <form onSubmit={addUserHandler}>
          <label htmlFor="username">Username</label>
          <input id="username" type="text" ref={nameInputRef} />
          <label htmlFor="age">Age (Years)</label>
          <input id="age" type="number" ref={ageInputRef} />
          <Button type="submit">Add User</Button>
        </form>
      </Card>
    </Wrapper>
  );
};

export default AddUser;

useImperativeHandle, React.forwardRef()

  • hook that interact with the input components imperatively
    (not by parsing some state to it that then changes something in the component but by calling a function inside of a component)
    (shouldn't do often because it is not a typical pattern)

  • can expose functionalities from a React component to its parent component
    to then use your component in the parent component through refs and trigger certain functionalities

  • can be used on particular behavior such as focusing or scrolling (a situation where we need to approach a child component's DOM event)

  • Ref 전달하기는 일부 컴포넌트가 수신한 ref를 받아 조금 더 아래로 전달
    (즉, “전송”)할 수 있는 옵트인 기능
    입니

=> want to focus the first invalid input found

Input.js

import React, {useRef, useImperativeHandle} from 'react'
 
const Input = ✨React.forwardRef((props, ✨ref) => {
    
    // 2️⃣ ref를 전달받음
    
	⭐const inputRef = useRef();
    
    // a rare case to use
    ✨const activate = () => {
    	inputRef.current.focus();
    }
    
    ✨useImperativeHandle(ref, () => {
    // 3️⃣ can use focus(activate) function in the parent component
    	return {
        	focus : activate
        }	
    });
    
    return (
    	 <input
        ⭐ref={inputRef}
        />
    )
});

Login.js

const Login = () => {

	const submitHandler = (event) => {
    	event.preventDefault();
        if (formIsValid) {
        	authCtx.onLogin(emailState,value, passwordState.value);
        } else if (!emailIsValid) {
        	✨3️⃣ emailInputRef.current.focus();
        } else {
        	✨3️⃣ passwordInputRef.current.focus();
        }
    }

	⭐const emailInputRef = useRef();
    ⭐const passwordInputRef = useRef();
    
    return (
    	// 1️⃣ Input component에 ref를 전달함
    	<Input 
        	⭐ref = {emailInputRef}
        />
        <Input 
        	⭐ref = {passwordInputRef}
        />
    )
}
profile
🏛️❄💻😻🏠

0개의 댓글