ousmane784
ousmane784

Reputation: 446

React preserve state in custom hook

Is there a way to preserve the state when calling a custom react hook from separate components? I made a simple example here but I was thinking of using the same logic in my app to store a fetch call to an api and use the data in different places in my app without calling the api more than once.

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

function useCounter(intialCount = 0){
  const [count, setCount] = useState(0);
  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });
  
  return [count, setCount];
}

const AnotherComponent = () => {
  const [count] = useCounter();


  return <div>{count}</div>
}

export default function App() {
  // Call custom hook `useCounter` to reuse Counter logic
  const [count, setCount] = useCounter(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>

      <button onClick={() => setCount(count - 1)}>
        Decrement
      </button>

      <AnotherComponent />
    </div>
  );
}

In this example, is it possible for AnotherComponent to have the same count as App. I dont want to use context either in my app component for performance reasons because the data I would get from an api is a large list.

Upvotes: 2

Views: 5289

Answers (3)

nolan
nolan

Reputation: 467

Use SWR or React Query, they will automatically cache your data from the server and stop duplicate callings.

Upvotes: -1

Steve Holgado
Steve Holgado

Reputation: 12071

If you don't want to use context then it is possible to achieve what you want using some shared state outside of the hook:

let sharedCount = 0;

function useCounter(initialCount) {
  const [count, _setCount] = useState(sharedCount);

  // On first run, set initial count
  useEffect(() => {
    if (initialCount !== undefined) {
      sharedCount = initialCount;
    }
  }, []);

  // If shared count is changed by other hook instances, update internal count
  useEffect(() => {
    _setCount(sharedCount);
  }, [sharedCount]);

  const setCount = (value) => {
    sharedCount = value; // Update shared count for use by other hook instances
    _setCount(value);    // Update internal count
  };
  
  return [count, setCount];
}

Upvotes: 4

Dupocas
Dupocas

Reputation: 21297

Yeap, you can easily achieve your goal by setting a Context to provide a centralized state and a custom hook to access that state in a modular way.

Let's assume that a want to share foo with my entire application.

  • Create a context provider

FooContext.js

import { createContext, useState } from 'react'

export const FooContext = createContext()

const { Provider } = FooContext

export const FooProvider = ({ children }) =>{
    const [foo, setFoo] = useState('bar')

    return(
        <Provider value={{foo, setFoo}}>
            { children }
        </Provider>
    )
}
  • Now wrap you application (or the section you want to be aware of foo with FooProvider
 <FooProvider>
    <RestOfApp/>
 </FooPrivider>
  • Now just create a custom hook to make the value and setter of foo easily accessible

useFoo.js

import { useContext } from 'react' 
import { FooContext } from './FooContext.js' 

export const useFoo = () =>{
    const { foo, setFoo } = useContext(FooContext)
    return [foo, setFoo]
}
  • To use it in a component (that's under FooProvider)
import { useFoo } from './useFoo'

const ComponentWithFoo = () =>{
    const [foo, setFoo] = useFoo()

    const changeFoo = value => setFoo(value)

    return <p> { foo } </p>
}

Notice that besides hooks you could also use HOCs or render props.

Upvotes: 0

Related Questions