Original article: How to Prepare for React Interviews – Front-End Technical Interview Guide

Una entrevista técnica es una oportunidad que tiene un empleador potencial de evaluar tus habilidades y conocimientos de desarrollo web.

El entrevistador te hará preguntas sobre tu experiencia y habilidades en HTML, CSS, y JavaScript. También te preguntarán, probablemente, preguntas específicas de frameworks como React, Angular, Vue o cualquier otro framework que utilice la empresa.

Quizás también te den un desafío de código para evaluar tus habilidades en un área específica.

Hoy, miraremos las preguntas y problemas más comunes en entrevistas técnicas de front-end, enfocándonos en React y JavaScript.

Lo que los entrevistadores están buscando

Cuando te entrevistes para una posición como desarrollador de front-end, prepárate para discutir tus habilidades y experiencia en varios lenguajes de programación, herramientas y frameworks.

Los entrevistadores también querrán confirmar que tengas un entendimiento profundo de las últimas tendencias y tecnologías del desarrollo web.

Prepárate para hablar acerca de tus proyectos anteriores y como afrontaste la resolución de problemas de todo tipo.

Asegúrate de mostrar tus habilidades de resolución de problemas discutiendo como afrontaste las dificultades durante el proceso de desarrollo.

Por último, no olvides resaltar tus fortalezas.

Preguntas más frecuentes en entrevistas técnicas de Front-End

Los problemas de entrevistas técnicas de front-end son directos y comunes. Si has estado programando por al menos 6 meses, estarás familiarizado con la mayoría de los conceptos que se preguntan.

Una vez practiques las preguntas adecuadas con un enfoque basado en el tiempo, deberías poder pasar las entrevistas.

Echemos un vistazo a las preguntas más frecuentes.

Map, ForEach, Filter y Reduce

Las preguntas más frecuentes (generalmente al principio de las entrevistas) son acerca de métodos de arreglos (array methods). El entrevistador querrá evaluar que tan cómodo te sientes con manipulación de arreglos.

El método .map()

El método .map() itera sobre un arreglo, computa cualquier lógica que hayas escrito en el cuerpo del mapa, y retorna un NUEVO arreglo.

let arr = [
  { id: 1, age: 12, name: 'Manu' },
  { id: 2, age: 24, name: 'Quincy' },
  { id: 3, age: 22, name: 'Abbey' },
]

let names = arr.map((el) => el.name)
console.log(names)
// Resultado: [ 'Manu', 'Quincy', 'Abbey' ]

El método .forEach()

ForEach es similar a.map() pero NO retorna un arreglo.

let arr = [
  { id: 1, age: 12, name: 'Manu' },
  { id: 2, age: 24, name: 'Quincy' },
  { id: 3, age: 22, name: 'Abbey' },
]

arr.forEach((el) => el.age+= 10);
console.log(arr);

// Resultado: 22 32 44

El método .filter()

El método filtró (filter), como el nombre sugiere, ayuda a filtrar los valores dentro de un arreglo basándose en una condición Booleana.

Si la condición Booleana es verdadera, el resultado será retornado y agregado al final del arreglo. Si no, será omitido. Filtro también retorna un arreglo, tal como el método .map().

let arr = [
  { id: 1, age: 12, name: 'Manu' },
  { id: 2, age: 24, name: 'Quincy' },
  { id: 3, age: 22, name: 'Abbey' },
]

let tooYoung = arr.filter((el) => el.age <= 14);
console.log(tooYoung);

// Resultado: [ { id: 1, age: 12, name: 'Manu' } ]

El método .reduce()

En términos simples, el método .reduce() toma en cuenta un valor anterior, valor actual y un acumulador.

El tipo de retorno del método .reduce() es siempre un único valor. Es útil cuando quieres procesar todos los valores del arreglo y obtener algún tipo de valor resultado acumulado.

// Calcula la edad total de tres personas.
let arr = [
  { id: 1, age: 12, name: 'Manu' },
  { id: 2, age: 24, name: 'Quincy' },
  { id: 3, age: 22, name: 'Abbey' },
]

let totalAge = arr.reduce((acc, currentObj) => acc + currentObj.age, 0)
console.log(totalAge)

// Resultado: 57

Aquí, currentObj es el objeto sobre el cual se está iterando. Así mismo, el valor  acc value guarda el resultado, que luego es emitido en el arreglo totalAge.

Cómo implementar Polyfills

Otra importante pregunta de entrevistas es cómo implementar polyfills de los métodos map y filter.

Un polyfill es un fragmento de código (en términos de arquitectura web de JavaScript) que se utiliza para funcionalidades modernas en navegadores antiguos que no lo implementan nativamente.

Puesto de manera sencilla, un polyfill es una implementación customizada de funciones nativas de JavaScript. De alguna manera, se utiliza para crear tus propios métodos .map() o .filter().

Cómo usar el polyfill .map()

let data = [1, 2, 3, 4, 5];

Array.prototype.myMap = function (cb) {
  let arr = [];
  for (let i = 0; i < this.length; i++) {
    arr.push(cb(this[i], i, this));
  }
  return arr;
};
const mapLog = data.myMap((el) => el * 2);
console.log(mapLog);

El método myMap toma un callback que se ejecuta dentro del cuerpo de myMap. Tenemos básicamente un bucle for dentro del cuerpo myMap, que itera sobre this.length. Esto es simplemente el length del arreglo sobre el cual la función myMap es llamada.

Dado que la sintaxis de map() es arr.map(elementoActual, índice, arreglo), la función myMap() toma en cuenta exactamente eso.

De la misma manera, dado que map() retorna un nuevo arreglo, creamos un arreglo vacío y cargamos los resultados en él. Al finalizar, retornamos el nuevo arreglo.

Cómo usar el polyfill .filter()

let data = [1, 2, 3, 4, 5];

Array.prototype.myFilter = function (cb) {
  let arr = [];
  for (let i = 0; i < this.length; i++) {
    if (cb(this[i], i, this)) {
      arr.push(this[i]);
    }
  }
  return arr;
};
const filterLog = data.myFilter((el) => el < 4);
console.log(filterLog);

.filter() es muy similar a .map() en términos de implementación. Pero dado que filter filtra los resultados basándose en un valor booleano, tendremos una condición if() adicional para filtrar los resultados y condicionalmente cargamos dentro del arreglo.

¿Qué es el Debouncing?

Esta es una pregunta de entrevista famosa que tiene muchos usos e implementaciones en el mundo real.

Debouncing es un método para prevenir que una función se invoque con demasiada frecuencia, y que en lugar de esto espere cierto tiempo desde que fue llamada por última vez para invocarla de nuevo.

Piensa en Amazon. Cuando escribes cualquier cosa en la barra de búsqueda, cuando paras por AL MENOS 0.5 segundos, los resultados son descargados. Esto es exactamente lo que hace el debouncing.

Para implementar el debouncing, tomemos un ejemplo: generar un nombre de usuario basado en los aportes del usuario.

import "./styles.css";
let inputEle = document.getElementById("inputElement");
let username = document.getElementById("username");

let generateUsername = (e) => {
  username.innerHTML = e.target.value.split(" ").join("-");
};
let debounce = function (cb, delay) {
  let timer;
  return function () {
    let context = this;
    clearTimeout(timer);
    timer = setTimeout(() => {
      cb.apply(context, arguments);
    }, delay);
  };
};

inputEle.addEventListener("keyup", debounce(generateUsername, 300));

Aquí, estamos intentando crear un nombre de usuario customizado basado en aportes del usuario. Si el usuario comienza a escribir, no querremos crearlo inmediatamente, sino esperar 300 milisegundos antes de crear el nombre de usuario. Estamos intentando imitar un llamado al API, así que asumimos que el usuario escribe cualquier cosa, hace un llamado al API en el backend y descarga una respuesta.

La función debounce() toma dos valores , cb y delay . cb es la función callback que se ejecuta una vez el temporizador se queda sin tiempo.

Usamos setTimeout() para crear un temporizador, lo que significa que la función dentro del setTimeout se ejecutará luego de que haya pasado cierto tiempo.

El método apply se utiliza para llamar a la función callback con el objeto con el que se llamo inicialmente, aplicando argumentos y contexto al mismo.

¿Qué son las Clausuras (Closures)?

De acuerdo a la documentación de MDN para clausuras,

Una Clausura es una función que guarda referencias del estado adyacente (ámbito léxico). En otras palabras, una clausura permite acceder al ámbito de una función exterior desde una función interior. En JavaScript, las clausuras se crean cada vez que una función es creada.

Para simplificar esto, tomemos un ejemplo y entendamos cómo funcionan los cierres.

function start() {
  var name = "Manu"; // name es una variable local creada por start()
  function displayName() {
    // displayName() es la función interna, una 'clausura'
    alert(name); // variable de uso declarada en la función padre
  }
  displayName();
}
start(); // la caja de alerta "Manu" es mostrada

Aquí, una clausura se forma entre las funciones start() y displayName(). La función displayName() tiene acceso a la variable name presente en la función start().

En términos simples, la función interna conocerá sus alrededores (el ambiente léxico).

Ganchos (Hooks) de React

Las preguntas mas populares en las entrevistas técnicas para roles de front-end, cuando se trata de ganchos de React, son:

  1. useState()
  2. useReducer()
  3. useEffect()
  4. useRef()
  5. Hooks customizados y su implementación.

¿Cómo funciona el hook useState()?

Para manejar un estado dentro ed tu componente, el hook useState() es el mecanismo ideal.

Veamos un ejemplo para entender:

import { useState } from "react";
import "./styles.css";

export default function App() {
  const [title, setTitle] = useState("freeCodeCamp");
  const handleChange = () => {
    setTitle("FCC");
  };
  return (
    <div className="App">
      <h1>{title} useState</h1>
      <button onClick={handleChange}>Change Title</button>
    </div>
  );
}

Los métodos useState() arrojan dos valores, la variable estado y una función para cambiar dicha variable state.

En el fragment de código mostrado arriba, estamos creando un estado title para guardar el título de la página. El estado inicial indicado es freeCodeCamp.

Al presionar click, podemos utilizar el método setTitle() para cambiar la variable de estado a FCC.

El método useState() es el recurso más apropiado para controlar estados en un componente funcional.

¿Cómo funciona el hook useReducer()?

En términos simples, useReducer() es la manera "cool" de controlar un estado en tu aplicación. Es más estructurado y te ayuda a mantener un estado complejo en tu aplicación.

Veamos un ejemplo para entender como funciona el hook useReducer() :

import "./styles.css";
import { useReducer } from "react";

const initialState = { title: "freeCodeCamp", count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "change-title":
      return { ...state, title: "FCC" };
    case "increment-counter":
      return { ...state, count: state.count + 1 };
    default:
      throw new Error();
  }
}

export default function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      <div className="App">
        <h1>{state.title} CodeSandbox</h1>
        <button onClick={() => dispatch({ type: "change-title" })}>
          Change Title
        </button>
        <button onClick={() => dispatch({ type: "increment-counter" })}>
          Increment Counter
        </button>
      </div>
      <p style={{ textAlign: "center" }}>{state.count}</p>.
    </>
  );
}

El hook useReducer() toma dos parámetros, la función reducer y un valor initialState.

La función reducer es una implementación basada en switch-case que retorna el estado final que useReducer() utiliza internamente para proporcionar de vuelta al componente.

Los valores retornados de la función useReducer() son state y dispatch. El estado es el valor state que puede ser utilizado dentro del componente. En nuestro caso, el estado tiene dos valores: title y count. Estos pueden ser manipulados utilizando el método dispatch() que es retornado por el método useReducer().

En el caso de arriba, para cambiar el título, hemos escrito un caso de change-title dentro de la función reducer. Esto puede detonarse (trigger) con la ayuda dela función dispatch({ type: "change-title" }). Esto detonará el la función change-title y cambiará el estado del atributo title.

Lo mismo sucede para la parte count que se encuentra en la aplicación.

Como dije antes, es una manera "cool" de controlar un estado dentro de tu aplicación. ?

¿Cómo funciona el hook useEffect()?

Piénsalo de esta manera: si quieres tener un efecto secundario para una variable de estado que cambia, puedes usar el hook useEffect() para desencadenarlo.

Por ejemplo, digamos que el valor de entrada (valor de entrada) de tu campo "input" cambia, y quieres llamar a un API luego de que haya cambiado. Puedes escribir la lógica del API handle en el bloque useEffect().

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

export const App = () => {
    const [value, setValue] = useState('');
    useEffect(() => {
      console.log('value changed: ', value);
    }, [value])
	return <div>
        	<input type="text" name="username" value={value} onChange={(e) => setValue(e.target.value)} />
        </div>
}

Aquí, tenemos un campo de input que tiene un valor de estado value adjunto. Este valor cambiará cuando el usuario intente insertar cualquier cosa.

Una vez el valor haya sido actualizado y renderizado, el bloque useEffect() entrará en efecto y la declaración console se desencadenará, arrojando como resultado el último valor de estado que este allí.

Aquí, un buen valor de uso de useEffect() puede ser la implementación de llamadas a la API. Asumamos que quieres llamar a un API a través del valor en el campo input. La función bloque useEffect será la mejor manera de hacerlo.

Otra parte de esto es el arreglo de dependencia, que es el segundo argumento del hook useEffect(). En nuestro caso, mencionamos [value] como el segundo argumento.

Esto básicamente significa que CADA VEZ QUE value CAMBIA, la función dentro de useEffect se desencadenará. Si no se pasa nada en el  arreglo de dependencia, la función bloque se desencadena una vez.

¿Cómo funciona el hook useRef()?

El hook  useRef nos da la habilidad de mutar el DOM (pero esta no es la única implicación de useRef).

De acuerdo a los documentos:

useRef retorna un objeto ref mutable cuya propiedad .current es inicializada al argumento pasado (initialValue). El objeto retornado persistirá por la vida completa del componente.

En términos simples, utilizaremos useRef si queremos persistir el valor de algo durante la vida entera del componente. La implementación básica de useRef viene con elementos DOM. Veamos un ejemplo:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` apunta al elemento de texto montado
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Enfocar el input</button>
    </>
  );
}

Aquí, asignaremos una propiedad ref al bloque input. Esto se asociará a la referencia inputEl que creamos.

Ahora este elemento input puede ser manipulado de la manera que queramos. Podemos modificar el atributo style y estilizarlo, podemos tomar la propiedad value para ver si está siendo ayudada por el elemento input como valor, y así sucesivamente.

En el ejemplo anterior, cuando hacemos click en el botón, el input está enfocado y podemos inmediatamente comenzar a escribir. podemos hacer esto con la ayuda de inputEl.current.focus() – esencialmente el método focus() presente en el objeto current.

¿Qué son los hooks customizados?

Una de las preguntas que he observado más frecuentemente en rondas de entrevistas de front-end es crear un hook customizado para eventos del teclado.

Hemos visto varios hooks diferentes, pero el entrevistador te podría pedir que crees un hook por ti mismo. Esto podría ser retador para algunos pero con algo de práctica se vuelve mucho más sencillo.

Entendamos lo que es un Hook:

El uso básico de un hook customizado es extraer la lógica de una función a un componente propio.

Reflejando lo que pasará si tienes que escuchar la presión del botón enter dentro de cada uno de tus componentes. En lugar de escribir la lógica para  escuchar una y otra vez, podemos extraer la lógica a un componente por sí mismo y usarlo donde queramos (de la misma forma que useState() o useEffect()).

Hay algunas condiciones para que una función pueda llamarse un Hook:

  1. Siempre debe empezar con la palabra use.
  2. Podemos decidir que recibe como argumentos y que retorna (si es que retorna algo).
// Custom Hook: useAvailable
function useAvailabe(resource) {
  const [isAvailable, setIsAvailable] = useState(null);

  // ...

  return isAvailable;
}

// Uso:
  const isAvailable = useAvailable(cpu);

Aquí, sin importar cuantas veces llamemos useState y useEffects dentro del hook customizado, serán completamente independientes de la función en donde usemos el hook customizado.

Tomemos un ejemplo creando un hook customizado para almacenar valores locales.

Como crear un hook customizado – ejemplo de useLocalStorage

El hook customizado useLocalStorage es una manera de persistir datos dentro del almacenamiento local. Los valores get (obtener) y set (establecer) dentro del almacenamiento local utilizando pares key y value para que cuando el usuario vuelva a tu aplicación web, vean el mismo resultado que utilizaron anteriormente.

La implementación de más abajo es de un valor de etiqueta simple select que, una vez cambiado, persiste la data en el almacenamiento local.

useLocalStorage.js

// Usar el hook customizado Local Storage
import { useState } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    if (typeof window === 'undefined') {
      return initialValue;
    }
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });
  const setValue = (value) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.log(error);
    }
  };
  return [storedValue, setValue] as const;
}

export default useLocalStorage;

App.js

import * as React from 'react';
import './style.css';
import useLocalStorage from './useLocalStorate';

export default function App() {
  const [storedValue, setStoredValue] = useLocalStorage(
    'select-value',
    'light'
  );

  return (
    <div>
      <select
        className="select"
        value={storedValue}
        onChange={(e) => setStoredValue(e.target.value)}
      >
        <option value="dark">Dark</option>
        <option value="light">Light</option>
      </select>
      <p className="desc">
        Value from local storage: <span>{storedValue}</span>
      </p>
    </div>
  );
}

Aquí, el hook useLocalStorage toma dos parámetros, el nombre de key local para almacenar, y un valor default que tiene que estar allí.

El hook retorna dos valores: el valor de almacenamiento local del key que estamos utilizando y una manera de cambiar dicho valor de key dándonos un método setter. En este caso, el método setStoredValue.

En el archivo useLocalStorage.js, primero intentamos GET (OBTENER) el valor de almacenamiento local con el key utilizando el método localStorage.getItem(). Si esto existe, estableceremos el valor. Si es encontrado, haremos JSON.parse() del valor y lo retornaremos. De otra fosma, el valor initialValue que fur proporcionado se establecerá como el valor default.

La función setLocalStorage() toma en cuenta si el valor proporcionado es una función o un valor simple variable. Así mismo, se da a la tarea de establecer el valor local utilizando la función localStorage.setItem().

Como destacar como Desarrollador a través de la creación de proyectos paralelos

Lo que siempre me ha ayudado a destacarme son los proyectos paralelos que he construido.

En mi opinión, no tienes que construir 10 proyectos básicos. En cambio, prueba construir uno o dos proyectos verdaderamente buenos en donde implementes los conceptos de React/HTML/CSS/JavaScript y todo lo que has estado aprendiendo.

Asume que el entrevistador tiene 14 entrevistas a la semana y tiene que revisar los CV de 14 candidatos. Estarán más interesados en tu perfil porque has creado una app para acortar URLs que cobra $1 después de cada 1000 visitas al link en lugar de un clon de Amazon o Netflix.

De nuevo, no hay nada malo en crear clones y practicar tus habilidades. Pero siempre es bueno tener al menos un proyecto único que te ayude a destacar de la multitud.

Así mismo, crear proyectos paralelos te ayudará a mejorar como desarrollador. No es probable que sepas todo lo necesario cuando crees un proyectod esde cero. En el camino, tendrás que aprender muchas habilidades diferentes y mejorar en ellas.

Práctica, Práctica, Práctica.

Hay una frase famosa que dice:

(Todas las entrevistas son una entrevista de práctica hasta que obtienes tu primer trabajo de front-end. - William Shakespeare).

Y es verdad en gran medida.

Yo mismo he fallado cientas de veces anes de lograr obtener mi primer trabajo. Es la retroalimentación constante y las iteraciones que tienes que hacer para obtener lo que quieres.

En nuestro caso, obtener un trabajo de front-end es fácil cuando:

  • Tienes un conocimiento profundo de tus habilidades – React en este caso (además de HTML, CSS, y JS).
  • Tienes un conjunto de proyectos para mostrar, haciendo que destaques.
  • Estas dispuesto a dedicar el tiempo y esfuerzo para aprender más y retarte a ti mismo.
  • Lees el blog y preguntas prácticas de freeCodeCamp regularmente  (?)

Conclusión

Hay muchas preguntas para practicar para una ronda de preguntas practicas. El entrevistador puede preguntarte diferentes preguntas para probar tus habilidades.

Puedes usar Algochurn para practicar las preguntas de entrevista más populares de JavaScript, preguntas de entrevista de React, y preguntas de algoritmos realizadas comúnmente en entrevistas técnicas de front-end así como sus soluciones y enfoques.

Si tienes preguntas, por favor conecta conmigo a través de Twitter (@mannupaaji) y/o mi página web (manuarora.in)

¡Buena suerte y Happy Coding! ??