How can I transform this imperative reducer into a more declarative format using Ramda?

I am currently working with a reducer function that aggregates values in a specific way.

The first argument is the aggregated value, while the second argument represents the next value. This function reduces over the same reaction argument, aggregating the state$ value. Each time it runs, a new aggregated value is produced.

/**
 * Applies all the reducers to create a state object.
 */
function reactionReducer(reaction: ReactionObject): ReactionObject {
    let state$ = reactionDescriptionReducer({}, reaction);
    state$ = reactionDisabledReducer(state$, reaction);
    state$ = reactionIconReducer(state$, reaction);
    state$ = reactionOrderReducer(state$, reaction);
    state$ = reactionStyleReducer(state$, reaction);
    state$ = reactionTitleReducer(state$, reaction);
    state$ = reactionTooltipReducer(state$, reaction);
    state$ = reactionVisibleReducer(state$, reaction);
    return state$;
}

const state = reactionReducer(value);

Although this setup works, I'm looking for a more flexible solution. I believe RamdaJS could provide a way to achieve this.

const state = R.????({}, value, [reactionDescriptionReducer
    reactionDisabledReducer,
    reactionIconReducer,
    reactionOrderReducer,
    reactionStyleReducer,
    reactionTitleReducer,
    reactionTooltipReducer,
    reactionVisibleReducer]);

As someone new to RamdaJS, I appreciate your patience with what may be a beginner question.

How can I utilize RamdaJS to run a chain of reducers effectively?

Answer №1

and creates a fresh reducer, (r, x) => ..., by merging the two input reducers, f and g-

const and = (f, g) =>
  (r, x) => g (f (r, x), x)

all, utilizing and, forms a new reducer by combining multiple reducers-

const identity = x =>
  x

const all = (f = identity, ...more) =>
  more .reduce (and, f)

Create myReducer using all-

const myReducer =
  all
    ( disabledItemReducer
    , iconItemReducer
    , orderItemReducer
    // ...
    )

Assuming mocked implementations for these three (3) reducers are available-

const disabledItemReducer = (s, x) =>
  x < 0
    ? { ...s, disabled: true }
    : s

const iconItemReducer = (s, x) =>
  ({ ...s, icon: `${x}.png` })

const orderItemReducer = (s, x) =>
  x > 10
    ? { ...s, error: "over 10" }
    : s

Execute myReducer to observe the results-

const initState =
  { foo: "bar" }

myReducer (initState, 10)
// { foo: 'bar', icon: '10.png' }

myReducer (initState, -1)
// { foo: 'bar', disabled: true, icon: '-1.png' }

myReducer (initState, 100)
// { foo: 'bar', icon: '100.png', error: 'over 10' }

Expand the code snippet below to verify outcomes in your browser-

const identity = x =>
  x

const and = (f, g) =>
  (r, x) => g (f (r, x), x)

const all = (f, ...more) =>
  more .reduce (and, f)

const disabledItemReducer = (s, x) =>
  x < 0
    ? { ...s, disabled: true }
    : s

const iconItemReducer = (s, x) =>
  ({ ...s, icon: `${x}.png` })

const orderItemReducer = (s, x) =>
  x > 10
    ? { ...s, error: "over 10" }
    : s

const myReducer =
  all
    ( disabledItemReducer
    , iconItemReducer
    , orderItemReducer
    // ...
    )

const initState =
  { foo: "bar" }

console .log (myReducer (initState, 10))
// { foo: 'bar', icon: '10.png' }

console .log (myReducer (initState, -1))
// { foo: 'bar', disabled: true, icon: '-1.png' }

console .log (myReducer (initState, 100))
// { foo: 'bar', icon: '100.png', error: 'over 10' }

You can select any names for and and all as you prefer. They could be part of a reducer module, such as reducer.and and reducer.all

Answer №2

One way to leverage Ramda in this scenario is by taking advantage of its support for passing functions as a monad instance to R.chain (also known as the Reader monad).

This allows you to chain together multiple functions that operate on a shared environment - in this case, the variable reaction.

We can use R.pipeWith(R.chain) to enable composing a sequence of these functions, each taking an input (such as your $state flowing through each function) and returning a new function that takes the environment as argument, generating a result to be passed to the subsequent function in the pipeline.

// Functions for illustration purposes

const reactionDescriptionReducer = ({...state}, reaction) =>
  ({ description: reaction, ...state })

const reactionDisabledReducer = ({...state}, reaction) =>
  ({ disabled: reaction, ...state })

const reactionIconReducer = ({...state}, reaction) =>
  ({ icon: reaction, ...state })

// Using `R.pipeK`
const kleisli = R.pipeWith(R.chain)

// Functions going into chain need to be curried
const curried = f => a => b => f(a, b)

// Composing the series of functions
const reactReducer = kleisli([
  curried(reactionDescriptionReducer),
  curried(reactionDisabledReducer),
  curried(reactionIconReducer)
])({})

// If all goes well...
console.log(
  reactReducer("someCommonReactionValue")
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>

Answer №3

Instead of utilizing Ramda, my initial strategy would be to keep things simple with the following approach:

const createReducer = (...functions) => (input) => functions.reduce((state, func) => func(state, input), {})

const customFunc = createReducer(
  (currentState, data) => ({...currentState, foo: `<<-${data.foo}->>`}),
  (currentState, data) => ({...currentState, bar: `=*=${data.bar}=*=`}),
  (currentState, data) => ({...currentState, baz: `-=-${data.baz}-=-`})
)

console.log(
  customFunc({foo: 'a', bar: 'b', baz: 'c'})
) //~> {foo: '<<-a->>', bar: '=*=b=*=', baz: '-=-c-=-'}

Although Ramda offers reduce and flip, their use in this particular scenario may not provide significant benefits.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Check to see if the item is not already in the cart, and if so, add it and then increase its quantity

Utilizing React context, I have implemented a simple logic to add products to the cart using the useReducer hook for adding items. If we look at the Redux Toolkit implementation, here is my redux logic: const cartItemSlice = createSlice({ name: " ...

Instead of using a v-if condition, add a condition directly in the Vue attribute

Apologies for the unclear title, as I am unsure of what to name it with regards to my current issue, I am attempting to create a component layout using vuetify grid. I have a clear idea of how to do this conventionally, like so: <template> <v-fl ...

I encountered a problem with React Native while attempting to update the state with a new value

As I work on developing my app using react native and firebase, I encountered an issue with the error message TypeError: undefined is not an object (evaluating 'this.state.desativado.push') when attempting to click the + button. For the complete ...

What is the best way to sum up multiple checkbox values in JavaScript?

var bookRate = new Array('The Right to differ', 'Issues In Contemporary Documentary', 'Writing, Directing and Producing', 'Lee Kuan Yew My Lifelong Challenge'); var selection = document.rate.checkbox; var sum = 0.00 ...

What is the correct way to construct an object in TypeScript while still taking types into account?

Hey, I'm having trouble implementing a common JavaScript pattern in TypeScript without resorting to using any to ignore the types. My goal is to write a function that constructs an object based on certain conditions and returns the correct type. Here& ...

Steps for utilizing a function from the parent component to initialize TinyMCE

I have implemented an uploading function in my parent component. As I set up tinymce, I connected the [init] property of my component to the loadConfig() function. <editor [(ngModel)]="data" [init]="loadConfig()"></editor> The loadConfig func ...

Select a random class from an array of classes in JavaScript

I have a collection of Classes: possibleEnemies: [ Slime, (currently only one available) ], I am trying to randomly pick one of them and assign it to a variable like this (all classes are derived from the Enemy class): this.enemy = new this.possibleEn ...

Tips for implementing an autoscroll feature in the comments section when there is an abundance of comments

Having a large number of comments on a single post can make my page heavy and long sometimes. This is the current layout of my post comment system: Post 1 Comment for post 1 //if comments are more than 3 <button class="view_comments" data-id="1">Vi ...

AngularJS is patiently waiting for the tag to be loaded into the DOM

I am trying to incorporate a Google chart using an Angular directive on a webpage and I want to add an attribute to the element that is created after it has loaded. What is the most effective way to ensure that the element exists before adding the attribut ...

Leveraging regular expressions for image domains within NextJS next.config.js

Is it possible to use regex in next.config.js to allow image domains? Giphy uses different numbers for its endpoints (e.g. media0.giphy.com, media2.giphy.com), but my regex isn't working and I'm seeing this error message: hostname "media0.gi ...

Automatic suggestions for my personalized npm module (written in ES6/Babel) in Webstorm

When I utilize the material-ui package in Webstorm, I am able to experience helpful auto-completion using the ctrl+space shortcut: https://i.stack.imgur.com/Pivuw.png I speculated that this feature may be attributed to the inclusion of an index.es.js fil ...

Guide: Building Angular/Bootstrap button checkboxes within a loop

I am in the process of designing a grid (table with ng-repeat) in which each row contains 4 columns of buttons. My goal is to use checkboxes as the buttons, like the Angular/Bootstrap btn-checkbox, so that they can be toggled on and off. I plan to set thei ...

Adding Material-UI icons dynamically in a React TypeScript project requires understanding the integration of imported icons

I have a collection of menu buttons with icons, stored in an array of objects. The icon names are saved as strings that correspond to Material UI icons: interface MenuButton { text: string, onClickFunction: Function icon: string } export defau ...

The MUI theme seems to be missing its application

As a newcomer to MUI, I'm facing challenges when trying to apply a custom theme. My goal was to create a new variant for the button using the code snippet below: // @ts-nocheck import React, {FC} from 'react'; import { createTheme, ThemeProv ...

Steps to enable Nodemailer to execute a separate .js script

Currently, I have the main nodejs server file named myserver.js const express = require("express"); const app = express(); const nodemailer = require("nodemailer"); const port = 80; const vectorExpress = require("./node_modules/@ ...

The Ajax request functions flawlessly on Mozilla but encounters issues on Chrome, causing confusion as it occasionally works

I am using a PHP file with a class and function to store data in the database, accessed via AJAX. While everything works smoothly in Mozilla, Chrome seems to be causing some issues. Strangely, sometimes it works fine, but other times it fails for no appare ...

Disable the setTimeout() function in order to prevent the countdown from refreshing

I have a JavaScript countdown function that is working well, but I am unsure how to stop and refresh the timer to extend the time. When I call the function again before it times out, it behaves strangely by showing two countdown timers because the updateTi ...

Topaz font does not properly display backslashes and certain characters when rendered on Canvas

Who would have thought I'd stumble upon a new challenge on Stack Overflow? But here we are. If there's already a solution out there, please guide me in the right direction. I'm currently developing an interactive desktop environment inspired ...

Encountering an error with Angular2 when referencing node modules

I encountered an issue when trying to use angular2 from the node_modules directory. Can anyone guide me on how to resolve this error? Is there something specific I need to include in my HTML file? I am looking for a Git repository where I can access Angu ...

Unspecified outcome of Ajax array as a choice in Select

I'm fairly new to working with AJAX / JSON and I'm having trouble figuring out how to parse an AJAX array response into a <select> dropdown. Despite going through several online tutorials and attempting different approaches to return the r ...