Search code examples
reactjsfunctional-programmingreact-functional-component

Functional programming in React Function Components


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.


Solution

  • 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