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

React-Select for Creating a Dynamic Multi-Category Dropdown Menu

I am looking to incorporate react-select into my project for a multi-category dropdown list. Specifically, I need the ability to select only one option at most from each category. To better illustrate this requirement, consider the following example wher ...

Is it possible to determine the type of a variable by simply clicking on it in IntelliJ (specifically in typescript)?

Having the ability to hover over a variable and see the expected type in TypeScript would be incredibly beneficial. I'm curious if there is some sort of internal static analysis being conducted that stores this information. Is there a method for acces ...

Receiving an abundance of alert notifications triggered by the search functionality of jsTree

I've created a function to search for text within the jsTree framework. The goal is to highlight the node if the search text is found. If not, inform the user with a message saying "No node matching the search string, please try again." However, I&a ...

Creating a 2D Image Display in three.js

I'm facing a challenge with my threejs project. My goal is to have a 2D image appear on the screen when I press a key. I've done some research but haven't been able to find a solution that works for me. The methods I've tried either don ...

Set the display property of all child elements within the DIV to none

CSS <div class="container"> <span></span> <input type="text"> </div> JavaScript function hideElements(){ let container = document.querySelector(".container"); let elements = ...

What's the deal with the `return of ()` syntax?

Just came across this piece of code: https://i.sstatic.net/JZXP5.png Code snippet in typescript. The first line looks like: ... return of (true); Can someone explain this syntax to me? ...

Ways to retrieve element(s) and delete a specific class located in the DOM structure

This is my first time using stackoverflow and posting a question here! :] Can someone guide me on the best way to write JQuery code for this particular task? Task: My goal is to remove the 'active' CLASS from a nav element when it is active: ...

Is it possible to display a variety of color schemes in just one console.log()?

My task involves working with an array of hexadecimal values, "colors": ["#d5dd90","#e6bb45","#ef9770"] To log these out in different colors, I used the following method: colors.forEach((value)=>{ console.log(& ...

What is the best way to position my Jchartfx area graph below my gridview?

When my page loads, the graph appears like this. It consistently shows up in the top left corner even though it should be positioned underneath the grid view as intended. var chart1; function createGraph(mpy) { if (mpy == undefined) mpy = 12.00; ch ...

Wait for another user keypress event in jQuery after a 0.5 second delay

Currently, I am working on developing a live search feature for my website. In order to reduce unnecessary requests and optimize performance, I am looking to implement a simple jQuery solution (while also ensuring that there is back-end flood control in pl ...

The nuSelectable plugin enhances the functionality of jQuery

I am currently experimenting with the jQuery nuSelectable plugin in order to enable users to select multiple items simultaneously. Unfortunately, I am encountering difficulties in making the selection work as intended. You can find the plugin here. After ...

What is the most efficient method for managing window properties and child components in React/Redux?

My <Layout> component loads different child components based on the page. Some of these children may have tabs, while others may not. This variation in content affects how scrolling should work and consequently influences the structure of the scroll ...

Adding Google Tag Manager to a NextJS TypeScript project can be tricky, especially when encountering the error message "window.dataLayer is not

I am currently setting up GTM on my website, but I am facing a challenge as my NextJS project is in Typescript. I followed the example on Github provided by Vercel, but I encountered this error: TypeError: window.dataLayer is not a function Below is the c ...

Exploring the return type of the `within` function in TypeScript Library

I have helpers set up for my React tests using the testing library: const getSomething = (name: string, container: Screen | any = screen) { return container.getByRole('someRole', { name: name }) } The container can be either the default screen ...

In certain situations, the JavaScript code runs either before or after the print dialog is displayed when using the window

I am facing an issue with the print function on my web page. I want to display a thank you message to the user after they have either printed or cancelled the print dialog. Below is a simplified version of the print function code: function printThenThank ...

Angular foreach method encounters a syntax issue

When I use the getTotal.getValues() function to make a server call that returns values like "one", "two", "three" up to "nine", I am able to print them using console.log(res). However, I am facing an issue where I cannot push these returned values into t ...

Modify mesh in three.js scene

Is there a way to dynamically change a mesh in a group triggered by a button click? I am loading an external .obj file: loader.load( obj, function ( object ) { createScene( object, mod.tipo, pid, cor.replace("#","0x") ); }); and adding it to a gro ...

Building an anchor tag that employs the HTTP DELETE method within an Express.js app

Recently, I delved into using express.js with handlebars.js as my template engine. One task I wanted to tackle was creating a delete link that followed RESTful principles and used the HTTP DELETE verb instead of GET. After some trial and error, I discover ...

Error Encountered While Creating a Polygon Wallet on Fireblocks

After following the instructions from Fireblocks Docs, I successfully created a default wallet named "BTC_TEST" like this: enter image description here. However, when attempting to create a Matic wallet, I encountered an Axios Error. Despite Matic being a ...

Express Angular Node Template Render throwing an error: module 'html' not found

I am currently in the process of creating a web application using AngularJS with ui-router for routing via $stateProvider, ensuring that only the specified states are displayed in the ui-view. In my server.js file, I have set up an initial framework such ...