React, Redux, TypeScript - rlip/java GitHub Wiki
- React
- Java Script
- API
- Redux
<div className={`form-control ${isValid ? 'invalid' : ''}`}
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}
//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
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/>
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>
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>
</>
);
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')
)
}
</>
)
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>)}
</>
);
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.
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 };
});
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]
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'});
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], [])
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]
)
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
};
);
);
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);
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);
}
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
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);
- 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)}>
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>
);
}
npm create-react-app <app-name>
npm start - uruchamia
npm install - instaluje biblioteki
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)}
/>
})}
)
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>
);
}
}
- 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 () => {...}, [])
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
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ć
}
Nowoczesne podejście Nie trzeba od jakieś wersji dodawać import react
const functionalComponent = () => {
return (...)
}
export default FunctionalComponent
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>
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
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
// 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} />
);
}
}
}
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;
const amountNumber = +amountString;
Łączy zwracając nową tablicę
array.concat(newItem) - zwraca nową tablicę w przeciwieństwie do push
Pozwala przelecieć przez wszystkie elementy i np. zsumować coś
const numberOfCardItems = items.reduce((currentNumber, item) => {
return currentNumber + item.amount;
}, wartoścPoczatkowa);
Zwraca index pierwszego elementu, który spełnia warunek
const existingCartItemIndex = state.items.findIndex(item => item.id === action.item.id)
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);
}
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
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);
}
Ż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('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}
interface MyInterface {
a: string;
b: number;
c: OtherType;
}
type MyKeys = keyof MyInterface;
// jest równoważne z:
type MyKeys = 'a' | 'b' | 'c';
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};
type Foo = Exclude<'a' | 'b', 'b' | 'c'>;
// jest równoważne z:
type Foo = 'a';
foo.a = "updated"; // error
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>
type Foo = {a: string, b: number};
type PartialFoo = Partial<Foo>;
// jest równoważne z:
type PartialFoo = {
a?: string;
b?: number;
};
pick + exclude
type OrderDTO = Omit<Order, 'details'> & {details: OrderDetailsDTO}
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
npm init
npm install redux
node redux-demo.js
// z reactem
npm install react-redux
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.
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'});
// 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;
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;
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]);
}
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)
- Tworzymy routera (dtate/index.js)
- opakować w app
- 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
- 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) {