:-)

)-':

:-/

[]);

useEffect(() => {

  // ZONA PELIGROSA

}, []);

useImperativeHandle()
useEffect()
useEff this
function Component(props) {
  
  useEffect(() => {
    // Do something
    
    return () => {/* Cleanup */}
  }, [/* Dependencies */]);
  
  
  // ...

  
  return (
    <div>{/* ... */}</div>
  );
  
}

×2

React 18 runs effects
twice on mount

(in strict mode)

is not for all effects.

useEffect()

🤔

componentDidMount

componentDidUpdate

componentWillUnmount

useEffect(() => {
  // componentDidMount?
}, []);
useEffect(() => {
  // componentDidUpdate?
}, [something, anotherThing]);
useEffect(() => {
  return () => {
    // componentWillUnmount?
  }
}, []);

useEffect is not a lifecycle hook.

import React, { useState, useEffect } from 'react';

function Example() {
  const [value, setValue] = useState("");
  const [count, setCount] = useState(-1);
  
  useEffect(() => {
    setCount(count + 1)
  });
  
  const onChange = ({ target }) => setValue(target.value);
  
  return (
    <div>
      <input type="text" value={value} onChange={onChange} />
      <div>Number of changes: {count}</div>
    </div>
  );
}

useEffect is not

a state setter.

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

No dependency array!

Dependency array

useEffect(() => {
  
  doSomething();

}, [whenever, these, things, change])

Effect

"Declarative"

"Imperative"

  • When something happens,
  • execute this effect.
  • When something happens,
  • it will cause the state to change
  • and depending on which parts of the state changed,
  • this effect should be executed,
  • but only if some condition is true.
  • And React may execute it again
  • for some future reason.
  • But only in Strict mode!
  • Which you shouldn't disable
  • for some future reason.

Dependency array

useEffect(() => {
  doSomething();
  
  return () => cleanup();
}, [whenThisChanges]);

Ideal

useEffect(() => {
  if (foo && bar && (baz || quo)) {
    doSomething();
  } else {
    doSomethingElse();
  }
  
  // oops, forgot the cleanup
}, [foo, bar, baz, quo]);

Ideal Reality

useEffect(() => {
  if (isOpen && component && containerElRef.current) {
    if (React.isValidElement(component)) {
      ionContext.addOverlay(overlayId, component, containerElRef.current!);
    } else {
      const element = createElement(component as React.ComponentClass, componentProps);
      ionContext.addOverlay(overlayId, element, containerElRef.current!);
    }
  }
}, [component, containerElRef.current, isOpen, componentProps]);
  useEffect(() => {
    if (removingValue && !hasValue && cssDisplayFlex) {
      setCssDisplayFlex(false);
    }
    setRemovingValue(false);
  }, [removingValue, hasValue, cssDisplayFlex]);
const isVisible = useOnScreen(ref)
const { data, error, mutate, size, setSize, isValidating } = useSWRInfinite(
  (...args) => getKey(...args, repo, PAGE_SIZE),
  fetcher
)

const issues = data ? [].concat(...data) : []
const isLoadingInitialData = !data && !error
const isLoadingMore =
      isLoadingInitialData ||
      (size > 0 && data && typeof data[size - 1] === 'undefined')
const isEmpty = data?.[0]?.length === 0
const isReachingEnd = size === PAGE_SIZE
const isRefreshing = isValidating && data && data.length === size

useEffect(() => {
  if (isVisible && !isReachingEnd && !isRefreshing) {
    setSize(size + 1)
  }
}, [isVisible, isRefreshing])

Dependences are the wrong

mental model for effects.

function Component(props) {
  
  useEffect(() => {
    // Do something
    
    return () => {/* Cleanup */}
  }, [/* Dependencies */]);
  
  
  // ...

  
  return (
    <div>{/* ... */}</div>
  );
  
}

React 18 runs effects
twice on mount

(in strict mode)

How to fix this?

×2

cleanup

┬─┬ノ( º _ ºノ)

Unmount (simulated)

effect

(╯°□°)╯︵ ┻━┻

Remount

effect

(╯°□°)╯︵ ┻━┻

Mount

useEffect()
useDefect()
useFoot(() => {
  setGun(true); 🦶🔫
});

What is useEffect() for?

Synchronization.

useEffect(() => {
  const sub = createThing(input).subscribe(value => {
    // do something with value
  });
  
  return sub.unsubscribe;
}, [input]);

What is useEffect() for?

Synchronization.

useEffect(() => {
  const sub = createThing(input).subscribe(value => {
    // do something with value
  });
  
  return sub.unsubscribe;
}, [input]);
const [itemData, setItemData] = useState(null);

useEffect(() => {
  // Synchronize with external system
  const sub = storeApi.subscribeToItem(itemId, setItemData);
  
  // Subscription disposal
  return sub.unsubscribe;
  
}, [itemId]); // Subscription dependency

Action effects

Activity effects

"Fire-and-forget"

Synchronized

External
system

Activity effects

Synchronized

Unmount

Remount

Where do action
effects go?

function Component(props) {
  
  useEffect(() => {
    // ...
    
    return () => {/* ... */}
  }, [/* ... */]);
  
  
  // ...

  
  return (
    <div>{/* ... */}</div>
  );
}

???

No side-effects in render

useEffect (awkward)

Outside the component?

eventHandler()

someEffect

someEffect

someEffect

someEffect

Action effects happen
outside of rendering.

Where do action effects go?

Event handlers.

Sorta.

<form onSubmit={event => {
  // 💥 side-effect!
  submitData(event);
}}>
  {/* ... */}
</form>

Event happens

Effect

Event happens

State changes

Effect

State changes

State changes

Effect

Event handler

useEffect

UI is a function of state

(state, event) => nextState
effects...?
(state) => UI
(state, event) => (nextState,        )
effects
(state, event) => nextState

🍵

When do effects happen?

State transitions.

Always.

Middleware, callbacks, sagas, reactions, sinks, monads (?), whenever

Where do action effects go?

Event handlers.

In state transitions.

...which happen to be executed at the same time as event handlers.

beta.reactjs.org

You don't need useEffect for

handling user events.

useEffect() ➡️ eventHandler()
const [isLoading, setIsLoading] = useState(false);
const [formData, setFormData] = useState(null);
const [error, setError] = useState(null);

useEffect(() => {
  if (!isLoading || !formData) { return; }
  let isCanceled = false;
  
  submitData(event)
    .then(() => {
      if (isCanceled) { return; }
      setIsLoading(false);
    })
    .catch(err => {
      setIsLoading(false);
      setError(err);
    });
  
  return () => {
    isCanceled = true;
  }
}, [isLoading, formData]);

<form onSubmit={event => {
  setIsLoading(true);
  setFormData(event);
}}>
  {/* ... */}
</form>
<form onSubmit={event => {
  // 💥 side-effect!
  submitData(event);
}}>
  {/* ... */}
</form>
const [isLoading, setIsLoading] = useState(false);

<form onSubmit={event => {
  if (isLoading) { return; }

  // 💥 side-effect!
  submitData(event);

  setIsLoading(true);
}}>
  {/* ... */}
</form>
const [isLoading, setIsLoading] = useState(false);

<form onSubmit={event => {
  if (isLoading) { return; }

  // 💥 side-effect!
  submitData(event)
    .then(() => { setIsLoading(false) })

  setIsLoading(true);
}}>
  {/* ... */}
</form>
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);

<form onSubmit={event => {
  if (isLoading) { return; }

  // 💥 side-effect!
  submitData(event)
    .then(() => { setIsLoading(false) })
    .catch(err => {
      setIsLoading(false);
      setError(err);
    });

  setIsLoading(true);
}}>
  {/* ... */}
</form>
const [state, send] = useCheckoutForm();

<form onSubmit={event => {
  send({ type: 'submit', data: event });
}}>
  {/* ... */}
</form>

You don't need useEffect for

transforming data.

useEffect() ➡️ useMemo()
function Cart() {
  const [items, setItems] = useState([]);
  const [total, setTotal] = useState(0);

  useEffect(() => {
    setTotal(
      items.reduce((currentTotal, item) => {
        return currentTotal + item.price;
      }, 0)
    );
  }, [items]);

  // ...
}
function Cart() {
  const [items, setItems] = useState([]);
  const total = items.reduce((currentTotal, item) => {
    return currentTotal + item.price;
  }, 0);

  // ...
}




function Cart() {
  const [items, setItems] = useState([]);
  const total = useMemo(
    () =>
      items.reduce((currentTotal, item) => {
        return currentTotal + item.price;
      }, 0),
    [items]
  );

  // ...
}

You don't need useEffect for

communicating with parents.

useEffect() ➡️ eventHandler()
function Product({ onOpen, onClose }) {
  const [isOpen, setIsOpen] = useState(false);

  useEffect(() => {
    if (isOpen) {
      onOpen();
    } else {
      onClose();
    }
  }, [isOpen]);

  return (
    <div>
      <button
        onClick={() => {
          setIsOpen(!isOpen);
        }}
      >
        Toggle quick view
      </button>
    </div>
  );
}
function Product({ onOpen, onClose }) {
  const [isOpen, setIsOpen] = useState(false);

  function toggleView() {
    const nextIsOpen = !isOpen;
    setIsOpen(!isOpen);
    if (nextIsOpen) {
      onOpen();
    } else {
      onClose();
    }
  }

  return (
    <div>
      <button onClick={toggleView}>Toggle quick view</button>
    </div>
  );
}
function useToggle({ onOpen, onClose }) {
  const [isOpen, setIsOpen] = useState(false);
  
  function toggler() {
    const nextIsOpen = !isOpen;
    setIsOpen(nextIsOpen);
    
    if (nextIsOpen) {
      onOpen();
    } else {
      onClose();
    }
  }
  
  return [isOpen, toggler];
}

function Product({ onOpen, onClose }) {
  const [isOpen, toggler] = useToggle({ onOpen, onClose });

  return (
    <div>
      <button onClick={toggler}>Toggle quick view</button>
    </div>
  );
}

You don't need useEffect for

subscribing to external stores.

useEffect() ➡️ useSyncExternalStore()
function Store() {
  const [isConnected, setIsConnected] = useState(true);

  useEffect(() => {
    const sub = storeApi.subscribe(({ status }) => {
      setIsConnected(status === 'connected');
    });

    return () => {
      sub.unsubscribe();
    };
  }, []);

  // ...
}
function Product({ id }) {
  const isConnected = useSyncExternalStore(
    storeApi.subscribe,
    () => storeApi.getStatus() === 'connected',
    true
  );

  // ...
}


import { createMachine } from 'xstate';
import { useMachine } from '@xstate/react';
// ..

const machine = createMachine({
  // ...
});

const CheckoutForm = () => {
  const [state, send] = useMachine(machine);
  
  return (
    <form onSubmit={() => send('SUBMIT')}>
      {state.matches('shipping') && <Shipping />}
      {state.matches('billing') && <Billing />}
      {state.matches('review') && <Review />}
      {state.matches('receipt') && <Receipt />}
    </form>
  );
}
⬅ useSyncExternalStore()
npm i @xstate/react
import { createMachine, interpret } from 'xstate';
import { useMachine } from '@xstate/react';

const machine = createMachine({
  initial: 'first',
  states: {
    first: {
      entry: doSomething,
      // ...
    },
    // ...
    submitting: {
      invoke: {
        src: (context) => submitForm(context.data),
        onDone: {
          actions: logAnalytics,
          target: 'submitted'
        }
      }
    },
    submitted: {
      // ...
    }
  }
});

Entry + exit actions

Invocations (activities)

Transition actions

npm i xstate

Demo time

stately.ai/editor

You don't need useEffect for

fetching data.

useEffect() ➡️ renderAsYouFetch()
import { getItems } from '../storeApi';

function Store() {
  const [items, setItems] = useState([]);

  useEffect(() => {
    let isCanceled = false;

    getItems().then((data) => {
      if (isCanceled) return;

      setItems(data);
    });

    return () => {
      isCanceled = true;
    };
  });

  // ...
}
import { useLoaderData } from "@remix-run/react";
import { json } from "@remix-run/node";
import { getItems } from '../storeApi';

export const loader = async () => {
  const items = await getItems();
  
  return json(items);
}

export default function Store() {
  const items = useLoaderData();
  
  // ...
}
import { getItems } from '../storeApi';

function Store({ items }) {
  // ...
}

export async function getServerSideProps() {
  const items = await getItems();

  return { props: { items } }
}

export default Store;
import { getItems } from '../storeApi';
import { useQuery, useQueryClient } from 'react-query';

function Store() {
  const queryClient = useQueryClient()
  // ...
  
  return (
    <button onClick={() => {
      queryClient.prefetchQuery('items', getItems);
    }}>
      See items
    </button>
  );
}

function Items() {
  const { data } = useQuery('items', getItems);
  
  // ...
}

🏃‍♀️ Race conditions

⏪ No instant back button

🔍 No initial HTML content

🌊 Chasing waterfalls

Fetching in useEffect problems

You don't need useEffect for

initializing global singletons.

useEffect() ➡️ justCallIt()
function Store() {
  useEffect(() => {
    storeApi.authenticate();
  }, []);

  // ...
}

☝️ This will run twice!

function Store() {
  const didAuthenticateRef = useRef();

  useEffect(() => {
    if (didAuthenticateRef.current) {
      return;
    }

    storeApi.authenticate();

    didAuthenticateRef.current = true;
  }, []);
} 
let didAuthenticate = false;

function Store() {
  useEffect(() => {
    if (didAuthenticate) {
      return;
    }

    storeApi.authenticate();

    didAuthenticate = true;
  }, []);
} 
storeApi.authenticate();

function Store() {
  // ...
} 
if (typeof window !== 'undefined') {
  storeApi.authenticate();
}

function Store() {
  // ...
} 
function renderApp() {
  if (typeof window !== 'undefined') {
    storeApi.authenticate();
  }

  appRoot.render(<Store />);
} 

useEffect is for synchronization

useEffect is for synchronization

State transitions trigger effects

useEffect is for synchronization

State transitions trigger effects

Action effects go in event handlers

useEffect is for synchronization

State transitions trigger effects

Action effects go in event handlers

Render-as-you-fetch 

useEffect is for synchronization

State transitions trigger effects

Action effects go in event handlers

Render-as-you-fetch

Model effects with state machines

¡Gracias, React Alicante!