3
\$\begingroup\$

I have some (minimal) experience with the functional programming paradigm and I'm new to React.js and reactive programming. I'm trying to get a flavour for the React.js philosophy and I keep seeing references to functional programming, which has gotten me very excited.

I've been experimenting with some layouts that use curries and avoid use of JSX in favour of pure JavaScript and curry functions. Below is a sample of a Navbar I have written using Bootstrap. Please forgive violations of best practice, I'm sure there are several, but would really appreciate feedback on the style, functionality and maintainability. Would also appreciate any references for best-practices.

The navbar is written in Typescript. It's a stateless functional component, it works and is designed to only render links based on whether or not the user has an active session. It also renders views when a link is clicked. Many of the views have not been written yet so I just used a place holder 'Test'.

import { Component, ComponentElement, createElement as e, DOMElement, SFC } from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { connect, Provider } from "react-redux";
import { setElemChildrenCurry, elements } from "../../libs/elements";
import { endSession } from "../../reducers/session-management/session.actions";
import { store } from "../../reducers/session-management/session.store";
import Login from "../login/login";
import Register from "../register/register";
import Test from "../test/test";

const a = elements.a;
const button = elements.button;
const div = elements.div;
const li = elements.li;
const nav = elements.nav;
const span = elements.span;
const ul = elements.ul;

interface INavbarProps {
    sessionActive: boolean;
}

interface INavbarLinkData {
    alwaysShow: boolean,
    linkId: string,
    requiresSession: boolean,
    // Some links don't have a view.
    view?: ComponentElement<any, any>,
    viewLink: DOMElement<any, Element> | ComponentElement<any, any>,
}

const NAVBAR_BOOTSTRAP_CLASSES = "navbar navbar-inverse bg-primary navbar-toggleable-md navbar-light bg-faded";
const NAVBAR_ATTRIBUTES = {
    id: "cos-navbar",
    className: NAVBAR_BOOTSTRAP_CLASSES
};
const NAVBAR = setElemChildrenCurry(nav, NAVBAR_ATTRIBUTES);

const NAVBAR_COLLAPSE_BUTTON_ATTRIBUTES = {
    "aria-controls": "navbarSupportedContent",
    "aria-expanded": "false",
    "aria-label": "Toggle navigation",
    className: "navbar-toggler navbar-toggler-right",
    "data-target": "#cos-navbar-links",
    "data-toggle": "collapse",
    type: "button",
}

const NAVBAR_COLLAPSE_BUTTON_LEAF = button(NAVBAR_COLLAPSE_BUTTON_ATTRIBUTES,
    span({ className: "navbar-toggler-icon" }));

const NAVBAR_BRAND_BUTTON_LEAF = a({ className: "navbar-brand", href: "#" }, "CoS");

const NAVBAR_COLLAPSING_DIV = setElemChildrenCurry(div, { className: "collapse navbar-collapse", id: "cos-navbar-links" });

const NAVBAR_LINKS_UL = setElemChildrenCurry(ul, { className: "navbar-nav mr-auto" });

const NAVBAR_LINK_LIST_ITEM = setElemChildrenCurry(li, { className: "nav-item active" });

// Need to connect the logout button so that it can send redux signals to
// have the Navbar re-rendered correctly.
const CONNECTED_LOGOUT_LINK = e(connect()(({ dispatch }) => {
    return NAVBAR_LINK_LIST_ITEM(
        a({
            className: "nav-link",
            href: "#",
            id: "cos-logout",
            onClick: () => { dispatch(endSession()); }
        }, "Logout"))
}));

/**
 * Wraps individual links in Navbar link tags.
 * 
 * @param id - The CSS ID to pass to the link to be insrted into the list item.
 * @param displayName - The string to display on the link to the user.
 * 
 * @returns - Curry function to generate the appropriate element.
 */
const createLinkElement: (id: string, displayName: string) => DOMElement<any, Element> =
    (id: string, displayName: string) => {
        return NAVBAR_LINK_LIST_ITEM(
            a({
                className: "nav-link",
                href: "#",
                id,
                onClick: () => { renderView(id); },
            }, displayName)
        );
    }

const LINKS: INavbarLinkData[] = [
    {
        alwaysShow: true,
        linkId: "cos-navbar-home",
        requiresSession: false,
        view: e(Test, null, null),
        viewLink: createLinkElement("cos-navbar-home", "Home"),
    },
    {
        alwaysShow: true,
        linkId: "cos-navbar-locator",
        requiresSession: false,
        view: e(Test, null, null),
        viewLink: createLinkElement("cos-navbar-locator", "Locator"),
    },
    {
        alwaysShow: false,
        linkId: "cos-navbar-rate",
        requiresSession: true,
        view: e(Test, null, null),
        viewLink: createLinkElement("cos-navbar-rate", "Rate"),
    },
    {
        alwaysShow: false,
        linkId: "cos-navbar-profile",
        requiresSession: true,
        view: e(Test, null, null),
        viewLink: createLinkElement("cos-navbar-profile", "Profile"),
    },
    {
        alwaysShow: false,
        linkId: "cos-navbar-register",
        requiresSession: false,
        view: e(Register, null, null),
        viewLink: createLinkElement("cos-navbar-register", "Register"),
    },
    {
        alwaysShow: false,
        linkId: "cos-navbar-login",
        requiresSession: false,
        view: e(Login, null, null),
        viewLink: createLinkElement("cos-navbar-login", "Login"),
    },
    {
        alwaysShow: false,
        linkId: "cos-navbar-logout",
        requiresSession: true,
        viewLink: CONNECTED_LOGOUT_LINK,
    },
];

/**
 * Renders a view based on the link clicked by a user.
 * 
 * @param linkId - The CSS ID of the link clicked by a user.
 */
const renderView = (linkId: string): void => {
    LINKS.map((link: INavbarLinkData) => {
        if (linkId === link.linkId) {
            unmountComponentAtNode(document.getElementById("cos-outlet"));
            render(e(Provider, { store }, link.view),
                document.getElementById("cos-outlet"));
        }
    });
}

/**
 * Renders standard collapsing Navbar. Includes jQuery to collapse the Navbar
 * after a link is selected.
 * 
 * @prop sessionActive - Holds boolean for whether or not a user session is
 *     currently active.
 */
const Navbar: SFC<INavbarProps> = ({ sessionActive }) => {
    /**
     * Unfortunately Bootstrap doesn"t support recollapsing the navbar after an
     * option is selcted. This will trigger a click event on the Navbar button to
     * have the Navbar re-collapse after a link is selected.
     *
     * TO-DO: If anyone can figure out how to collapse the Navbar without jQuery,
     *        please do!
     *
     */
    jQuery(document).ready(function ($) {
        $(".nav-link").on("click", function () {
            $(".navbar-toggler").click();
        });
    });

    return NAVBAR([
        NAVBAR_COLLAPSE_BUTTON_LEAF,
        NAVBAR_BRAND_BUTTON_LEAF,
        NAVBAR_COLLAPSING_DIV([
            NAVBAR_LINKS_UL(
                LINKS.reduce(
                    (acc: (DOMElement<any, Element> | ComponentElement<any, any>)[], val: INavbarLinkData) => {
                        return val.requiresSession === sessionActive || val.alwaysShow ?
                            acc.concat(val.viewLink) :
                            acc;
                    }, []))
        ])
    ]);
}

export default Navbar;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.