reactjsreact-hooksreact-custom-hooks

How can i expose my state and state setter method outside without passing props or refs


I was using a library known as Swiper js. In there i saw something which i am unable to understand. Swiper is giving me a hook which returns all the data related to its state like activeSlide, isLast, isStart and many more without explicitly passing ref to that component.

// import Swiper core and required modules
import { Navigation, Pagination, Scrollbar, A11y } from 'swiper/modules';

import { Swiper, SwiperSlide } from 'swiper/react';

// Import Swiper styles
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import 'swiper/css/scrollbar';

export default () => {
  return (
    <Swiper
      modules={[Navigation, Pagination, Scrollbar, A11y]}
      spaceBetween={50}
      slidesPerView={3}
      navigation
      pagination={{ clickable: true }}
      scrollbar={{ draggable: true }}
      onSwiper={(swiper) => console.log(swiper)}
      onSlideChange={() => console.log('slide change')}
    >
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <SwiperSlide>Slide 3</SwiperSlide>
      <SwiperSlide>Slide 4</SwiperSlide>
      ...
    </Swiper>
  );
};
import { useSwiper } from 'swiper/react';

export default function SlideNextButton() {
  const swiper = useSwiper();

  return (
    <button onClick={() => swiper.slideNext()}>Slide to the next slide</button>
  );
}

As you can see above useSwiper returns an object swiper which has a method slideNext . I want to create a component in a similar manner where i can access the state and update the state from outside without passing any props or refs just how swiper.js is doing.

I have tried to use context and importing the context but it didn't work.


Solution

  • You can create your own context which you can use in all child components.

    Create a context

    // MySwiper.js
    import { createContext, useContext, useState, useCallback } from "react";
    
    const MySwiperContext = createContext();
    
    export const MySwiper = ({ children }) => {
      const [value, setMyValue] = useState(10);
    
      const myCustomMethod = useCallback(() => {
        // Do things
        console.log("my custom method");
      }, []);
    
      const overrideValue = useCallback((newValue) => {
        setMyValue(newValue);
      }, []);
    
      return (
        <MySwiperContext.Provider
          // Things you want to expose to your custom hook
          value={{
            value,
            myCustomMethod,
            overrideValue,
          }}
        >
          {children}
        </MySwiperContext.Provider>
      );
    };
    
    // Expose the context with a custom hook
    export const useMySwiper = () => useContext(MySwiperContext);
    

    Use the context

    // App.js
    import "./styles.css";
    import { MySwiper, useMySwiper } from "./MySwiper";
    
    // Needs to be a child of the `MySwiper` component
    // for the `useMySwiper` hook to work
    const MySwiperChildComponent = () => {
      const { value, overrideValue, myCustomMethod } = useMySwiper();
    
      return (
        <div>
          value from mySwiper {value}
          <br />
          <button onClick={() => overrideValue(value + 10)}>add 10</button>
          <br />
          <button onClick={() => myCustomMethod()}>trigger custom method</button>
        </div>
      );
    };
    
    export default function App() {
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <h2>Start editing to see some magic happen test!</h2>
          <MySwiper>
            <MySwiperChildComponent />
          </MySwiper>
        </div>
      );
    }
    

    Check out this code sandbox for a working example