Search code examples
reactjsreact-hookslogic

How would you implement a custom diffing algorithm in React JS?


Disclaimer: I'm not seeking a complete coding tutorial here but rather a theoretical or conceptual answer which can help me develop the end-diffing algorithm. I have a decent knowledge of how dirty, diffing and patching work in React JS to help components re-rendered and when I say diff checking, I don't mean anything regarding React's internal diffing algorithm as many questions do on Stack Overflow.

Now with that said, I just want to develop a certain thing that would help me generate a changelog kind of data. An example would be 100 times better.....

Assume I have following file structure...

----> OrderComponent

----------> OrderDetailsForm

----------> OrderDelieveryForm

Parent component OrderComponent which has the state of order which has been passed in OrderDetailsForm and OrderDelieveryForm as a prop.

Now an order can be updated from all three components, What is the best plan of action to generate the data which can help me get all the changes done in that state?(maybe an array of objects for each action)

Action 1: order.name, from "" to "ORDER400"
Action 2: order.lines.push(line_item)
Action 3: order.customer.name, from "Jake" to "Jones".

How can I implement the diffing algorithm which then can let me generate a changelog like Order name has been set to "ORDER400", Order customer name has been updated from "Jake" to "Jones", 1 Order line has been added.

In fact, I have a global redux store setup just in case your plan needs an app-level store. Let me know what kind of things might be helpful to achieve results like this!

JFI: I have a few things in mind in which I'd dispatch an action with changes and then deeply merge both objects and then deeply compare objects for changes. Or maybe go with some HOC which then can be wrapped in all three components. I'm not just sure what would be the best call here!


Solution

  • I have a good experience with the following two very small and fast functions:

    • microdiff (no need to roll your own diffing algo), this one is great, tiny, fast, but if you really want or need to write your own diffing method, you can certainly use this one as a base/inspiration and adapt it to your needs: https://github.com/AsyncBanana/microdiff
    • micropatch you can basically use the result of a microdiff-diff to patch an object, so they work together great: https://github.com/AsyncBanana/micropatch

    usage is pretty simple and I feel like it could fit your requirements:

    let order = {
        originalProperty: true,
    };
    const orderFromOrderDetailsForm = {
        originalProperty: true,
        newProperty: "new",
    };
    
    console.log(diff(order, orderFromOrderDetailsForm));
    // results in an array of diff objects, basically the changes you could
    // use to create your desired output message:
    // [
    //     {type: "CREATE", path: ["newProperty"], value: "new"}
    // ]
    
    // if you want to create a merged object you can simply use diff
    // in conjunction with patch like this:
    
    order = patch(order, diff(order, orderFromOrderDetailsForm));
    // results in merged object:
    /* {
        originalProperty: true,
        newProperty: "new"
    } */
    
    

    all credits go to the author!

    and generally, i think it would be better to use the global store to keep the order state and read/update it from within your components, so that all components relying on that shared state always have the same (current) order object available, no matter who updates it, that would basically eliminate your need for diffing and patching entirely, except of course you still want to generate those "changelogs", which could of course be done by using microdiff within your global store when an update comes in.