Should I put the data changing code in a function or just in an action?
P粉410239819
P粉410239819 2024-01-10 18:11:13
0
1
375

I'm currently writing a bus timetable application that uses object types to model timetable "documents".

interface Timetable {
  name: string
  stops: string[]
  services: string[][]
}

In addition to types, I have many functions, and if I'm going to use mutations, I usually write them as methods on the class. I mainly use Immer so I don't have to write a lot of extended syntax. For example,

const addStop = (timetable: Timetable, stopName: string): Timetable => {
  return produce(timetable, (newTimetable) => {
    newTimetable.stops.push(stopName)
  })
}

To manage state I use Zustand and Immer, but I feel like if I used Redux my problem would be the same. In my store I have an array of Timetable objects, and an operation that also uses Immer to reassign the currently selected Timetable object:

        updateTt: (tt, index) => {
          set((state) => {
            state.timetables[index] = tt
          })
        },
        updateThisTt: (timetable) => {
          set((s) => {
            if (s.selectedTtIdx === null) {
              throw new Error("no selected timetable")
            }
            s.timetables[s.selectedTtIdx] = timetable
          })
        },

Then I call the data change function in the React component and call the update operation:

const onAddStop = (name) => {
  updateThisTt(addStop(timetable, name))
}

This works, but I'm not sure if I'm doing it correctly. I now have two layers of Immer calls, my component now has data modifying functions that are called directly in its event handlers, and I don't really like the look of the "methods" even though overall it's a minor bug.

I have considered:

  • Convert all data modifications into operations. This looks like it will be harder to maintain and harder to understand because there is a lot of duplication in indexing the store's array of objects.
  • Create an action for each data modification function; then, call these actions from my event handler. This seems to create some duplication, even just in terms of names.
  • Typecast my Timetable to a class, rewrite the data modification functions as mutation methods, and set [immerable] = true and let Immer do all the work for me action. I've done this, but I'd rather stick to the immutable record mode.

For what it's worth, the documentation for Flux, Zustand, or Immer tends to show the first option, and only occasionally; no application is as simple as counter = counter 1. What is the best way to build an application using the Flux architecture?

P粉410239819
P粉410239819

reply all(1)
P粉576184933

(I'm not familiar with Zusand and Immer, but maybe I can help...)

There are always different ways and I'm suggesting my favorite one here.

Clearly distinguish between "scheduling" actions and actual "mutations" of state. (Maybe add another level in between).

Specific "mutation" function

I recommend creating specific "mutation" functions rather than generic ones, i.e.:

  • instead of updateThisTt: () => { ...,
  • Use functions such as addStop: () => { ....

Create as many mutation functions as needed, each with a purpose.

Constructing new state in "mutation" function

Conceptually, use immer generators only within mutation functions.
(I mean about the store. Of course, you can still use immer for other purposes) .

According to this official example:

import { produce } from 'immer'

const useLushStore = create((set) => ({
  lush: { forest: { contains: { a: 'bear' } } },
  clearForest: () =>
    set(
      produce((state) => {  // <----- create the new state here
        state.lush.forest.contains = null
      })
    ),
}));

const clearForest = useLushStore((state) => state.clearForest);
clearForest();

Inside the component you can now call the "mutator" function.

const onAddStop = (name) => {
  updateThisTt( timetable, name );
}

Building a new country

If building the new state gets complicated, you can still extract some "builder" functions. But first consider the next section, "Large Files and Duplication."

For example your addStop function can also be called inside a Zustand mutation:

updateThisTt: ( timetable: Timetable, stopName: string ) => {
    const newTimeTable = addStop( timetable, stopName );
    set( ( s ) => {
        if( s.selectedTtIdx === null ){
            throw new Error( "no selected timetable" )
        }
        s.timetables[ s.selectedTtIdx ] = newTimeTable;
    });
}

Large Files and Duplication

Code duplication should certainly be avoided, but there are always trade-offs.

I won't suggest a specific approach, but please note that code is usually read more than written . Sometimes I think it's worth writing a few more letters, for example something like state.timetables[index] multiple times, If it makes the purpose of the code more obvious. You need to judge for yourself.

Anyway, I recommend putting your mutation function into a separate file that doesn't do anything else, This way, it may seem easier to understand than you think.

If you have a very large file but are completely focused only on modifying the state , And the structure is consistent, making it easy to read even if you have to scroll A few pages.

For example if it looks like this:

// ...

//-- Add a "stop" to the time table 
addStop: ( timetable: Timetable, stopName: string ) => {

   // .. half a page of code ...
   
},

//-- Do some other specific state change
doSomethingElse: ( arg1 ) => {

   // .. probably one full page of code ...
   
},

//-- Do something else again ... 
doSomethingAgain: () => {

   // .. another half page of code ...
   
},

// ...

Also note that these variadic functions are completely independent (or are they? I expect Zustand to use pure functions here, no?) .

This means that if it gets complicated, you can even split up multiple mutation functions Split into separate files within a folder Like /store/mutators/timeTable.js.

But you can easily do this anytime later.

Building the "payload" ("action caller")

You may feel you need another "level" Between Event handler and Mutation function. I usually have a layer like this, but I don't have a good name for it. We will temporarily refer to this as "Operation Caller".

Sometimes it is difficult to decide what belongs to the "variation function" and what belongs to the "variation function" "Action caller".

Anyway, you can "build some data" inside the "action caller", but you should No state operations of any kind are performed here, not even using immer.

Transforming state and creating payload

This is a subtle distinction and you probably shouldn't worry too much about it (and there may be exceptions) , but as an example:

You can use parts of the old state within an "action caller", for example:

// OK:
const { language } = oldState;
const newItem = {  // <-- This is ok: build a new item, use some data for that.
    id: 2,
    language: language,
};
addNewItemMutator( newItem ):

But you should not notmap (or convert) the old state to the new state, for example:

// NOT OK:
const { item } = oldState;
const newItem = {  // <-- This kind of update belongs inside the mutator function.   
    ...item,  
    updatedValue: 'new',
};
addNewItemMutator( newItem ):

another example

No specific suggestions are made here, just an example:

It can get messy passing many values ​​to mutators, for example:

const myComponent = ( props ) =>{
    const { name } = props;
    cosnt value = useValue();
    
    // ...
    
    const onAddNewItem: () => {
        const uniqueId = createUUID();
        addNewItemMutator( name, value, uniqueId, Date.now() );
    },

In the event handler you want to focus on what you really want to do, like "add new item", But don’t consider all arguments. Additionally, you may need the new item for other things.

Or you can write:

const callAddItemAction = function( name, value ){

    const newItem = {
        name:        name,
        value:       value,
        uniqueId:    createUUID(),
        dateCreated: Date.now(),
    };
    
    // sendSomeRequest( url, newItem ); // maybe some side effects
    
    addNewItemMutator( newItem );
}

const myComponent = ( props ) =>{
    const { name } = props;
    cosnt value = useValue();
    
    // ...
    
    const onAddNewItem: () => {
        callAddItemAction( name, value );
    },
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template