Frontend Scrapbook

Notes that make a difference

Managing state in pure React

By admin

on Tue Aug 18 2020

State can take different forms

Model data : nouns in the application

View/UI state : Are data sorted in some order

Session State : Is the user even logged in ?

Communication: process of fetching the nouns from the server

Location : where are we in the application

This boils down to Model state ( data in your application ) and Ephemeral state ( everything else! )

1. Class based state

this.state = { count : 1 }
….
….

this.setState({count: count+1});
this.setState({count: count+1});
this.setState({count: count+1});

// React will queue up state changes
// Effectively similar to Object.assign to merge these as a batch
// output will be 1 in

A function can be passed to this.setState

this.setState(state => ({count: state.count+1}));
this.setState(state => ({count: state.count+1}));
this.setState(state => ({count: state.count+1}));

// React doesn’t batch them like the previous and output will be 3 instead. This facilitates conditionals in callback.

this.setState takes two parameters. One is the function with state and props. Other argument is called callback which is executed after state has been updated.

Should I keep something in React component state ?

If you can calculate from props, don’t keep it.

If you are not using it in render method, don’t keep it.

For everything else, keep it in react component state.

2. Hooks

const [count, setCount] = React.useState(0);

A way to manage state in functional components.

We don’t have callback here as in case of Class components, but we can use useEffect hook here ( it helps to handle side effect ).

console.log, ajax etc are all side effects. We could use multiple useEffect to trade of the modularity over creation invocation of an additional function

In class based components ‘this’ itself mutates over time, so we always get the latest value. Function components capture the rendered values.

useRef hook can be used to persist state across renders in functional components.

const countRef = useRef();
countRef.current = 10;

useEffect callback function returns a function which will be called later to cleanup. Such as cleaning up setInterval, websocket connection etc.

3. useReducer

It is a function which takes a state and an action and gives a new state.

When there are more states, we happen to add more useState(s) and when things more and more complicated stuff, it makea sense to use a reducer. We can use a reducer inside a custom hook for example. using reducers increases the testability of code

const reducer = (state, action) =>state

React.memo and useCallback

React.memo takes a functional component and it re-renders only if props change.

useCallback and useMemo are two similar hooks with some difference. useMemo will not call the function if dependencies have not changed. useMemo will give us a function which we can call later

const toggleFn = useCallback(fn, [dependency]);

const memoizedValue = useMemo(fn, [dependency]);

useMemo will call the function, fn and return its value, where as useCallback returns the function, fn without calling it. Both hooks return a value only when one of the dependency value changes ( referential equality ).

Prop drilling occurs when you have deep component trees. It becomes problamatic when it comes to maintainaility.

4. Context API

‘Context provides a way to pass data through the component tree without having to pass props down manually at every level’

We might lose performance optimizationa when moving to context API as we might no longer use useCallback and useMemo since we move all state and methods into the new context. In this case, it will be a tradeoff between performance and code maintainability.

const Context = React.createContext();

const onClick = () => { };

….

….

const value = {onClick,….}

<Context.Provider value={value} > { children } </Provider>

In any child component, we could then directly access state/methods from the context.

const { onClick } = useContext(Context)

4. Fetching data

useEffect is the way in functional component.

We can move the logic to a custom hook or a custom reducer and maintain separation of concerns between state and the UI.

5. Thunk

thunk is a function that is returned from another function.

const useThunkReducer = (reducer, initialState) => {
   const [state, dispatch] = useReducer(reducer, initialState);
   const enhancedDispatch = (action) => {
   if(isFunction(action){
     action(dispatch);
   } else {
     dispatch(action);
   };
return [state, enhancedDispatch];
}

Actions are plan javascript functions and that again increases the testability of your code.

we could implement REDO and UNDO functionality using hooks to enhance existing reducers.

const useUndoReducer = (reducer, initialState) => {
  const undoState = {
    past: [],
    present: initialState,
    future: []
  };

  const undoReducer = (state, action) => {
    const newPresent = reducer(state.present, action);

    if (action.type === 'UNDO') {
      const [newPresent, ...newPast] = state.past;
      return {
        past: newPast,
        present: newPresent,
        future: [state.present, ...state.future]
      };
    }

    if (action.type === 'REDO') {
      const [newPresent, ...newFuture] = state.future;
      return {
        past: [state.present, ...state.past],
        present: newPresent,
        future: newFuture
      };
    }

    return {
      past: [state.present, ...state.past],
      present: newPresent,
      future: []
    };
  };

  return useReducer(undoReducer, undoState);
};

const reducer = (state = initialState, action) => {
  if (action.type === 'ADD') {
    return [
      {
        id: id(),
        ...action.payload
      },
      ...state
    ];
  }

  if (action.type === 'REMOVE') {
     return state.filter((item) => {
      if (item.id !== action.payload.id) {
        return item
      }
    });
  }
  return state;
};

....
....
const [state, dispatch] = useUndoReducer(reducer, initialState); //enhanced reducer with Undo functionality