Categories
React

How to Detect Clicks Outside a React Component Using Hooks?

Sometimes, we may want to detect clicks outside a React component for situations like when we want to create a popup menu that closes when we click outside.

In this article, we’ll look at how to detect clicks outside a React component with hooks.

Listening for Clicks on the Page

We can listen for clicks on the page to detect where we clicked on the page.

If we click outside the component, then we remove the React component from the DOM.

Checking for where we clicked on and running code accordingly is called event delegation.

For instance, we can write:

import React, { useEffect, useRef, useState } from "react";

const useComponentVisible = (initialIsVisible) => {
  const [isComponentVisible, setIsComponentVisible] = useState(
    initialIsVisible
  );
  const ref = useRef(null);

  const handleClickOutside = (event) => {
    if (ref.current && !ref.current.contains(event.target)) {
      setIsComponentVisible(false);
    }
  };

  useEffect(() => {
    document.addEventListener("click", handleClickOutside, true);
    return () => {
      document.removeEventListener("click", handleClickOutside, true);
    };
  });

return { ref, isComponentVisible, setIsComponentVisible };
};

  const DropDown = () => {
  const { ref, isComponentVisible } = useComponentVisible(true);

  return <div ref={ref}>{isComponentVisible && <p>drop down</p>}</div>;
};

export default function App() {
  return (
    <div>
      <DropDown />
    </div>
  );
}

We create the useComponentVisible hook to watch for clicks on the page.

We first define the isComponentVisible state to track when the component should be visible.

We set that to the initialVisible prop value.

Then we define a ref that’s assigned to the component we want to close when we click outside.

We do the element check in the handleClickOutside function.

This is whee we call contains to check if the element we clicked on is outside the component.

If !ref.current.contains(event.target) is true , then we know we clicked outside the component.

Next, we call document.addEventListener to listen for clicks on the whole page so we can do the check above.

We return a callback that calls removeEventListener to remove the event listener when we unmount the component.

Next, we define the DropDown component to that gets the ref and isComponentVisible state that we returned.

Then we assign the ref to the div so we use the click event listener to do the comparison.

Then finally, we add the DropDown component to App so we can see it.

Now when we click outside the text, the text should go away.

Conclusion

We can detect clicks outside a React component by assign a ref to the component we want to close when we click outside it.

Then we can attach an event handler to document and check whether we clicked outside the element that’s assigned the ref.

Categories
React

How to Update a React Component When Scrolling?

Sometimes, we want to update a React component when we’re scrolling.

In this article, we’ll look at how to update a React component when scrolling.

Add a Scroll Event Listener

We can add a scroll event listener into the useEffect hook to let us watch for scrolling events.

For instance, we can write:

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

export default function App() {
  const [scrollTop, setScrollTop] = useState();
  const [scrolling, setScrolling] = useState();

  useEffect(() => {
    const onScroll = (e) => {
      setScrollTop(e.target.documentElement.scrollTop);
      setScrolling(e.target.documentElement.scrollTop > scrollTop);
    };
    window.addEventListener("scroll", onScroll);

    return () => window.removeEventListener("scroll", onScroll);
  }, [scrollTop]);

  useEffect(() => {
    console.log(scrolling);
  }, [scrolling]);

  return (
    <div>
      {Array(50)
        .fill("foo")
        .map((a, i) => {
          return <p key={i}>{a}</p>;
        })}
    </div>
  );
}

We create the scrollTop and scrolling states with the useState hook.

Next, we call the useEffect hook with a callback that calls addEventListener to listen for scroll events triggered on the browser screen.

The onScroll function sets the scrollTop and scrolling state values.

We know we’re scrolling when e.target.documentElement.scrollTop > scrollTop is true .

We return a callback that calls removeEventListener to remove the scroll event listener when we unmount the component.

And we log the latest value of scrolling in the useEffect callback.

Finally, we return some content we can scroll through in the JSX.

Now when we scroll, we should see the latest value of scrolling logged.

Conclusion

If we want to update a React component when scrolling, we can watch for scroll events that are triggered with an event listener.

Then we can do what we want in the event listener when we’re scrolling in the event handler.

Categories
React

How to Get an Array of DOM Elements with React useRef hook?

Sometimes, we want to assign a ref to an array of rendered DOM elements in our React component.

In this article, we’ll look at how to get an array of DOM elements with the React useRef hook.

Create an Array of Refs and Store it in a Ref

We can create an array of refs and store it in an array of refs.

For instance, we can write:

import React, { useEffect, useRef } from "react";

const arr = ["left", "right"];
export default function App() {
  const refs = useRef(arr.map(() => React.createRef()));

  useEffect(() => {
    refs.current[0].current.focus();
  }, []);

  return (
    <div>
      {arr.map((el, i) => (
        <p key={i}>
          <input ref={refs.current[i]} value={el} onChange={() => {}} />
        </p>
      ))}
    </div>
  );
}

We call the useRef hook with the arr.map callback to map arr to an array of refs that we create with React.createRef .

Then we have a useEffect callback that calls refs.current[0].current.focus() to focus on the first element when App mounts.

Finally, we assign the refs in the map callback by assigning the ref property’s value to refs.current[i] .

Now when we mount the component, we should see the first input element having focus.

Alternatively, we can use the useMemo hook to cache the refs by writing:

import React, { useEffect, useMemo } from "react";

const arr = ["left", "right"];
export default function App() {
  const refs = useMemo(() => arr.map(() => React.createRef()), []);

  useEffect(() => {
    refs[0].current.focus();
  }, []);

  return (
    <div>
      {arr.map((el, i) => (
        <p key={i}>
          <input ref={refs[i]} value={el} onChange={() => {}} />
        </p>
      ))}
    </div>
  );
}

We create the refs the same way, but we wrap the callback with useMemo to cache the created refs.

Then we assign the refs and use them as we did in the previous example.

Conclusion

We can create an array of refs with the createRef function and the useMemo or useRef hook to store them.

Categories
React

How to Share States Between React Components?

Sharing states between components is something we’ve to do a lot with React.

In this article, we’ll look at ways we can use to share states between components in React.

Lift State Up

If 2 child components have the same parent component, then we can life the state we want to share from the child component to the parent.

For instance, we can write:

import React, { useState } from "react";

const DescendantA = ({ count, onCountChange }) => {
  return (
    <button onClick={() => onCountChange((count) => count + 1)}>
      Click me {count}
    </button>
  );
};

const DescendantB = ({ count, onCountChange }) => {
  return (
    <button onClick={() => onCountChange((count) => count + 1)}>
      Click me {count}
    </button>
  );
};

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <DescendantA count={count} onCountChange={setCount} />
      <DescendantB count={count} onCountChange={setCount} />
    </>
  );
}

We have the DescendantA and DescendantB components which both have App as their common ancestor.

So to share a state, we put the state in App .

We have the count state which is defined in App .

And we pass in count to both the DescendantA and DescendantB components.

Also, we pass in onCountChange to DescendantA and DescendantB .

And in DescendantA and DescendantB , we call onCountChange and display the count value.

Context API

Another way to share states between 2 components is to use the Context API.

The Context API lets us share states between any component in the context provider.

For instance, we can write:

import React, { useContext, useState } from "react";

const CountContext = React.createContext("count");

const DescendantA = () => {
  const { count, setCount } = useContext(CountContext);

  return (
    <button onClick={() => setCount((c) => c + 1)}>Click me {count}</button>
  );
};

const DescendantB = () => {
  const { count, setCount } = useContext(CountContext);

  return (
    <button onClick={() => setCount((c) => c + 1)}>Click me {count}</button>
  );
};

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <CountContext.Provider value={{ count, setCount }}>
      <DescendantA />
      <DescendantB />
    </CountContext.Provider>
  );
}

We call React.createContext to create the context.

Then we define DescendantA and DescendantB which calls useContext to get the context.

The value prop of the CountContext.Provider is what’s returned by useContext .

So we can get the count and setCount getter and setter from the returned value.

And in each component, we have the button to set the count .

In App , we wrap CountContext.Provider around DescendantA and DescendantB so we can use the context in both components.

State Management Solution

Another way to share data between multiple components is to use a state management solution.

A popular state management solution for React apps is Redux and React-Redux.

To use it, we can write:

index.js

import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import App from "./App";

const rootReducer = (state = 0, action) => {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    default:
      return state;
  }
};

const store = createStore(rootReducer);

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>,
  rootElement
);

App.js

import React from "react";
import { useDispatch, useSelector } from "react-redux";

const DescendantA = () => {
  const dispatch = useDispatch();
  const count = useSelector((state) => state);

  return (
    <button onClick={() => dispatch({ type: "INCREMENT" })}>
      Click me {count}
    </button>
  );
};

const DescendantB = () => {
  const dispatch = useDispatch();
  const count = useSelector((state) => state);

  return (
    <button onClick={() => dispatch({ type: "INCREMENT" })}>
      Click me {count}
    </button>
  );
};

export default function App() {
  return (
    <>
      <DescendantA />
      <DescendantB />
    </>
  );
}

In index.js , we have the rootReducer with that takes the state value and return the latest value of the store based on the action type we dispatch.

We pass in the rootReducer to createStore to create a Redux store.

Then we pass in the store to the React-Redux Provider component to let us use the store in any component inside the Provider .

In App.js , we have DescendantA and DescendantB which calls the useDispatch hook to get the dispatch function.

dispatch lets us dispatch an action to the store.

Also, we call the useSelector hook with a callback to return the value in the callback, which is the latest return value of rootReducer .

When we click the button, we call dispatch with the type set to 'INCREMENT' to commit the action with type 'INCREMENT' .

Now we should see count updated.

Conclusion

There’re several ways we can use to let us share states between components.

Categories
React

How to Send Data from a Child React Component to the Parent Component?

Sometimes in our React app, we’ve to send data from a child component to its parent component.

In this article, we’ll look at how to send data from a child React component to its parent.

Pass a Function to the Child Component

To pass data from a parent component to the child, we can pass a function to a child component as a prop.

For instance, we can write:

import React, { useState } from "react";

const Counter = ({ parentCallback }) => {
  const [count, setCount] = useState(0);

  return (
    <button
      onClick={() => {
        const newValue = count + 1;
        setCount(newValue);
        parentCallback(newValue);
      }}
    >
      Click me {count}
    </button>
  );
};

export default function App() {
  const callback = (val) => {
    console.log(val);
  };

  return (
    <div>
      <Counter parentCallback={callback} />
    </div>
  );
}

We have the Counter component that takes the parentCallback prop.

In the component, we have the count state created from the useState hook.

Then we have a button with the onClick callback that calls the parentCallback with newValue .

In the App component, we have the callback function that we pass into the Counter component as the value of the parentCallback prop.

Now when we click on the Click me button, we should see the console.log function run with val being the value we pass into parentCallback in Counter .

Conclusion

We can send data from a child React component to the parent component by passing in a function from the parent to the child.

Then in the child component, we can call it with the arguments we want to pass the argument values back to the parent component.