React 102 - Basic data flows

Bruno Noriller - Nov 16 '22 - - Dev Community

So, you’ve learned the basics of React, then when you finally started using you started falling for one pitfall after another. Well, you’re in the right place... finally!

Few things:

  • This is not to be a comprehensive guide
  • It is not for those just learning (hence 102 and not 101)
  • Also, I might take some generalizations for the sake of simplicity
    • as you may know… JS is weird!
  • Finally, I’ll pretend class components don’t exist and so neither should you.
    • I’m not saying you never should learn them, but today… not at the 102 level.

If you do want to learn from zero, I recommend checking out the Beta React Docs, you’ll learn things there that many people using it for quite a while don’t know (really!).

Finally, feel free to ask anything you might be having problems with. I love to help! (It might also help me find things to write, so win-win-win.)


Lifecycle and data flows

One of the biggest enemies of those starting React is to render stuff on the screen and have it change.

The basic render

function BasicRender(){
  return (
    <div>I'm rendering!</div>
  )
}
Enter fullscreen mode Exit fullscreen mode

This will just render whatever is in the JSX.

With props

function WithProps({counter}){
  return (
    <div>The counter is: { counter }</div>
  )
}
Enter fullscreen mode Exit fullscreen mode

First thing is that I’m destructuring the props value.

This will render at { counter } whatever the counter value is. And every time it gets a new value, it will render the new value.

In this case, there are two things to know/remember:

  1. React shallowly compares to check if the component should rerender or not.
  2. The difference between primitives and non-primitives.

Knowing that you would know that primitives are checked by their value while everything else is checked by their reference.

With split flows

function WithIf() {
  const random = Math.random();

  if (random > 0.5) {
    return (
      <div>Random is big!</div>
    );
  } else {
    return (
      <div>Random is small!</div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

if works just as you expect, there are rules about hooks where you can’t initiate them inside split flows (you can only use them at the top level of the component), but after that, it works just like that.

With state

function WithState() {
  const [counter, setCounter] = useState(0);

  return (
    <div>
      <div>The counter value is: { counter }</div>
      <button onClick={() => setCounter(counter + 1)}>Click me!</button>
    </div>
  )
}

function WithVariable() { // ???
  let counter = 0;

  return (
    <div>
      <div>The counter value is: { counter }</div>
      <button onClick={() => counter += 1}>Click me!</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

First things first: the second approach, with variable, will not work.

Why? The short answer is that React needs a hook to know that something changed in order to rerender. This happens because functional components are closures and while it will indeed change the value internally, React will never know when to rerender the component and when it does rerender it will recreate it at the 0 value.

So, why does the first approach works? That’s the React magic, and for now, you should just accept it and what you can do with it.

Remember about the props? That also applies to the state.

Non-primitives like objects or arrays, if you simply change the value inside and return the object, this means that the reference doesn’t change, even if you use the setState function.

The right way to think is that you always need to return a new reference, this means:

const newArr = [...oldArr];
const newObj = {
  ...oldObj
};

// spreading itself doesn't create new references
// but the container you're spreading it into is a new reference.
Enter fullscreen mode Exit fullscreen mode

With effects

function WithEffect1() {
  const [state, setState] = useState('initial state');

  useEffect(() => {
    setState('useEffect []');

    const timeout = setTimeout(() => {
      setState('useEffect [] > timeout');
    }, 500);

    return () => {
      setState('useEffect [] > return');
      clearTimeout(timeout);
    };
  }, []);

  return (
    <div>{state}</div>
  );
}

// you might see below duplicated, probably with the return,
// but in dev mode, it's the Strict Mode
// also, the initial state you'll never see in this situation
// because react is really fast

// If you slowed time down, you might see this happening:
/**
 * initial state
 * useEffect []
 * ~500 ms~
 * useEffect [] > timeout
 */
Enter fullscreen mode Exit fullscreen mode

This is the most basic use of useEffect. (Think of the setTimeout as a fetch call.)

Usually, we have more things going on, for example:

function WithEffect2() {
  const [state, setState] = useState('initial state');
  const [otherState, setOtherState] = useState(0);

  useEffect(() => {
    setState('useEffect [otherState]');

    const timeout = setTimeout(() => {
      setState('useEffect [otherState] > timeout');
    }, 500);

    return () => {
      setState('useEffect [otherState] > return');
      clearTimeout(timeout);
    };
  }, [otherState]);

  return (
    <div>
      <div>{state}</div>
      <button onClick={() => setOtherState(otherState + 1)}>
        Change the Other State
      </button>
    </div>
  );
}

/**
 * initial state
 * useEffect [otherState]
 * ~500 ms~
 * useEffect [otherState] > timeout
 * ~click~
 * // batched update, you'll never see the return
 * [useEffect [otherState] > return | useEffect [otherState]]
 * ~500 ms~
 * useEffect [otherState] > timeout
 */
Enter fullscreen mode Exit fullscreen mode

In this one there are more things happening.

But the idea is that the initial state will always be rendered first, regardless.

Then any and all useEffects will render the very next tick.

Anything that’s asynchronous like promises or fetches calls will render so soon they finish.

Return statements inside the useEffect trigger before the next calculation when they are triggered.

Usually, you have a state that will be rendered first (usually you’ll render as a nullish value using a if to render a Loading... or something like this). Then the fetch will start, finish and set a new state, that will trigger a render, now with the values.

One thing to take care is that if you put in the dependency array the very state you’re mutating in the useEffect, unless you have some check before, you’ll probably trigger an infinite recursion:

// this will break your page!
useEffect(() => {
    // add some exit condition if you really need to do this
  setState(state + 1);
}, [state])
Enter fullscreen mode Exit fullscreen mode

Bonus: derived state

function DerivedState({ myValue }) {
  const [state, setState] = useState(0);
  const [secondState, setSecondState] = useState(0);

  useEffect(() => {
    setState(myValue * 2);
  }, [myValue]);

  useEffect(() => {
    setSecondState(state * 2);
  }, [state])

  return (
    <div>
      <div>{state}</div>
      <div>{secondState}</div>
    </div>
  );
}

function DoThisInstead({ myValue }) {
  const stateToRender = myValue * 2;
  const secondState = stateToRender * 2;

  return (
    <div>
      <div>{stateToRender}</div>
      <div>{secondState}</div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Things like this are common. If you already have the value, you don’t need to put it inside a state and use useEffect to change it.

The way it’s been done in the first example you’ll see a cascade happening every time myValue changes and it might have unintended consequences.

If it comes from props, you can just calculate from it, when it changes, it will recalculate automatically.

If you’re doing something like a filteredItems from some fetch, same thing. The useEffect will save to the state the values, then you just use a const filtered = state.filter().

Every time state changes, so will the filter. If you need, you can always memo the values.


Example deployed

GitHub of the Project

Again: questions? Just ask! (And to not be ignored… read this helpful “how to ask a good question”)

And… question for YOU! Where are you having trouble with React?


Cover Photo by Solen Feyissa on Unsplash

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .