markaguir
markaguir

Reputation: 293

How to create shared-singleton components across all the platform?

I'm thinking on creating a React component called LoadingMask, where I can show or not (depending on the state) a loading mask from any component. The idea is showing it before an ajax call, and hiding it after I receive the data.

I don't want to display two masks at the same time, so if one component is making a request, and another one creates another request, I want to add 1 to my "MaskCounter", and substract one when the Request is finished. If the counter is 0, I need to hide the LoadingMask.

I order to do this, I think I need to create a "Singleton" component, that I can share through the whole platform, so there's only exist one LoadingMask. I also don't think it's nice to send the events to hide/show the mask to all components.

Any ideas?

Upvotes: 21

Views: 26303

Answers (2)

johndpope
johndpope

Reputation: 5257

I found this library use-between to be simple, powerful and useful. It removes complexity of redux for sharing data between within functional components.

import React, { useState, useCallback } from 'react';
import { useBetween } from 'use-between';

Context/Session.ts

  export const useShareableState = () => {
      const [count, setCount] = useState(0);
      const inc = useCallback(() => setCount(c => c + 1), []);
      const dec = useCallback(() => setCount(c => c - 1), []);
      return {
        count,
        inc,
        dec
      };
    };

App.tsx

import { useBetween } from 'use-between';
import { useShareableState } from './src/Context/Session'
const useSharedCounter = () => useBetween(useShareableState);

const Count = () => {
  const { count } = useSharedCounter();
  return <p>{count}</p>;
};

const Buttons = () => {
  const { inc, dec } = useSharedCounter();
  return (
    <>
      <button onClick={inc}>+</button>
      <button onClick={dec}>-</button>
    </>
  );
};

const App = () => (
  <>
    <Count />
    <Buttons />
    <Count />
    <Buttons />
  </>
);

export default App;

Upvotes: 2

soywod
soywod

Reputation: 4520

To share data between components, you can :

  • Use a lib like Redux, and keep in shared store your mask loader status
  • Use the React context api from your root component, and share loader status to all childrens. See an example below :

class Application extends React.Component {
  constructor() {
    super();
    
    this.state = {
      nbTasks: 0
    };
    
    this.addTask = this.addTask.bind(this);
    this.removeTask = this.removeTask.bind(this);
    this.isLoading = this.isLoading.bind(this);
  }
  
  addTask() {
    this.setState(prevState => ({
      nbTasks: prevState.nbTasks + 1
    }));
  }
  
  removeTask() {
    this.setState(prevState => ({
      nbTasks: prevState.nbTasks - 1
    }));
  }
  
  isLoading() {
    return this.state.nbTasks > 0;
  }
  
  getChildContext() {
    return {
      addTask: this.addTask,
      removeTask: this.removeTask,
      isLoading: this.isLoading
    };
  }
  
  render() {
    return (
      <div>
        <ComponentX />
        <ComponentY />
        <LoadingMask />
      </div>
    );
  }
}

Application.childContextTypes = {
  addTask: PropTypes.func,
  removeTask: PropTypes.func,
  isLoading: PropTypes.func
};

const LoadingMask = (props, context) => (
  context.isLoading()
    ? <div>LOADING ...</div>
    : null
);

LoadingMask.contextTypes = {
  isLoading: PropTypes.func
};

class ComponentX extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      message: 'Processing ...'
    };
  }
  
  componentDidMount() {
    this.context.addTask();
    
    setTimeout(() => {
      this.setState({
        message: 'ComponentX ready !'
      });
      
      this.context.removeTask();
    }, 3500);
  }
  
  render() {
    return (
      <div>
        <button disabled>{this.state.message}</button>
      </div>
    );
  }
}

ComponentX.contextTypes = {
  addTask: PropTypes.func,
  removeTask: PropTypes.func
};

class ComponentY extends React.Component {
  constructor(props, context) {
    super(props, context);
    
    this.state = {
      message: 'Processing ...'
    };
  }
  
  componentDidMount() {
    this.context.addTask();
    
    setTimeout(() => {
      this.setState({
        message: 'ComponentY ready !'
      });
      
      this.context.removeTask();
    }, 6000);
  }
  
  render() {
    return (
      <div>
        <button disabled>{this.state.message}</button>
      </div>
    );
  }
}

ComponentY.contextTypes = {
  addTask: PropTypes.func,
  removeTask: PropTypes.func
};

ReactDOM.render(
  <Application />,
  document.getElementById('app')
);
<script src="https://unpkg.com/prop-types/prop-types.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>

<div id="app"></app>

Upvotes: 3

Related Questions