How to open and close an inline dialog in a react redux app

1.5k Views Asked by At

I have a working inline dialog using react state. The working code is below.

import React, { PureComponent } from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';
import Button from '@atlaskit/button';
import InlineDialog from '@atlaskit/inline-dialog';

const styles = {
  fontFamily: 'sans-serif',
  textAlign: 'center',
};

class ButtonActivatedDialog extends PureComponent {

 
  static propTypes = {
    content: PropTypes.node,
    position: PropTypes.string,
  }

  state = {
    isOpen: false,
  };

  handleClick = () => {
    this.setState({
      isOpen: !this.state.isOpen,
    });
  }

  handleOnClose = (data) => {
    this.setState({
      isOpen: data.isOpen,
    });
  }

  render() {
    return (
      <InlineDialog
        content={this.props.content}
        position={this.props.position}
        isOpen={this.state.isOpen}
        onClose={this.handleOnClose}
      >
        <Button
          onClick={this.handleClick}
          isSelected
        >
         The Button 
        </Button>
      </InlineDialog>
    );
  }
}

const App = () => (
    <ButtonActivatedDialog 
      content={
        <div>
          <h5>
            Displaying...
          </h5>
            <p>
            Here is the information I need to display.
            </p>
        </div>}
      position='bottom right'
    />
);

render(<App />, document.getElementById('root'));

I would like to have the same behavior with the button but using redux to maintain the state of the dialog.

After reading some material I believe I need to dispatch an action that will activate a reducer witch in turn will help me update the state of the dialog. However, I don't believe I fully understand how this should be put together.

Here is my work in progress but for some reason my codeSanbox does not like the format in which I'm creating the store.

mport React, { PureComponent } from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';
import Button from '@atlaskit/button';
import InlineDialog from '@atlaskit/inline-dialog';

import { connect, createStore } from 'react-redux'

const styles = {
  fontFamily: 'sans-serif',
  textAlign: 'center',
};


const mapStateToProps = state  => {
  return {
    isDialogOpen: false,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    toggleDialog: () => dispatch({
      type: 'TOGGLE_DIALOG'
    })
  }
}


// action:
const tottleDialog = 'TOGGLE_DIALOG';

//action creator 
const toggleDialog = (e) => ({
  type: 'TOGGLE_DIALOG',
   e,
})

class ButtonActivatedDialog extends PureComponent {

 
  static propTypes = {
    content: PropTypes.node,
    position: PropTypes.string,
  }

  state = {
    isOpen: false,
  };

  handleClick = () => {
    this.setState({
      isOpen: !this.state.isOpen,
    });
  }

  handleOnClose = (data) => {
    this.setState({
      isOpen: data.isOpen,
    });
  }

  render() {
    return (
      <InlineDialog
        content={this.props.content}
        position={this.props.position}
        isOpen={this.state.isOpen}
        onClose={this.handleOnClose}
      >
        <Button
          onClick={this.handleClick}
          isSelected
        >
         The Button 
        </Button>
      </InlineDialog>
    );
  }
}

const App = () => (
    <ButtonActivatedDialog 
      content={
        <div>
          <h5>
            Displaying...
          </h5>
            <p>
            Info here
            </p>
        </div>}
      position='bottom right'
    />
);

 const store = createStore(toggleDialog, {})



//need and action 
//need an action creator - a function that returns an action: 


//
// render(<App />, document.getElementById('root'));

 render(
   <Provider store={store}>
     <App />
   </Provider>, document.getElementById('root')
);

1

There are 1 best solutions below

2
On

Ok so first you have to set up your redux state. We usually do this according to the re-ducks pattern here: https://github.com/alexnm/re-ducks

This means you will create a directory for each "part" of your application. Each part then has a:

  • Operation: To perform tasks on the state (like open up or close your inline menu)
  • Selector: To get some value of the state (like is the inline menu open?)
  • Action: To perform an action on the state (like set isOpen to true/false)
  • Reducer: To apply an action to the state (like the one from above)
  • Type: Any type of state change. Any action has a type, and the type decides which part in the reducer is executed.

So in your example, I would create a state/inlineMenu folder and inside it the following files:

actions.js:

import types from './types';

const toggleState = {
  type: types.TOGGLE_STATE
};

export default {
  updateMenuState
}

operations.js:

import actions from './actions';

const toggleState = actions.toggleState;

export default {
  updateMenuState
};

reducers.js:

import types from './types';

const initialState = {
  isOpen: false // closed per default
};

const inlineMenuReducer = (state = initialState, action) => {
  switch (action.type) {
    case types.TOGGLE_STATE:
      return { ...state, isOpen: !state.isOpen }

    default:
      return state;
  }
};

export default inlineMenuReducer;

selectors.js:

const isMenuOpen = state => state.inlineMenu.isOpen;

export default {
  isMenuOpen
};

types.js:

const TOGGLE_STATE = 'inlineMenu/TOGGLE_STATE';

export default {
  TOGGLE_STATE
};

index.js:

import reducer from './reducers';

export { default as inlineMenuSelectors } from './selectors';
export { default as inlineMenuOperations } from './operations';

export default reducer;

You also have to set up the default provider. Your path to the isOpen property in the selectors should probably be adjusted.

Now you have your global redux state set up.

We need to get the data in it now to the view. We need to use redux' connect function for this, in which it will take the operations and selectors and map them to the default react props.

So your connected component could look like this:

import React, { PureComponent } from 'react';
import { render } from 'react-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Button from '@myKitkit/button';
import InlineDialog from '@mykit/inline-dialog';
import { inlineMenuOperations, inlineMenuOperations } from '../path/to/state/inlineMenu';

const styles = {
  fontFamily: 'sans-serif',
  textAlign: 'center',
};

class ButtonActivatedDialog extends PureComponent {

  static propTypes = {
    content: PropTypes.node,
    position: PropTypes.string,
    toggleState: PropTypes.func.isRequired
  }

  handleClick = () => {
    const { toggleState } = this.props;

    // This will dispatch the TOGGLE_STATE action
    toggleState();
  }

  handleOnClose = () => {
    const { toggleState } = this.props;

    // This will dispatch the TOGGLE_STATE action
    toggleState();
  }

  render() {
    return (
      <InlineDialog
        content={this.props.content}
        position={this.props.position}
        isOpen={this.props.isOpen}
        onClose={this.handleOnClose}
      >
        <Button
          onClick={this.handleClick}
          isSelected
        >
         The Button 
        </Button>
      </InlineDialog>
    );
  }
}

// You need to add the provider here, this is described in the redux documentation.
const App = () => (
    <ButtonActivatedDialog 
      content={
        <div>
          <h5>
            Displaying...
          </h5>
            <p>
            Here is the information I need to display.
            </p>
        </div>}
      position='bottom right'
    />
);

const mapStateToProps = state => ({
  isOpen: inlineMenuSelectors.isOpen(state);
});

const mapDispatchToProps = dispatch => ({
  toggleState: () => dispatch(inlineMenuOperations.toggleState())
}

const ConnectedApp = connect(mapStateToProps, mapDispatchToProps);

render(<ConnectedApp />, document.getElementById('root'));