Skip to content

ReactJS

alex [dot] kramer [at] g_m_a_i_l [dot] com edited this page Jul 17, 2023 · 8 revisions

useEffect infinite-loop debugger

Borrowed from: https://stackoverflow.com/a/59843241

import { useEffect, useRef } from "react";

const usePrevious = (value, initialValue) => {
  const ref = useRef(initialValue);
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => {
  const previousDeps = usePrevious(dependencies, []);

  const changedDeps = dependencies.reduce((accum, dependency, index) => {
    if (dependency !== previousDeps[index]) {
      const keyName = dependencyNames[index] || index;
      return {
        ...accum,
        [keyName]: {
          before: previousDeps[index],
          after: dependency
        }
      };
    }

    return accum;
  }, {});

  if (Object.keys(changedDeps).length) {
    console.log('🔥🔥🔥=====> [use-effect-debugger] ', changedDeps);
  }

  useEffect(effectHook, dependencies);
};

Default props

const MyComponent: React.FC<{
  foo?: string;
  bar?: string;
}> = ({ foo = "whatever", bar = "lol" }) => {
  return (
    <div>
      {foo} {bar}
    </div>
  );
}

Optional valueless attributes

<MySpecialComponent
  attribute1="lol"
  attribute2={whatever}
  {...(someState ? { optionalAttribute1: true, optionalAttribute2: true } : {})}
  onClick={doStuff}
/>

Jest

Mock React Router useHistory

import React from "react";
import { MemoryRouter, useHistory } from 'react-router-dom';
import { render } from "@testing-library/react";

const HistoryTest = ({goto}) => {
  const history = useHistory();
  history.push(goto);
  return <></>
}

const mockHistoryPush = jest.fn();

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useHistory: () => ({
    push: mockHistoryPush,
  }),
}));

describe('Test mocking useHistory', () => {
  it('Redirects to correct URL on click', () => {
    render(
      <MemoryRouter>
        <HistoryTest goto="lol" />
      </MemoryRouter>,
    );

    expect(mockHistoryPush).toHaveBeenCalledWith('lol');
  });
});

React-Redux Example

https://stackblitz.com/edit/react-redux-in-one-file-1tkz6b

// HTML
/*
<div class="container">
  <h5 class="card-title mt-3">All Redux in one file 😁</h5>
  <div id="app" class="mb-2"></div>
  <a 
    class="badge badge-success" 
    target="_blank" 
    href="https://stackblitz.com/edit/react-redux-in-one-file?file=index.js"
  >
    Source
  </a>
</div>
*/

// Package.json
/*
{
  "name": "react-pmyis7",
  "version": "0.0.0",
  "private": true,
  "dependencies": {
    "bootstrap": "latest",
    "jquery": "1.9.1 - 3",
    "popper.js": "^1.14.3",
    "react": "16.5.0",
    "react-dom": "16.5.0",
    "react-redux": "^5.0.7",
    "redux": "^4.0.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {
    "react-scripts": "latest"
  }
}
*/

import React, { Component } from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { connect, Provider } from 'react-redux';

require('bootstrap/dist/css/bootstrap.css');
require('./style.css');

/**
 * Actions
 */
const Actions = {
  TOGGLE_MENU: 'TOGGLE_MENU',
  INCREMENT_THINGY: 'INCREMENT_THINGY',
};

/**
 * Reducer
 */
const Reducer = (state = { thingy: { num: 42 } }, action) => {
  console.log(`=====> ACTION ${action.type}: `, state);

  switch (action.type) {
    case Actions.TOGGLE_MENU:
      return { ...state, showMenu: !state.showMenu };
      break;
    case Actions.INCREMENT_THINGY:
      return { ...state, thingy: { ...state.thingy, num: ++state.thingy.num } };
      break;
    default:
      return state;
  }
  return state;
};

/**
 * Store
 */
const Store = createStore(Reducer);

/**
 * Connect
 */
const mapDispatchToProps = (dispatch, props) => ({
  onClickButton: () => dispatch({ type: Actions.TOGGLE_MENU }),
});
const mapStateToProps = (state) => ({
  showMenu: state.showMenu,
});

const IncrementButton = ({ onClickIncrementButton, thingy }) => {
  console.log(`=====> RENDER IncrementButton`);
  return (
    <button className={'btn btn-light'} onClick={onClickIncrementButton}>
      {thingy.num}
    </button>
  );
};
const IncrementButtonContainer = connect(
  (state) => ({
    thingy: state.thingy,
  }),
  (dispatch, props) => ({
    onClickIncrementButton: () => dispatch({ type: Actions.INCREMENT_THINGY }),
  })
)(IncrementButton);

// Button CLOSE
const CloseButton = (props) => {
  console.log(`=====> RENDER CloseButton`);
  const { onClickButton } = props;
  return (
    <button className={'btn btn-light'} onClick={onClickButton}>
      Close
    </button>
  );
};
const CloseButtonContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(CloseButton);

// Button TOGGLE
const ToggleButton = (props) => {
  console.log(`=====> RENDER ToggleButton`);
  const { onClickButton } = props;
  return (
    <button className={'btn btn-primary'} onClick={onClickButton}>
      Toggle visible
    </button>
  );
};
const ToggleButtonContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(ToggleButton);

// Menu
const Menu = ({ showMenu }) => {
  console.log(`=====> RENDER Menu`);
  return (
    <div>
      {showMenu ? (
        <div class="card mt-3">
          <div class="card-body">
            <nav class="nav">
              <a
                className={'nav-link active'}
                href="https://www.google.com"
                target="_blank"
              >
                Google
              </a>
              <a
                className={'nav-link'}
                href="https://www.facebook.com"
                target="_blank"
              >
                Facebook
              </a>
              <CloseButtonContainer />
            </nav>
          </div>
        </div>
      ) : (
        ''
      )}
    </div>
  );
};
const MenuContainer = connect(mapStateToProps)(Menu);

// Render
render(
  <Provider store={Store}>
    <div class="card">
      <div class="card-body">
        <IncrementButtonContainer />
        <ToggleButtonContainer />
        <MenuContainer />
      </div>
    </div>
  </Provider>,
  document.getElementById('app')
);
Clone this wiki locally