Friday

React.js: dynamic sidebar example with typescript

I'm trying to implement a generic sidebar in my react project.
* I used prime react sidebar.
* It can be used multiple times in the page/application
* It loads different prime react buttons for every sidebar.
* For every sidebar its own onclick function is called when buttons has clicked.

Example screenshots:























project structure:




















index.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import App from './components/app';
import reducer from './reducer/reducer';

const rootReducer = combineReducers(
  { reducer: reducer}
 );
const store = createStore(rootReducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
app.tsx
import React from 'react';
import { connect } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import Home from './home/home';
import { SET_USER } from '../static/reducerActionTypes';

interface IDispatchProps {
    setUser: (user: string) => void;
}

type IProp = IDispatchProps;

class App extends React.PureComponent<IProp> {
    constructor(props: IProp) {
        super(props);
    }

    componentDidMount() {
        const { setUser } = this.props;
        setUser('Eda Merdan Biricik');
    }

    componentWillUnmount() {
        const { setUser } = this.props;
        setUser('');
    }

    render() {
        return (
            <Router>
                <div className="app">
                    <Home />
                </div>
            </Router>
        );
    }
}

const mapDispatchToProps = (dispatch: any) => ({
    setUser: (user: string) => {
        dispatch({
            user,
            type: SET_USER,
        });
    },
});

export default connect(null, mapDispatchToProps)(App);
home.tsx
import React from 'react';
import MyLeftSidebar from "../sidebar/myLeftSidebar";
import MyRightSidebar from "../sidebar/myRightSidebar";

export default () => (
    <>
        <div>
            <MyLeftSidebar />
        </div>
        <div>
             <MyRightSidebar />
        </div>
    </>
);
myLeftSidebar.tsx
import React from 'react';
import { connect } from 'react-redux';
import CommonSidebar from '../../commons/sidebar/commonSidebar';

const sidebarParameters = {
    fetchApiParameter: 'left',
    reducerKey: 'leftSidebarButtons',
    position: 'left',
};

interface IStateProps {
    user: string;
}

type IProp = IStateProps;

class MyLeftSidebar extends React.PureComponent<IProp> {
    constructor(props: IProp) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick(params: any) {
        alert('do want you want. my params: ' + params);
    }

    render() {
        const { user } = this.props;

        return (
            <>
                {user !== null &&
                <CommonSidebar
                    handleOnClick={this.handleClick}
                    sidebarParameters={sidebarParameters}
                />
                }
            </>
        );
    }
}

const mapStateToProps = ({ reducer }: any): IStateProps => {
    return {
        user: reducer.get('user', null),
    };
};

export default connect(mapStateToProps, null)(MyLeftSidebar);
myRightSidebar.tsx
import React from 'react';
import { connect } from 'react-redux';
import CommonSidebar from '../../commons/sidebar/commonSidebar';

const sidebarParameters = {
    fetchApiParameter: 'right',
    reducerKey: 'rightSidebarButtons',
    position: 'right',
};

interface IStateProps {
    user: string;
}

type IProp = IStateProps;

class MyRightSidebar extends React.PureComponent<IProp> {
    constructor(props: IProp) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick(params: any) {
        alert('do want you want. my params: ' + params);
    }

    render() {
        const { user } = this.props;

        return (
            <>
                {user !== null &&
                <CommonSidebar
                    handleOnClick={this.handleClick}
                    sidebarParameters={sidebarParameters}
                />
                }
            </>
        );
    }
}

const mapStateToProps = ({ reducer }: any): IStateProps => {
    return {
        user: reducer.get('user', null),
    };
};

export default connect(mapStateToProps, null)(MyRightSidebar);
reducerActionTypes.ts
export const SET_SIDEBAR_BUTTONS = 'SET_SIDEBAR_BUTTONS';
export const SET_USER = 'SET_USER';
reducer.ts
import I from 'immutable';
import { SET_SIDEBAR_BUTTONS, SET_USER } from '../static/reducerActionTypes';

interface Action {
    type: string;
    data: any;
    user: string;
}

export default (state: any = I.Map(), action: Action) => {
    switch (action.type) {

        case SET_USER:
            return state.set('user', action.user);

        case SET_SIDEBAR_BUTTONS:
            const { data: { key = '', buttons = I.Map() } = {} } = action;
            return state.set(key, buttons);

        default:
            return state;
    }
};
commonSidebar.tsx
import React from 'react';
import I from 'immutable';
import { connect } from 'react-redux';
import { Sidebar } from 'primereact/sidebar';
import  { CommonSidebarButton } from './commonSidebarButton';
import { getSidebarButtons } from '../../apis/sidebarAPI';
import { SET_SIDEBAR_BUTTONS } from '../../static/reducerActionTypes';
import 'primereact/resources/themes/nova-light/theme.css';
import 'primereact/resources/primereact.min.css';
import './commonSidebar.scss';

interface ISidebarParameters {
    fetchApiParameter: string;
    reducerKey: string;
    position: string;
}

interface IOwnProps {
    handleOnClick: (params: object) => void;
    sidebarParameters: ISidebarParameters;
}

interface IDispatchProps {
    setSidebarButtons: (buttons: any) => void;
}

interface IStateProps {
    sidebarButtons: any;
    user: string;
}

type IProp = IOwnProps & IDispatchProps & IStateProps;

class CommonSidebar extends React.PureComponent<IProp> {
    constructor(props: IProp) {
        super(props);
        this.renderButtons = this.renderButtons.bind(this);
    }

    async componentDidMount() {
        const { setSidebarButtons, sidebarParameters, user } = this.props;
        const buttons = await getSidebarButtons(sidebarParameters.fetchApiParameter, user);
        setSidebarButtons(buttons);
    }

    componentWillUnmount(): void {
        const { setSidebarButtons } = this.props;
        setSidebarButtons(I.Map());
    }

    renderButtons() {
        const { sidebarButtons } = this.props;
        const isSuccess = I.List.isList(sidebarButtons);
        const sortedSidebarButtons = isSuccess && sidebarButtons.sortBy((s: any) => s.get('orderNo'));

        return(
            <div >
                {isSuccess &&
                sortedSidebarButtons.map((button:any, idx: number) => (
                    <CommonSidebarButton
                        button={button}
                        handleOnClick={this.props.handleOnClick}
                        key={idx}
                    />
                ))
                }
            </div>
        );
    }

    render() {
        const { position } = this.props.sidebarParameters;

        return(
            <Sidebar
                className="common-sidebar"
                modal={false}
                visible
                onHide={() => null}
                showCloseIcon={false}
                baseZIndex={1}
                position={position}
            >
                {this.renderButtons()}
            </Sidebar>
        );
    }
}

const mapStateToProps = ({ reducer }: any, ownProps: IOwnProps): IStateProps => {
    return {
        sidebarButtons: reducer.getIn([ownProps.sidebarParameters.reducerKey, 'data'], I.List()),
        user: reducer.get('user', ''),
    };
};

const mapDispatchToProps = (dispatch: any, ownProps: IOwnProps): IDispatchProps => ({
    setSidebarButtons: (data: any) => {
        dispatch({
            data: { buttons: data, key: ownProps.sidebarParameters.reducerKey },
            type: SET_SIDEBAR_BUTTONS,
        });
    },
});

export default connect<IStateProps, IDispatchProps, IOwnProps>(
    mapStateToProps,
    mapDispatchToProps
)(CommonSidebar);
commonSidebarButton.tsx
import React from 'react';
import { Button } from 'primereact/button';
import 'primereact/resources/themes/nova-light/theme.css';
import 'primereact/resources/primereact.min.css';
import 'primeicons/primeicons.css';
import './commonSidebar.scss';

export const CommonSidebarButton = ({ button, handleOnClick }: any) => (
    <Button
        label={button.get('iconLabel')}
        icon={button.get('iconName')}
        onClick={() => handleOnClick(button.get('payload'))}
    />
);
commonSidebar.scss
body {
  div {
    .common-sidebar {
      background: #2c7f33;
      width: 70px;
    }
  }
}

body {
  .p-sidebar {
    border: 0px ;
    padding:0px;
  }

  .p-button {
    background-color: transparent;
    border-color: #2c7f33;
    color: #ffffff;
    font-size: 12px;
    padding: 10px 5px;
    margin-top: 10px;
  }

  .p-button.p-button-icon-only {
    width: 100%;
  }

  .pi {
    font-size: 3.00em;
  }

  .p-button.p-button-text-icon-left .p-button-text {
    padding: 0px;
  }

  .p-button.p-button-icon-only .p-button-text {
    display: none;
  }

  .p-button:enabled:active {
    background-color: #1b9e1c;
    border-color: #ffffff;
  }

  .p-button:enabled:focus {
    box-shadow: 0 0 0 0.1em red;
  }

  .p-button:enabled:hover {
    background-color: #1b9e1c;
    border-color: #ffffff;
  }
}

.p-button-icon-only .p-button-icon-left, .p-button-text-icon-left .p-button-icon-left {
  position: initial;
}

.p-button-text {
  color: #ffffff;
  font-size: 9px;
}

.p-button-icon-only .p-button-icon-left{
  margin-top: 0px;
  margin-left: 0px;
}
sidebarAPI.ts
import axios from 'axios';
import I from 'immutable';

export const getSidebarButtons = async(sidebarType: string, user: string) => {
    try {
        const { data } = await axios.get(
            `http://localhost.../getSidebarButtons?sidebarType=${sidebarType}&user=${user}`);
        return I.fromJS({ data });

    } catch (e) {
        return I.fromJS({
            data: {
                error: 'Error while fetching sidebar buttons',
            },
        });
    }
};
For this dynamic loading a rest api has called. It takes 2 parameter -sidebarType, user- to decide which parameters should return.
An example of  rest api result: (sidebarType is 'left')
[{
"id":"1","orderNo":"2","iconName":"pi pi-bell",
  "iconLabel":"Give Your Icon Label If You Want",
  "payload":{"params":{"componentPath":"/test"}}
},
{
"id":"2","orderNo":"3","iconName":"pi pi-check",
  "payload":{"params":{"componentPath":"/test"}}
},
{
"id":"3","orderNo":"1","iconName":"pi pi-times",
  "iconLabel":"Give Your Icon Label If You Want",
  "payload":{"params":{"componentPath":"/test"}}
}]
for sidebarType = 'right'
[{
"id":"1","orderNo":"2","iconName":"pi pi-bell",
  "iconLabel":"Give Your Icon Label If You Want",
  "payload":{"params":{"componentPath":"/test"}}
}]

No comments:

Post a Comment