0

If I wanted to follow FP principals should my inner functions be pure in this React Function Component? Or is it fine due to the nature of this component and the fact that it is a Function Component? (because, I'm probably incorrect in my technical description, that it's a type of closure. and therefore it needs to change and access the state.)

const Component = ({data, onChange}) => {

  const [list, setList] = useState(data);

  const removeItem = (id) => {
    const newList = list.filter(item => item.id !== id);
    setList(newList);
    ... do something else here onChange ...
  }

  return (
    <>
      {list.map(({text, id}) => (
         <div onClick={() => removeItem(id) }>{text}</div>
      ))}
    </>
  )
}

Update Specifically should I be concerned that removeItem reference list and setList which are not inputs to this function when I'm trying to apply functional programming principals.

2
  • Do you have any concrete example of what kind of impurities you're worried about? Is it the setList you're thinking of? Commented Feb 9, 2021 at 10:40
  • Yeah, list and setList being referenced inside of that function. Commented Feb 9, 2021 at 10:57

2 Answers 2

1

About list:

const newList = list.filter((item) => item.id !== id);

Using the High Order Function .filter() we are making a copy of it and keep it as immutable data. So, we created a new list based on the old list and the filter condition maintained the immutability.

About setList:

setList(newList);

The workaround to avoid mutation will use the spread operator to spread all key/value pairs from the state object into the new state object (a similar idea as we normally do inside reducers) and it can look like this:

setList({ ...list, list: newList });

Also, we can use function delegation to encapsulate the method allowing the function to be composed (passed as data)

  const isDifferentID = item => item.id !== id;

  const removeItem = (id, setList, list) => setList({...list, list: list.filter(item => isDifferentID(item))});

Finally, all together:

const Component = ({data, onChange}) => {

  const [list, setList] = useState(data);

  const isDifferentID = item => item.id !== id;

  const removeItem = (id, setList, list) => setList({...list, list: list.filter(item => isDifferentID(item))});

  return (
    <>
      {list.map(({text, id, setList, list}) => (
         <div onClick={() => removeItem(id, setList, list)}>{text}</div>
      ))}
    </>
  )
}

Final notes:

Functional style discourages functions with side effects that modify internal state or make other changes that aren't visible in the functions return value. We aiming for statelessness and immutability as much as possible.

[Update]

Thanks. I like your suggestion about the arguments I can see this add predictability to the function, so I updated it to include: id, setList, list

Sign up to request clarification or add additional context in comments.

5 Comments

So for clarity sake, not all functions can be pure (obviously) and from Clean Code pg 45 "Functions should either do something or answer something, but not both. Either your function should change the state... or it should return some information..." Slightly paraphrased as it refers to OO Java example. In this case the function is changing state and thus cannot be pure. However I see the functional principals of immutability applied to your answer.
If we aim at statelessness and immutability would it be more correct to write removeItem like this? const removeItem = (id, setList, list) => setList({...list, list: list.filter(item => isDifferentID(item))}); So that removeItem is only using it's input parameters rather than referencing the lexical scope.
Does calling setList with an object (in the removeItem function) matter when you first called it with an array at the start? Also, is spreading the current list into the object for setList redundant? To make it more FP the removeItem function could be partially applied with list and setList then supplied to your onClick property. const removeItem = (list, setList) => id => setList(list.filter(item => item.id !== id))
Yeah, you are correct about the setList function detail, though I wasn't 100% concerned about that aspect @SuperJumbo more about what was passed in. But obviously it's useful for future peeps. Ok so we can say that functional React draws more boilerplate, for a function which is hidden in the function component and can't be tested directly. Of course it could be abstracted out into an external module. But this seems a little awkward for light weight components with one inner function.
@LeoLanese will you add the alternative HoF as an option?
0

As per @SuperJumbo comment, I thought the solution detailed was an equally valid answer (using a higher order function).

const Component = ({data, onChange}) => {

  const [list, setList] = useState(data);

  const isDifferentID = item => item.id !== id;

  const removeItem = (list, setList) => id => setList(list.filter(item => item.id !== id))
  
  const removeListItem = removeItem(list, setList);

  return (
    <>
      {list.map(({text, id, setList, list}) => (
         <div onClick={() => removeListItem(id)}>{text}</div>
      ))}
    </>
  )
}

But to some degree both seem like additional boilerplate - perhaps unneeded when we look at the original question.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.