Water Man
Water Man

Reputation: 323

React share all methods inside a function component that might interact with the state between another function component

I'm trying to share ALL the methods in a function component between another function component, even if the method interacts with the state, here's what I've got working so far:

// App.js
import React, { useRef } from 'react';
import FooColor from './components/FooColor.js';
import BarColor from './components/BarColor.js';

const App = () => {
    const fooChangeColorToRed = useRef(() => {});
    const fooChangeColorToGreen = useRef(() => {});
    const fooChangeColorToBlue = useRef(() => {});

    const barChangeColorToRed = useRef(() => {});
    const barChangeColorToGreen = useRef(() => {});
    const barChangeColorToBlue = useRef(() => {});

    return (
        <div>
            <FooColor
                fooChangeColorToRed={fooChangeColorToRed}
                fooChangeColorToBlue={fooChangeColorToBlue}
                fooChangeColorToGreen={fooChangeColorToGreen}
                barChangeColorToRed={barChangeColorToRed}
                barChangeColorToGreen={barChangeColorToGreen}
                barChangeColorToBlue={barChangeColorToBlue}
            />
            <BarColor
                fooChangeColorToRed={fooChangeColorToRed}
                fooChangeColorToBlue={fooChangeColorToBlue}
                fooChangeColorToGreen={fooChangeColorToGreen}
                barChangeColorToRed={barChangeColorToRed}
                barChangeColorToGreen={barChangeColorToGreen}
                barChangeColorToBlue={barChangeColorToBlue}
            />
        </div>
    );
};

export default App;
// FooColor.js
import React, { useState } from 'react';

const FooColor = (props) => {
    const [state, setState] = useState({
        color: 'No color picked yet',
    });

    const changeColorToRed = () => {
        setState({
            color: 'red',
        });
    };

    const changeColorToGreen = () => {
        setState({
            color: 'green',
        });
    };

    const changeColorToBlue = () => {
        setState({
            color: 'blue',
        });
    };

    props.fooChangeColorToRed.current = changeColorToRed;
    props.fooChangeColorToGreen.current = changeColorToGreen;
    props.fooChangeColorToBlue.current = changeColorToBlue;

    return (
        <div>
            <div style={{ fontSize: 30 }}>Foo</div>
            Foo Color: {state.color}
            <br />
            <button onClick={props.barChangeColorToRed.current}>Change bar color to red</button>
            <button onClick={props.barChangeColorToGreen.current}>Change bar color to green</button>
            <button onClick={props.barChangeColorToBlue.current}>Change bar color to blue</button>
        </div>
    );
};

export default FooColor;
// BarColor.js
import React, { useState } from 'react';

const BarColor = (props) => {
    const [state, setState] = useState({
        color: 'No color picked yet',
    });

    const changeColorToRed = () => {
        console.log('hi');
        setState({
            color: 'red',
        });
    };

    const changeColorToGreen = () => {
        setState({
            color: 'green',
        });
    };

    const changeColorToBlue = () => {
        setState({
            color: 'blue',
        });
    };

    props.barChangeColorToRed.current = changeColorToRed;
    props.barChangeColorToGreen.current = changeColorToGreen;
    props.barChangeColorToBlue.current = changeColorToBlue;

    return (
        <div>
            <div style={{ fontSize: 30 }}>Bar</div>
            Bar Color: {state.color}
            <br />
            <button onClick={props.fooChangeColorToRed.current}>Change foo color to red</button>
            <button onClick={props.fooChangeColorToGreen.current}>Change foo color to green</button>
            <button onClick={props.fooChangeColorToBlue.current}>Change foo color to blue</button>
        </div>
    );
};

export default BarColor;

This has a few issues, sharing more than 10 methods or so becomes very unreadable. Also, the "BarColor" component initially wouldn't be able to call methods in the "FooColor" component until a method is called in the "FooColor" component. This might be because of the order they are placed in inside of the App component

Is there a more efficient and readable way to do the exact same thing?

Upvotes: 0

Views: 583

Answers (1)

tomleb
tomleb

Reputation: 2525

Why not create a custom hook with as much shared logic as possible that returns all of your data and needed methods?

For example:

/* color.jsx */

export function useColor() {

  // Our shared state:
  const [state, setState] = useState({
    color: 'No color picked yet',
  });

  // Our methods:
  const changeColorToRed = () => {
    setState({
      color: 'red',
    });
  };

  const changeColorToGreen = () => {
    setState({
      color: 'green',
    });
  };

  const changeColorToBlue = () => {
    setState({
      color: 'blue',
    });
  };

  return {
    state,
    changeColorToRed,
    changeColorToGreen,
    changeColorToBlue,
  }
}

So basically, we'll have this shared hook containing a shared state and methods,
and we'll return everything we want to use in the relevant comps.

For example:

/* FooColor.jsx */

const FooColor = () => {
  const { state, changeColorToRed, changeColorToGreen, changeColorToBlue } = useColor();

  return (
    <div>
      <div style={{ fontSize: 30 }}>Foo</div>
      Foo Color: {state.color}
      <br />
      <button onClick={changeColorToRed}>Change foo color to red</button>
      <button onClick={changeColorToGreen}>Change foo color to green</button>
      <button onClick={changeColorToBlue}>Change foo color to blue</button>
    </div>
  );
};

Then you can import and use it in the exact same way in BarColor.

In this example we get a clean result without the need for useRef or even props of any kind in this case.

For more info, you can check out the documentation about custom hooks.

Also, the use-case here is a little vague so I hope that I understood your needs correctly.
Let me know if you need some more help with this.


Update:

If we need 2 separate states for each component color,
we can simply manage separate states in the shared hook.

Example:

  // Either like this:
  const [fooColor, setFooColor] = useState('No color picked yet');
  const [barColor, setBarColor] = useState('No color picked yet');

  // Or like this (my personal preference):
  const [state, setState] = useState({
    fooColor: 'No color picked yet',
    barColor: 'No color picked yet',
  });

  // Then we can add a parameter in order to avoid function duplication:
  const changeColorToRed = (comp) => {
    switch (comp) {
      case 'foo':
        setState({
          // Spread the existing contents of the state (`barColor` in this case):
          ...state,
          fooColor: 'red',
        });

      case 'bar':
        setState({
          ...state,
          barColor: 'red',
        });

      default:
        console.error('Non-existent comp passed');
    }
  };

Upvotes: 1

Related Questions