DEV Community

Remon Fawzi
Remon Fawzi

Posted on

Write better JavaScript and ReactJs code with Immerjs

What is an immutable state?

It's a state that doesn't change over time.

So when we create an immutable object we don't change it's properties, If we need to change it's properties, we create a new copy of it and modify the properties as we want.

In JavaScript we can use both mutable and immutable states, We decide which is better for us, We can use Object.freeze() for example to make our object immutable.

Let's have an example, We've an object called state ...

const state = {
  id: 1,
  name: 'John',
  accounts: {
    facebook: {
      id: 'facebook',
      url: '/'
    },
    twitter: {
      id: 'twitter',
      url: '/'
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

We need to modify it's twitter account url and assign it to a new state without affecting the current state, So we would do something like this

const newState = {
  ...state,
  accounts: { ...state.accounts, twitter: { 
  ...state.accounts.twitter, url: '/newUrl' } }
};
Enter fullscreen mode Exit fullscreen mode

This code has two main issues

  1. It's so long in comparison to its purpose, I just want to update one property.
  2. Readability is not so good, Why I'm seeing many properties and nested levels while I'm only updating one property?!

If we use ImmerJs, It'll be like this

const newState = produce(state, (draftState) => {
  draftState.accounts.twitter.url = '/newUrl';
});
Enter fullscreen mode Exit fullscreen mode

Here, we can see

  1. Shorter code
  2. More readable, I'm only seeing the property I'm changing!

So, Immerjs produce function creates a new draft copy of your object or array, And let you modify it as you want, Then returns you a new state with the changes you applied.

In React

All states are immutable, When we set a state we create a new variable and assign it to the state, We don't change the current one

Let's have an example

const [quiz, setQuiz] = useState({
   title: "Quiz 1",
   questions: [
      {
        id: "1",
        type: "mcq",
        title: "Question 1?",
        answers: [
           {
             id: "a_1",
             text: "Answer 1",
             isTrue: true
           },
           {
             id: "a_2",
             text: "Answer 2",
             isTrue: false
           }
        ]
      }
   ]
});
Enter fullscreen mode Exit fullscreen mode

We've a state holds an object, What if we want to push a new answer to the quiz? We would do something like this

const addAnswer = (questionId, newAnswer) => {
  setQuiz({
    ...quiz,
    questions: quiz.questions.map((question) =>
      question.id === questionId
        ? { ...question, answers: [...question.answers, 
         newAnswer] }
        : question
    )
  });
};
Enter fullscreen mode Exit fullscreen mode

Complex, right? Here's how it's done using Immerjs

const addAnswer = (questionId, newAnswer) => {
  setQuiz(produce(prev, (draft) => {
      const question = draft.questions.find((el) => el.id === questionId);
      question.answers.push(newAnswer);
    }
  );
};
Enter fullscreen mode Exit fullscreen mode

So it's not just about reducing amount of code, But we only focus on what we need to change "The answer", without caring about the other quiz properties.

There's also a light package use-immer contains a custom hook useImmer, It's used instead of useState to call the produce function automatically when setting a state

const [quiz, setQuiz] = useImmer({
   .....
});
Enter fullscreen mode Exit fullscreen mode

It'll make our code more simpler

const addAnswer = (questionId, newAnswer) => {
  setQuiz((draft) => {
    const question = draft.questions.find((el) => el.id === questionId);
    question.answers.push(newAnswer);
  });
};
Enter fullscreen mode Exit fullscreen mode

This awesome package immerjs makes Redux toolkit too much simpler than old redux pattern, It's automatically implemented there, So you don't need to use the many spread operators just to change one value in your reducers.

Thank you so much!

Top comments (0)