Using 8 On/Off buttons to control 8 relays

When your code doesn't block execution, you can put a button-handler in void loop() that only updates a read status for the rest of the sketch to use.

here is a one-button example from 2018. I have a multi-button version as well, might have a matrix-button version somewhere too, the big lesson is to check 1 button per void loop() pass with that running at over 50KHz reads a lot of pins per ms.
I do have a faster executing timing method since 2018 but can't find a sketch saved that uses it, instead of end time - start time it simply watches for bit 9 of micros() to flip (512 us rather than 500) which could be a different bit for faster or slower read intervals.

Non-blocking code allows tasking on a single thread is OLD tech.

// add-a-sketch_button 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, Apr 30/2018 by GFS. Compiled on Arduino IDE 1.6.9.
// Update May 6/2018, Aug 11, 2018

/*  Button Debounce Example

  --- for this example connect a button between pin 7 and GND
  --- or stick a jumper in pin 7 and while holding the board steady
  --- tap the free end onto the grounded USB port box to press.
  --- Press and hold the Button to toggle led13 blinking.

  Yes I'm using a 16 bit micros timer to time fractions of millis as micros.
  The button reader only reads 1 button per call so as to not block void loop().
  Each button has a history byte that holds the last 8 reads with 256 possible
  states but only 4 of them being significant.
  0 is the button held down
  255 is the button left up
  127 is the buton changing from up to down, button just released.
  128 is the button changing from down to up, button just pressed.
  everything else is to be ignored as bounce.
*/

// button vars
byte buttonPin = 7;
byte buttonHistory;
word markButtonTime;        // 16-bit micros timers
const word waitButtonTime = 500; // micros

// added sketch task, on-off blinker vars
byte ledState, ledPin = 13; // use byte for small values, int cost 2 bytes
word startBlink, waitBlink; // 16 bit millis is good to time 65.535 seconds

void buttonTask()   // yup, this is my new button debounce compromise method.
{ // read twice per milli, bits 0 to 6 all same sets the state
  if ( word( micros()) - markButtonTime >= waitButtonTime ) // read occaisoinally
  {
    buttonHistory <<= 1; // if you don't know <<= look it up in the Arduino Reference
    // keep a browser open to that page when you use the IDE.
    buttonHistory += digitalRead( buttonPin ); // read history streams through buttonHistory
    markButtonTime = micros(); // gets the low 16 bits of micros(), time to 60 ms + margin
  }
  /*        buttonHistory bits read as values:
    0 is button held down, 8 reads of 0 in a row
    255 is button left up, 8 reads of 1 in a row
    127 is buton changing from up to down, 7 1's and 1 0 = just released
    128 is button changing from down to up, 7 0's and 1 1 = just pressed.
    everything else is to be ignored as bounce.
    Understand that 7 same bits in a row counts as pin state stable.
  */
}

void OnOffBlinker() // only blinks if there's a wait time, can be switched on/off
{
  if ( waitBlink > 0 ) // this is the on/off switch
  {
    // word( millis()) gets the low 16 bits of the 32-bit millis() return.
    if ( word( millis()) - startBlink >= waitBlink ) // difference in time by subtracting start from end
    {
      ledState = !ledState;  // ! is NOT: not_0/true becomes 0/false else 0 becomes 1.
      digitalWrite( ledPin, ledState ); // the led changes state.
      startBlink += waitBlink; // next blink starts when it should, where diff > wait.
    }
  }
  else if ( ledState > 0 ) // waitBlink == 0 turns blinking off
  {
    digitalWrite( ledPin, ledState = LOW ); //  make sure the led is OFF
  } // yes, you can set a variable during calculation in C, the write here is LOW.
}

void setup()
{
  Serial.begin( 115200 );
  for ( byte i = 0; i < 66; i++ )  Serial.println();
  Serial.println( F( "\n  Button Debounce Example, free by GoForSmoke\n" ));
  Serial.println( F( "\n-- for this example connect a button between pin 7 and GND" ));
  Serial.println( F( "--- or stick a jumper in pin 7 and while holding the board steady" ));
  Serial.println( F( "--- tap the free end onto the grounded USB port box to press." ));

  pinMode( buttonPin, INPUT_PULLUP );
  pinMode( ledPin, OUTPUT );

  buttonHistory = 255;
  waitBlink = 500;
};


void loop()
{
  buttonTask();
  /*
    0 is the button held down
    255 is the button left up
    127 is the buton changing from up to down, button just released.
    128 is the button changing from down to up, button just pressed.
    everything else is to be ignored as bounce.
  */

  switch ( buttonHistory ) 
  {
    case 128 : // pin is HIGH in bit 7, LOW for 7 reads, up to down detected
      buttonHistory = 0; // change detected, make it into no change now
      Serial.print( F( "press detected     " ));
      Serial.println( millis());
      if ( waitBlink == 0 ) // toggle action tied to button press
      {
        waitBlink = 500; // makes the blinking start
        startBlink = millis(); // gets the low 16 bits
      }
      else
      {
        waitBlink = 0; // makes the blinking stop
      }
      break;
    case 127 : // pin is LOW in bit 7, HIGH for 7 reads, down to up detected
      buttonHistory = 255; // change detected, make it into no change now
      Serial.print( F( "release detected   " ));
      Serial.println( millis());
      break;
  }

  OnOffBlinker();
}