React, Redux, TypeScript - rlip/java GitHub Wiki

React

Warunki w return

<div className={`form-control ${isValid ? 'invalid' : ''}`}

Formularze

Można tez użyć formik

const AddUser = props => {
   const addUserHandler = event => {
      event.preventDefault(); //żeby nie przeładowywać strony
   }
}
return (
   <from onSubmit={addUserHandler}>
   ...
);

Niżej prosta wersja, można też zrobić osobny komponent input i tam ogarniać wszystkie staty, ale można tez zrobić hooka - wtedy łatwiej ogarniać czy cały form jest valid

///Bez ref, trochę lepsze, szczególnie jak chcemy zrobic reser, albo walidację co klawisz
const SimpleInput = props => {
    const [enteredName, setEnteredName] = useState('');
// Można jescze dodać walidację: Dodać state czy input jest poprawny i zainicjalizować błędnie - żeby się bład
// nie syświetlał zanim user dotknie formularz, albo dodac 3 state czy input jest dotknięty i sprawdzać wartości obu state

    const [enteredNameTouched, setEnterednameTouched] = useState(false);
    //ponieważ jak się zmieni state to się ten komponent zrewaliduje, to niżej się
    //też ustawi i nie trzeba tego zawsze sprawdzać
    const enteredNameIsValid = enteredName.trim() != '';
    const nameInputIsIsInvalid = !enteredNameIsValid && enteredNameTouched;

    const formIsValid = enteredNameIsValid && ewentualneInneInputy;


    const nameInputChangeHandler = event => {
        setEnteredName(event.target.value);
    }    
    const nameInputBlurHandler = event => {
       enteredNameTouched(true);
    }
    const formSubmissionHandler = event => {
        event.preventDefault();
        setEnterednameTouched(true)
        if(!enteredNameIsValid) {
            return;
        }
        //...
        setEnterednameTouched(false)
        setEnteredName('');
    }
    
    return (
        <form onSubmit={formSubmissionHandler}>
            <div className='form-control'>
                <label htmlFor='name'>Your name</label>
                <input type='text' id='name' onBlur={nameInputBlurHandler} onChange={nameInputChangeHandler} value={enteredValue}/>
                {nameInputIsInvalid && (<p className='error-text'>Name must not be empty</p>)}
            </div>
            <div className="formActions">
                <button>Submit</button>
            </div>
        </form>
    )
}
/// z ref - dobre, jeśli chcemy value tylko przy submit
const nameInputRef = useRef();
const formSubmissionHandler = event => {
        event.preventDefault();
        console.log(nameInputRef.current.value);
}
...
<input ref={nameInputRef}

form-hook

//np ueeInput
const useinput = (validateValue) => {
    const [enteredValue, setEnteredValue] = useState('');
    const [isTouched, setTouched] = useState(false);
    
    const valueIsValid = validateValue(enteredValue);
    const hasError = !valueIsValid && isTouched;
    
    const valueChangeHandler = event => {
        setEnteredValue(event.target.value);
    }  
    const inputBlurHandler = event => {
       setTouched(true);
    }
    const reset = () => {
        setEnteredValue('');
        setTouched(false);
    }
    
    return {
        value: enteredValue,
        hasError, //to co hasEroor: hasError
        isValid: valueIsValid,
        valueChangeHandler,
        inputBlurHandler,
        reset
    }
};
export default useInput;
///i w tym wyżej można użyć tego hooka, można też użyć alasów jak niżej
const {
        value: enteredName,
        isValid: enteredNameIsValid,
        hasError: nameInputHasError,
        valueChangeHandler: nameChangeHandler,
        inputBlurHandler: nameBlurHandler,
        reset: resetNameInput
} = useInput(value => value.trim() !== '');

const formSubmissionHandler = event => {
    event.preventDefault();
    if(!enteredNameIsValid) {
        return;
    }
    //...
    resetNameInput();
}
//...
return (
    <form onSubmit={formSubmissionHandler}>
        <div className='form-control'>
            <label htmlFor='name'>Your name</label>
            <input type='text' id='name' onBlur={nameBlurHandler} onChange={nameChangeHandler} value={enteredValue}/>
            {nameInputHasError && (<p className='error-text'>Name must not be empty</p>)}
        </div>

W useReducer jest połączenie isTouched ze value inputa

CSS

Klasyczne klasy CSS i zewnętrzne arkusze:

import './App.css';
<div className="global-class"/>

Style inline-owe:

	<div style={{marginTop: '1em'}}>

styl jako część komponentu

import styled from 'styled-components';
const StyledDiv = styled.div`margin-top: 1em;`;
<StyledDiv/>

Moduły

Zapewniają unikalność nazw per moduł

import styles from './Button.module.css'

<button type={props.type} className={stypes.button} onClick={props.onClick}>
{props.children}
</button>
----
// tak styles['form-control'] zamiast po kropce jak z myślnikiem
<div className={`${styles['form-control']} ${isValid && styles.invalid}`}> 
...
</div>

Fragment

to co <>

return (
    <React.Fragment>
        <h2>Hi there!</h2>
		<h2>This does not work</h2>
	</React.Fragment>
);
// to samo co, ale zawsze działa
return (
    <>
        <h2>Hi there!</h2>
		<h2>This does not work</h2>
	</>
);

Portals

Umożliwiają wyświetlanie w innej części drzewa html

//do index.html dodać
<div id="backdrop-root"></div>
<div id="modal-root"></div>
---
import ReactDOM from React

const Backdrop = props => 
    <div clasName={classes.backdrop} onClick={props.onConfirm} />
    
const ModalOverlay = props => {
    ...
}    

return (
    <>
       {ReactDOM.createPortal(
               <Backdrop onConfirm={props.onConfirm} />,
               dockumen.getElementById('backdrop-root')
           )
        },
       {ReactDOM.createPortal(
               <ModalOverlay title={props.title} 
               message={props.message} 
               onConfrm={props.onConfirm}/>,
               dockumen.getElementById('modal-root')
           )
        }
    </>
)

Context

Stosujemy jak chcemy przekazać coś pomiędzy oddalonymi od siebie komponentami. Można też w osobnym folderze np. store i nazwy np z myślnikami, żeby nie było jak w komponentach. Można zarówno zapisywać zmienne jak i funkcje. Nie jest zoptymalizowany do częstych zmian, jak ma być dużo zmian to lepiej użyć reduxa.

//store/auth-conext.js
const AuthContext = React.createContext({ //to nie musi być obiekt, ale chyba najlepiej
    isLogin: false,  //wartości domyślne
    onLogout: () => {},
    onLogin: (email, password) => {},

});
export default AuthContext;
////////////////
// tu żeby dobrze wyglądało to tu można trzymać wszystko, oraz zrobić tak:
export const AuthContextProvider = props => {
    const [isLoggedIn, setIsLogedIn] = useState(false);
    useEffect(() => {
       const storedUserLoggedInformation = localStorage.getItem('isLoggedIn');
       if (storedUserLoggedInformation == '1') {
         setIsLogedIn(1);
        }
    }, [])

    const logoutHandler = () => {
        setIsLoggedIn(false)
    } 
    const loginHandler = () => {
        setIsLoggedIn(true)
    } 
    return <AuthContext.Provider>{props.children}</AuthContext.Provider>;
}
// i teraz mona opakować apkę w AuthContextProvider 
<AuthContextProvider>
    <App />
</AuthContextProvider>

------------------------- opakowanie
//trzeba zapakować, tam gdzie chcemy go użyć
const isLoggedIn = false;

return (
     <> //albo też bez tego
          <AuthContext.Provider value={{
               isLoggedIn: isLoggedIn,
               onLogout: logoutHandler
          }}>
              ...
          </AuthContext.Provider>
     </>
);
------------------------- użycie
//bez hooka
return (
     <>
         <AuthContxt.Consumer>
         {ctx => {
             return (
                 <>
                    {ctx.isLoggedIn && ( <li><a>Users</a></li>)}
                 </>
             );
         }
         </AuthContxt.Consumer>
     </>
);
// z hookiem - lepiej
const ctx = useContext(AuthContext);
return (
     <>
         {ctx.isLoggedIn && ( <li><a>Users</a></li>)}

     </>
);

Hooks

Powinny być na górze komponentu. Tylko w komponencie funkcyjnym, lub custom react hooks. Trzeba użyć na górze i bez żadnych ifów.

useState

React zapisuje dane w kolejności, więc nie można dać warunków przy wywołaniu useState

this.state = {foo: 1};
+
this.setState({foo: 2});

zmienia się na:

const [foo, setFoo] = useState(1);
+
setFoo(2);

setUserInput((prevState) => {  //tak odnosimy sie do poprzedniego state
   return { ...prevState, enteredTitle: event.target.value };
});

useEffect

W nim ma być wszystko co nie ma się wykonywać zawsze jak - pobieranie z api, można zrobić sprawdzanie walidacji. Jako dependencja ma być wszystko od czego zależy, rówwnież funkcje (z wyjątkiem reactowych) chyba że jest waży powód żeby tego nie robić

componentDidMount() { doSth(); }
+
componentDidUpdate(prev) {
    if (prev.sth !== this.props.sth) {
        doSth();
    }
}

zmienia się na:
//wykonaj jeśli coś z props.sth się zmieniło (płytkie porównanie)
useEffect(() => doSth(), [props.sth]);
useEffect(() => {
   const id = setTimeout(() => 
       jakas funkcja
	   , 500);
	   
	   return () => {
	      clearTimeout(id) // to wykonuje się zawsze przed kolejnym (nie pierwsym)
		  //wywołaniem useEffect
	   }
   }, [dependencje])
}
    useEffect( () => {
         loadData();
        }, [props.orderId]
    )
//to co:
    componentDidMount() {
        this.loadData()
    }
    componentDidUpdate(prevProps: Props) {
        if (prevProps.orderId !== this.props.orderId) {
            this.loadData()
        }
    }
--------------------------- 
const { isValid: emailIsValid } = emailState; //z emilState ezźmnie isValid i zrobi zmienną emailIsValid
const { isValid: passwordIsValid } = passwordState;

useEffect(() => {
     const identifier = setTimeout(() => {
        setFormIsValid(emailIsValid && passwordIsValid);
    , 500);
    return () => {
        clearTimeout(identifier);
    };
  }, [emailIsValid, passwordIsValid]);
const [table, setTable] = useState<Table>()

useEffect ( () => {
      const fetchData = async () => {
             const table = await getTableById(tableId)
             setTable(table)
      }, []) // z [] - wykonaj się tylko raz, bez - wykonaj się zawsze
})
--------
useEffect ( () => {
       return () => {
        // to sie wykona na unmount
         console.log(tableId)
       }
}, [tableId]

useReducer

Jak chcemy zmienić state bazując na wartości innego state

const [state, dispatchFn] = useReducer(reducerFn, initialState, initFn);
const emailRecuder = (state, action) => { // może być poza komponentm
    //state - poprzednia state
    if (action.type === 'USER_INPUT') {
         return { value: action.val, isValid: action.val.includes('@')};
    }
    if (action.type === 'USER_INPUT') {
         return { value: state.value, isValid: state.value.includes('@')};
    }
    return { value: '', isValid: false };
}
...
const [emailState, dispatchEmail] = useReducer(emailReducer, {
     value: '',
     isValid: null
});

...
const emailChangeHandler = event => { //onChange
   dispatchEmail({type: 'USER_IMPUT', val: event.target.value});
}

const validateEmailHandler = () => { //onBlur
   dispatchEmail({type: 'IMPUT_BLUR'});
}

Dla inputu, nie trzeba, ale można połączyć toched z wartością

const initialInputState = {
    value: '',
    isTouched: false
}
const inputStateReducer = (state, action) => {
    if (action.type === 'INPUT') {
        return { value: action.value, isTouched: state.isTouched}
    }
    if (action.type === 'BLUR') {
        return { value: state.value, isTouched: true}
    }
    if (action.type === 'RESET') {
        return { value: '', isTouched: false}
    }
    return initialInputState;
}
//...
const [inputState, dispatch] = useReducer(inputStateReducer, initialInputState);
// i potem możemy użyć inputState.value i inputState.isTouched
// a także zmieniać isTouched albo value
const valueChangeHandler = event => {
        dispatch({type: 'INPUT', value: event.target.value})
}  
dispatch({type: 'BLUR'});
dispatch({type: 'RESET'});

useMemo

Mamy jakieś drogie obliczenia, które chcemy żeby nie były wykonywane częściej niż trzeba. Również można tam dać np. tworzenie tablicy, żeby przy rewalidacji nie była tworzona nowa.

// result jest wyliczany tylko
// gdy zmieni się param
const result = useMemo (
    () => expensiveComputation(param),
    [param]
)
-----
const sortedList = museMemo( () => {
    return items.sort((a,b) => a - b)
}, [items]);
-----
const listItems = useMemo(() => [5, 2, 3, 1], [])

useCallback

Jeżeli używamy React.Memo to komponent jest odświeżany tylko jak się zmienią propertisy, ale funkcje i tablice zawsze się zmienią, jak są przekazywane a parent będzie zrewalidowany. Chyba, że użyjemy callback, który robi nową instancja funkcji, tylko gdy zmieni się parametr, w tym przypadku p2

const callback = useCallback(
    (p1) => doSomething(p1, p2),
    [p2]
)

useRef

Można używać jeśli tylko chcemy czytać elementy. Raczej nie jak mamy zmieniać - wtedy lepiej state.

   const nameInputRef = useRef()
   ...
   const nameValue = nameInputRef.currentValue
   ...
   return (
       ...
       <input ref={nameInputRef}
       );
   )
-------------------------------------------
const freeValue = useRef(2)

function useScrollOnUpdate<T extends HtmlElement>() {
  const ref = useRef<T>(null)
  useEffect(() => {
      ref.current?.scrollIntoView({behavior: 'smoth'})
  })
  return topRef;
}
----------
const topRef = useScrollOnUpdate<>HTMLElement>()
return (
   <h2 ref={topRef} />
)
-------------------------------------------
//wyniesienie funkcjonalności lub wartości parenta to rodzica
const emailInputRef = useRef();

const submitHandler = event => {
    emailInputRef.current.focus();
}

...
<Input ref={emailInputRef}
----
// Z tym React.forwardRef robimy jak chcemy obsłużyć ref w customowym komponencie
// jako 2 argument forwardRef dostaniemy ref 
const Input = React.forwardRef((props, ref) => {
    const inputRef = useRef();
    const activate = () => {
        inputRef.current.focus();
    };
    useImpertativeHandle(ref, () =>
        return { //zwracamy co ma być dostępne z zewnątrz
            focus: activate
       };
    );
);

Własny Hook

Pozwala na reużycie logiki w komponentach. Można w nim używać innych hooków.

np. /hooks.use-cunter,js

const useCounter = (forwards = true) => {
    const [counter, setCounter] = useState(0);
    useEffect(() => {
        const nteerval = setInterval(() => {
            if (forwards) {
                setCounter(prevCounter => prevCounter + 1);
            } else {
                setCounter(prevCounter => prevCounter - 1);
            }
        }, 1000);
         return () => clearInterval(interval);
    }, [forwards]);
    return counter; //można zwrócić cokolwiek
}
export default useCounter;
-----------
//true nie trzeba bo jest domyślne
const counter = useCounter(false);

Trzeba uważać na wszystkie zmienne przekazywane do hooka i dawane do dependencji useCallback w hooku, możliwe że trzeba je dawać też dać w useCallback żeby nie było nieskończonych pętli

/hooks/use-http.js

const useHttp = () => {
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState(null)
    //Ponieważ używamy niżej state, trzeba zrobić useCallback, żeby
    //nie było nieskończonej pętli przy daniu funkcji sendRequest do useEffect
    // z dependencją sendRequest
    const sendRequest = useCallback(async(requestConfig, applyData) =>  {
        setIsLoading(true);
        setError(null);
        try {
            const response = await fetch(requestConfig.url, {
                method: requestConfig.method ? requestConfig.method : 'GET',
                headers: requestConfig.headers ? requestConfig.headers : {},
                body: requestConfig.body ? JSON.stringify(requestConfig.body) : null
            });
            if(!response.ok) {
                throw new Error('Request failed!');
            }
            const data = await response.json();
            applyData(data);
            } catch(err) {
                setError(err.message || 'Somthing went wrong!');
            }
            setIsLoading(false);
    ), []};
    return { //skrótowa wersja zapisu isLoading: isLoading, itd
        isLoading,
        error,
        sendRequest
    };
}

-----------
    //niżej przykład aliasowania sendRequest na fetchTask
    const {isLoading, error, sendRequest: fetchTask} = useHttp();

    useEffect(() = {
        const transformTasks = taskObj => {
                //...
                setTask(convertedTaskOb);
        };
        fetchTask({url: 'https://some-url.pl'}, transformTasks);
    }, [fetchTask])
----------
    const {isLoading, error, sendRequest: sendTaskRequest} = useHttp();
    ...
    const createTask = taskData => {
        ...
    }
    
    sendTaskRequest ({
        url: 'http://someUrl',
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: { text: taskText}
    }, createTask);

Szablon komponentu

Można używać zamiast diva

import React from 'react';

import './Card.css';

const Card = (props) => {
  const classes = 'card ' + props.className;

  return <div className={classes}>{props.children}</div>;
};

export default Card;

///////////////
.card {
  border-radius: 12px;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.25);
}

Pure components

najlepiej do komonentów wizualnych - tych które nie mają stanu React.PureComponent - automatycznie dodaje shouldComponentUpdate, która porównuje wszystkie propsy i nie zmieni się, jak się propsy nie zmienią. React.memo(component) - to pure component dla componentów funkcyjnych

Memo

React będzie sprawdzał czy się zmieniały propsy i tylko jeśli się zmieniły to będzie uruchomiał (revalidate) komponent i jego dzieci. Ale w zamian za to będzie musiał przechowywać wartość propsów i ją porównywać. Opłaca się jak komponent ma dużo dzieci.

export default React.memo(SomeComponent);

Cykl życia

  • componentDidMount - wywoływany raz po pierwszym wyrenderowaniu, tu można np. przychodzące dane z api przypisać do state
  • shouldComponentUpdate - jeśli zrwóci true, to przechodzi dalej
  • componentDidUpdate -
  • componentWillUnmount - usinięcie z dom
  • componentDidCatch - dobrze jest gdzieś to zrobić, żeby złapać błędy
shouldComponentUpdate(nextProps, nextState){
// zwracanie false zablokuje cały update 
}
-------------------------app.tsx
interface State {
    selectedOrderId?: number,
    orders: OrderInfo[]
}

class App extends React.Component<{}, State> {

    // constructor(props: {}) {  //tak też można
    //     super(props);
    //     this.state = {
    //         selectedOrderId: 1,
    //         orders: getOrdersShortInfo()
    //     }
    // }

    state: State = {
        selectedOrderId: undefined,
        orders: getOrdersShortInfo()
    }

    changeSelectedOrder = (selectedOrder: number) => {
        this.setState({
            selectedOrderId: selectedOrder
        })
    }
... 
render() {
 { !!this.state.selectedOrderId && (
    <OrderDetails orderId={this.state.selectedOrderId}/>
                            )}

}
-------------------------
interface Props {
    orders: OrderInfo[],
    onOrderSelect: (id: number) => void
}

const OrderSelectionList: React.FC<Props> = ({orders, onOrderSelect}) => {
    return (
        <React.Fragment>
            {orders.map((order) => (
                <div className="box" key={order.name} onClick={() => onOrderSelect(order.id)}>

Nawigacja

npm install react-router-dom

const NavBar - () => {
   return(
   <BrowserRouter>
   <div> ...
   <ul>
   <li> 
   <Navlink className="nav-link" to="/">Home</NavLink>
   </li>
   </div>
   <Switch> //renderuje 1 pasujący
   <Route path="/" exact component={App} />
   </Switch>
   <BrowserRouter>

);
}

Polecenia

npm create-react-app <app-name>

npm start - uruchamia

npm install - instaluje biblioteki 

Bindowanie

Funkcję np. cartItemRemoveHandler zamieniamy tak, że będzie wywołana z dodatkowym parametrem id

const cartItemRemoveHandler = id => {}
const cartItemAddHandler = item => {}

const cartItems = (
    <ul className={classes['cart-items']} >
    {cartCtx.items.map(item => {
        <CartItem
            key={item.id}
            onRemove={cartItemRemoveHandler.bind(null, item.id)}
            onRemove={cartItemAddHandler.bind(null, item)}
        />
    })}
)

Komponent klasowy

import React from 'react'
class ClassComponent extends React.Component {
    constructor() {
        super();
        this.state = {
            showUsers: true,
            more: 'Test'
        }  
    }
    toogleUserHandler() {
    //w oróżnieniu od hooka setState, tutal podmnieniamy tylko wymienione parametry
    //reszta pozostaje bez zmian
        this.setState(curState => {
            return {showUsers: !curState.showUsers };
        })
    }
    
    render() {
        return (
            <ul>
                <li>{this.props.something}</li>x
                <li>{this.state.something}</li>
            </ul>
        );
   }
}

SideEffects

  • ComponentDidMount() = useEffect(..., [])
  • ComponentDidUpdate(prevProps, prevState) = useEffect(..., [someValue]) Trzeba dodać jakiegoś ifa jeśli zmieniamy state, żeby nie zrobić nieskończonej pętli
  • ComponentWillUnmount = useEffect(() => return () => {...}, [])

Context

Inicjalizowanie - tak jak w funkcyjnym. Użycie inaczej. Tak można tylko użyć jednego kontextu. Można jeszcze przez Cosumera

static contextType = UseContext;
...
this.context.users

ErrorBoundary

Tylko w komponncie klasowym

class ErrorBoundry extends Component {
    constructor() {
        super();
        this.state = { hasError: false }
    }
    componentDidCatch(error) { //kiedy dziecko wygeneruje błąd
        this.setState({hasError: true})
    }
    render() {
        if(this.state.hasError){
            //działa tylko na produkcji
            return 'Something goes wrong';
        }
        return this.props.children()
     }
}
...
<ErrorBoundry>
   <jakieśkompoenty>
</ErrorBoundry>
//i teraz można wrapować componenty, których błądy chcę wrapować
        
}

Komponent funkcyjny

Nowoczesne podejście Nie trzeba od jakieś wersji dodawać import react

const functionalComponent = () => {
     return (...)
}
export default FunctionalComponent

Scroling

class OrderDetails extends React.PureComponent<Props> {

    topRef = React.createRef<HTMLInputElement>();
    variable = React.createRef<number>(); //zmienna, przydatna w komponentach funkcyjnych

    componentDidMount() {
        this.topRef?.current?.scrollIntoView()
    }

    componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<{}>, snapshot?: any) {
        this.topRef?.current?.scrollIntoView()
    }

    render() {
        const order = this.props.order

        return (
            <Fragment>
                <h2 className="title is-3" ref={this.topRef}>{order.name}</h2>

Struktura Projektu

podejście 1:
PageOne
 components
   Element1.js
   Element1.test.js
   Element1.module.scss
   Element2.js
   Element2.test.js
   Element2.module.scss
 containers
   PageOneContainer.js
   PageOneContainer.test.js

podejście 2:
PageOne
  Element1
    Element1.js
    Element1.test.js
    Element1.module.scss
  Element2
    Element2.js
    Element2.test.js
    Element2.module.scss
  PageOneContainer
    PageOneContainer.js
    PageOneContainer.test.js

Routing

Całą aplikację zawijamy w komponent integrujący routing z historią w przeglądarce:

<BrowserRouter>
    <main>My app…</main>
</BrowserRouter>

W testach przyda się:

<MemoryRouter 
    initialEntries={["/tables/1"]}>
<Route path="/" exact
       component={HomePage} />

<Route path="/about" 
       component={AboutPage} />

<Route path="/post/:id"
       component={PostPage} />

// odczyt w PostPage
this.props.match.params.id

linki w HTML

<Link to="/about">About</Link>

<NavLink to="/post/123"
activeClassName="highlight">
Post 123
</NavLink>

// programowo
this.props.history.push("/about");

Mamy switch - wybiera jedną route routy można renderować też niżej w komponentach

Metody współdzielenia logiki

// fabryka produkująca fabrykę (HOC-a)
function withHOC(opts) {
    // HOC = fabryka produkująca komponenty
    return function (Target) {
        // komponent-dekorator:
        return (props) => {
           // dodatkowa logika
           return (
             <Target {...props}/>
           )
        }
    }
}

// fabryka produkująca fabrykę (HOC-a)

const withWaiterCondition =  withRoleCondition(UserRole.waiter)
const WaiterOnlyRoute = withWaiterCondition(Route)


function withRoleCondition<T exdends object>(exceptedRole: UserRole) {
    // HOC = fabryka produkująca komponenty
    return function <T exdends object>(Target: React.ComponentType) {

       return class extends ReactComponent {
          static contentType = RoleContent
          
          render() {
             if(currentRole !== expectedRole) {
               return null;
             }
             return (
                <Target {...this.props} />
             );
           }
    }
}

Generyczny komponent

interface State<T> {
    data: T[]
}

interface Props<T> {
    loadData: () => Promise<T[]>,
    viewName: string,
    selectionList: (data: T[]) => React.ReactNode,
    detailView: (id: number) => React.ReactNode,
}

class SelectableList<T> extends React.Component<RouteComponentProps & Props<T>, State<T>> {

    state: State<T> = {
        data: []
    }

    async componentDidMount() {
        const data = await this.props.loadData()
        this.setState({data})
    }

    render() {
        return (
            <div className="columns">
                <div className="column is-one-quarter">
                    <h2 className="title is-3">{this.props.viewName}</h2>
                    {this.props.selectionList(this.state.data)}
                </div>
                <main className="column">
                    <Route path={this.props.match.url + '/:id'}
                           render={({match}) => {
                               return this.props.detailView(parseInt(match.params.id))
                           }}/>
                </main>
            </div>
        )
    }
}
///////////////////// użycie
interface State {
    orders: OrderInfo[]
    selectedOrderId?: number
}

class KitchenPage extends React.Component<RouteComponentProps, State> {

    render() {
        return (
            <SelectableList
                {...this.props}
                loadData={getOrdersShortInfo}
                viewName={"KitchenView"}
                selectionList={(data) => {
                    return <OrderSelectionList orders={data}/>
                }}
                detailView={(id) => {
                    return <OrderDetailsContainer orderId={id}/>
                }}
            />
        );
    }
}

export default KitchenPage;

Java Script

Typy

const amountNumber = +amountString;

Tablice

Concat

Łączy zwracając nową tablicę

array.concat(newItem) - zwraca nową tablicę w przeciwieństwie do push

Reduce

Pozwala przelecieć przez wszystkie elementy i np. zsumować coś

const numberOfCardItems = items.reduce((currentNumber, item) => {
        return currentNumber + item.amount;
}, wartoścPoczatkowa);

FindIndex

Zwraca index pierwszego elementu, który spełnia warunek

const existingCartItemIndex = state.items.findIndex(item => item.id === action.item.id)

Promise

doSomethingAsync()
     .then(r => doSomthingWithResult(r)
// to samo co 

const r = await doSomethingAsycn();
doSomthingAsync(r);
doSomethingAsync()
    .then(result1 => {
        return doSomethingWithResult(result1);
    })
    .then(result2 => doSomethingMore(result2))
    .catch(err => {
        doSomethingWithError(err);
    });

// to samo co 

try {
    const result1 = await doSomethingAsync();
    const result2 = await doSomethingWithResult(result1);
    await doSomethingMore(result2);
} catch (err) { 	
    doSomethingWithError(err);
}

API

Axios

W axios nie trzeba sprawdzać czy jest ok, albo kodu http tak jak przy fetch

npm install axios // można użyć fetcha albo tego

Fetch

const[movies, setMovies] = useState([])
 
function fetchMoviesHandler() {
    fetch('https://swapi.dev/api/films/').then(response => {
        return respnse.json(); //automatycznie konwertuje na obiect z jsona
    }).then(data => {
         const transformedMovied = data.resuls.map(movieData => {
             return {
                 id: movieData.episode_id
             }
         })
         setMovies(transformedMovied);
    })
    ///.catch() //tak błędy
}
// to samo, plus is loading
async function fetchMoviesHandler() {
        setIsLoading(true)
        setError(null)
    try {
        const response = await fetch('https://swapi.dev/api/films/');
        if (!response.ok) { //lub sprawdzanie kodu http
            throw new Error('Somthing went wrong!')
        }
        const data = await response.json(); 
        const transformedMovied = data.resuls.map(movieData => {
             return {
                 id: movieData.episode_id
             }
         });
         setMovies(transformedMovied);
     } catch (error) {
        setError(error.message)      
     }
     setLoading(false);
}

Fetch & useEffect & useCallback

Żeby wywołać powyższe fetchMoviesHandler przy inicjalizowaniu komponentu trzeba zrobić to w useEffect. I jako dependecję trzeba ustawić samą fetchMoviesHandler. Jako że to jest obiekt, a obiekty się zmieniają przy rewalidacji to trzeba ciało fetchMoviesHandler wywołać w useCallback

useEffect(() => { //poniżej defiicję funkcji 
    fetchMoviesHandler();
}, [fetchMoviesHandler])

const fetchMoviesHandler = useCallback(async () => {
    ...
    }, [] // tu też zewnętrzne dependencje, powyższy fetchMoviesHandler nie ma takich
}

Fetch - Post

fetch('https://somurl', {
    method: 'POST'
    body: JSON.stringify(movie) //wymaga stringowego jsona
    headers: { 
        'Content-Type': 'application/json' //może być wymagany
    }
})
# Typescript, State, Onclick
any jest przypisywany do wszystkiego, wszystko jest przypisywane do any

unknown - na górze wszystkich typów, wszystko extends unknown, ale unknown nic nie extenduje
never - na dole typów, nie zawiera żadnej wartości

#### Operator &

```javascript
type Foo = {a:string}
type Bar = {b:string}
type FooAndBar = Foo & Bar // {a:string, b:string}

Keyof

interface MyInterface {
    a: string;
    b: number;
    c: OtherType;
}

type MyKeys = keyof MyInterface;
// jest równoważne z:
type MyKeys = 'a' | 'b' | 'c';

Pick

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

type Foo = Pick<MyInterface, 'a' | 'b'>;
// jest równoważne z:
type Foo = {a: string; b: number};

Exclude

type Foo = Exclude<'a' | 'b', 'b' | 'c'>;
// jest równoważne z:
type Foo = 'a';
foo.a = "updated"; // error

Readonly

type Foo = {
	readonly a: string
};
const foo: Foo = {a: "initial"};
foo.a = "updated"; // error

-------------------
type Foo = {a: string};
type ReadonlyFoo = Readonly<Foo>;

// jest równoważne z:
type ReadonlyFoo = {readonly a: string};

Patrz także:
ReadonlyArray<T>
ReadonlySet<T>
ReadonlyMap<K, V>

Partial

type Foo = {a: string, b: number};
type PartialFoo = Partial<Foo>;

// jest równoważne z:
type PartialFoo = {
    a?: string;
    b?: number;
};

Omit

pick + exclude

type OrderDTO = Omit<Order, 'details'> & {details: OrderDetailsDTO}

Redux

reducer-concept

Jest centralny dataStore. Komponenty się subskrybują na niego i są powiadamiane o zmianach, ale bezpośrednio nie mogą zmienić tych danych. Robią to przez dispachowanie akcji. Następnie akcje te przekazywane są do reducerów. One robię co powinny dla tej akcji i zmieniają state w centralnej dataStore i zasubkrybowane komponenty są powiadamiane o zmianie.

  • stosujemy w dużych aplikacjach (nie trzeba jak w kontekscie robić jeden duży contextProvider, albo dużo małych).
  • lepiej nadaje się do często zmieniających się danych
  • gdy dużo elementów zależy od siebie
  • nie, gdy jest mało interakcji pomiędzy komponentami

Komandy

npm init 
npm install redux
node redux-demo.js
// z reactem
npm install react-redux

Reducer

Funkcja przyjmująca oldState i dispatchedAction, a zwracająca newStateObject. Powinna być pure function - ten sam input powinien zawsze zwracać ten sam output, bez żadnych sideEffects.

Działanie - bez Reacta

const redux = require('redux');
//dajemy default value do state
const counterReducer = (state = {counter: 0}, action) => {
    if (action.type === 'increment') {
        return {
            counter: state.counter + 1
        };
    }
    if (action.type === 'decrement') {
        return {
            counter: state.counter - 1
        };
    }
    return state;
};

const store = redux.createStore(counterReducer);
console.log(store.getState());

const counterSubscriber = () => {
  const latestState = store.getState();
  console.log(latestState);
};

store.subscribe(counterSubscriber);
store.dispatch({type: 'increment'});
store.dispatch({type: 'decrement'});

React + Redux

Komponent funkcyjny

// np. store/index.js
import {createStore} from "redux";
//state ma domyślną - inicjalizująca wartość
const initialState = {counter: 0, showCounter: true}
const counterReducer = (state = initialState, action) => {
    if (action.type === 'increment') {
// nie możemy tu zmieniać state. Trzeba zwracać nowy obiekt!       
        return { // to nadpisuje state
            counter: state.counter + 1,
            showCounter: state.showCounter
        };
    }
    if (action.type === 'increase') { // ten actiontype może wrzucić do stałej i wyeksportować
        return {
            counter: state.counter + action.amount,
            showCounter: state.showCounter
        };
    }
    if (action.type === 'decrement') {
        return {
            counter: state.counter - 1,
            showCounter: state.showCounter
        };
    }
    if (action.type === 'toggle') {
        return {
            counter: state.counter,
            showCounter: !state.showCounter
        };
    }
    return state;
};

const store = createStore(counterReducer);
export default store;
//------------ App.js
//trzeba obwinąć przez Provider komponent, w których chcemy użuwać reduxa
//jak obwiniemy app, to można do używać wszędzie
import {Provider} from 'react-redux'

import './index.css';
import App from './App';
//trzeba mu podać z jakiego store kożystamy
import store from './store/index';
ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);
//------------ Counter.js
// tu jak chcemy kożystać z reduxa możemy użyć hooda useState albo useSeletor
// można też użyc funkcji connet - dla komponentów klasowych
// useDispatch - do dispatchowania akcji
import {useDispatch, useSelector} from 'react-redux'

const Counter = () => {
    const dispatch = useDispatch();
    //to pobiera wartość, ale też automatycznie dodaje
    //subskrybcje zmian stora do tego komponentu
    //bierzemy state, z niej counter i apisujemy w zmiennej counter
    const counter = useSelector(state => state.counter);
    const showCounter = useSelector(state => state.showCounter);

    const incrementHandler = () => {
        dispatch({type: 'increment'})
    };
    const increaseHandler = (amount) => {
        dispatch({type: 'increase', amount: amount})
    };
    const decrementHandler = () => {
        dispatch({type: 'decrement'})
    };
    const toggleCounterHandler = () => {
        dispatch({type: 'toggle'})
    };

    return (
        <main className={classes.counter}>
          <h1>Redux Counter</h1>
          {showCounter && (<div className={classes.value}>{counter}</div>) }
          <div>
            <button onClick={incrementHandler}>Inclement</button>
            <button onClick={() => increaseHandler(5)}>Increasy by 5</button>
            <button onClick={decrementHandler}>Declement</button>
          </div>
           <button onClick={toggleCounterHandler}>Toggle Counter</button>
        </main>
    );
};

export default Counter;
//App.js jak wyżej
//-------------------Counter.js
import {useDispatch, useSelector} from 'react-redux'
import {counterActions} from "../store";

const Counter = () => {
    const dispatch = useDispatch();
    //to pobiera wartość, ale też automatycznie dodaje
    // subskrybcje zmian stora do tego komponentu
    const counter = useSelector(state => state.counter);
    const showCounter = useSelector(state => state.showCounter);

    const incrementHandler = () => {
        dispatch(counterActions.increment())
    };
    const increaseHandler = (amount) => {
        // amount będzie dostępny w zmiennej payload akcji
        // może to też być obiekt
        dispatch(counterActions.increase(amount));
    };

    const decrementHandler = () => {
        dispatch(counterActions.decrement())
    };

    const toggleCounterHandler = () => {
        dispatch(counterActions.toggleCounter())
    };

    return (
    <main className={classes.counter}>
      <h1>Redux Counter</h1>
    {showCounter && (<div className={classes.value}>{counter}</div>) }
      <div>
        <button onClick={incrementHandler}>Inclement</button>
        <button onClick={() => increaseHandler(5)}>Increasy by 5</button>
        <button onClick={decrementHandler}>Declement</button>
      </div>
       <button onClick={toggleCounterHandler}>Toggle Counter</button>
    </main>
    );
};

export default Counter;

Redux toolkit

Ułatwia korzystanie z reduxa.

npm install @reduxjs/toolkit
//np. store.index.js
import {configureStore, createSlice} from '@reduxjs/toolkit'

const initialState = {counter: 0, showCounter: true};
//tworzymy slice - może być osobno dla różnych części reducera - 
//np. w innym pliku autentyfikacja, w innym coś innego
const counterSlice = createSlice({
    name: 'counter', //nazwa jest wymagana
    initialState, //to co initialState: initialState
    reducers: {
        increment(state) {
            state.counter++; //tutaj - dzięki toolkit, możemy manipulować istniejącą state
            //bo już toolikit zajmuje się tym żeby stworzyć nową state
        },
        decrement(state) {
            state.counter--
        },
        increase(state, action) {
            state.counter = state.counter + action.payload;
        },
        toggleCounter(state) {
            state.showCounter = !state.showCounter;
        }
    }
});

const store = configureStore({
    //to może być pojedyńczy reducer:
    reducer: counterSlice.reducer
    //albo mapa reducerów, które będą dla nas zmergowane w jednej,
    //trzeba pamiętać o tym że w useSelector po state. trzeba dodać nazwę reducera, tu np. counter2
    // reducer: {
    //     counter: counterSlice.reducer,
    //     counter2: counterSlice.reducer,
    //   },
});
// w counter actions są akcje z auto-wygenerowanym typem
export const counterActions = counterSlice.actions;
export default store;

Action creators

Można użyć np. do side efectów

//store/cart-actions.js

export const fetchCartData = () => {
    return dispatch => {
        const fetchData = async () => {
            const response = await fetch(
                'http://some.url'
            )   
          if(!response.ok) {
              throw new Error('Could not fetch cart data!'); 
          }
          const data = await response.json();
          return data;
        };
        try {
            const cartData = fetchData();
            dispatch(cartActions.replaceCard({
              items: cardData.items || [],
              totalQuantity: cartData.totalQuantity
            }));
        } catch(error) {
            dispatch(
                  ui.Actions.showNotification({
                    status: 'error',
                    title: 'Error!',
                    message: 'Sending cart data failed',
                  })
          );  
        }
    }
}

export const sendCardData = cart => {
    return async dispatch => {
        dispatch(
          ui.Actions.showNotification({
            status: 'pending',
            title: 'Sending...',
            message: 'Seding cart deta!',
            })  
        ); 
        const sendRequest = async () => {
          const response = await fetch(
                  'http://some.url',
                  {
                    method: 'PUT',
                    body: JSON.stringify(cart)
                  }
          );
          if(!response.ok) {
              throw new Error('Sending cart data failed.');
          }
        }
        try {
          await sendRequest();
          dispatch(
                  ui.Actions.showNotification({
                    status: 'success',
                    title: 'Success!',
                    message: 'Sending cart data successfully!',
                  })
          );
        } catch (error) {
          dispatch(
                  ui.Actions.showNotification({
                    status: 'error',
                    title: 'Error!',
                    message: 'Sending cart data failed',
                  })
          );
        }
        dispatch();
    }
}
------------App.js

let isInitial = true;

function App() {
    const dispatch = useDispatch();
    const cart = useSelector = (state => state.cart);
    
    useEffect(() => {
        dispatch(fetchCartData()); // wywołujemy naszą akcję
    }, [cart, dispatch]);
    
    useEffect(() => {
        if(isInitial){
            isInitial = false;
            return;
        }
        if (cart.changed) { //zmieniane tylko jak coś zmienimy,
            // żeby nie było zapisywane za 1 razem
          dispatch(sendCartData(cart));
        }
    }, [cart, dispatch]);
}

Komponent klasowy

import {connect} from 'react-redux'
class Counter extends Component {
    incrementHandler(){
        this.props.increment(); //funkcja z mapDispatchToProps
    };
    decrementHandler(){
        this.props.decrement();
    };
    toggleCounterHandler() {}

    render() {
        return (
            <main className={classes.counter}>
              <h1>Redux Counter</h1>
              <div className={classes.value}>{counter}</div>
              <div>
  <!-- robimy bind(this), żeby this w metodzie odnosiło się do klasy -->
                <button onClick={this.incrementHandler.bind(this)}>Inclement</button>
                <button onClick={this.decrementHandler.bind(this)}>Declement</button>
              </div>
               <button onClick={this.toggleCounterHandler.bind(this)}>Toggle Counter</button>
            </main>
            );
    };
    
    const mapStateToProps = state => {
        return {
            counter: state.counter      
        }
    };
    const mapDispatchToProps = dispatch => {
        return {
            increment: () => dispatch({typw: 'increment'}),   
            dectement: () => dispatch({typw: 'decrement'}),   
        }; 
    };
}
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
  1. Tworzymy routera (dtate/index.js)
  2. opakować w app
  3. Tworzymy akcje (mona na poziomie modułu)

reducery - Reducer to funkcja, które przyjmuje poprzedni stan oraz akcję i zwraca zupełnie nowy stan, Tworzą nowy stan, stan trafia do zasubsktypcjonowanych komponenów, za pomocą mapStateToProps, lub hookowej useSelector z komponentów wychodzą eventy przez Action creator, który robi dispatchAction i przekazują do stora

  1. Reducery
export function ordersReducer(orders = [], action) {
      switch(action.type) {
            case DishMarkedAsReadyAction:
                  return orders.map((order) => {
                      if(order.id === action.orderId) {
                            return orderRecer(order, action) //delegujemy do innego reducera
                       }
                       return order
                  })
            default:
                 return orders
      }
}

function orderReducer (order, action) {
             switch (action.type){
                  case DishMarkedAsReadyAction:
                        return {..order, dishes: dishesReducer(order.dishes, 
               } 

}

function disherReducer(dishes, action) {
                                     
}

function dishReducer(dishes, action) {
⚠️ **GitHub.com Fallback** ⚠️