2
votes

I am playing around with the new React-Redux Hooks library

I have an react component that has two input fields that update to the react store using useState() - desc and amount. In order to update changes to the the redux store when field has been edited I use onBlur event and call dispatch to the redux store. That works fine.

When I want to clear the fields from another component I would like this to work in same manner as for class based functions via connect & map State to Props, however to to this with functional component I need to utilise useSelector(). I cannot do this as the identifiers desc and amount are already used by useState()

What am I missing here?

import { useDispatch, useSelector } from "react-redux"
import { defineItem, clearItem } from "../store/actions"

const ItemDef = props => {
  const dispatch = useDispatch()

  const [desc, setDesc] = useState(itemDef.desc)
  const [amount, setAmount] = useState(itemDef.amount)

  //MAPSTATETOPROPS
  //I WANT TO HAVE THESE VALUES UPDATED WHEN REDUX STORE CHANGES FROM ANOTHER COMPONENT
  //THESE LINES WILL CAUSE ERROR to effect - identifier has already been declared
  const desc = useSelector(state => state.pendingItem.desc)
  const amount = useSelector(state => state.pendingItem.amount)

  return (
    <div>
      <p>Define new items to be added below - before clicking Add Item</p>
      <input
        value={desc}
        type="text"
        name="desc"
        placeholder="Description of Item"
        onChange={e => setDesc(e.target.value)}
        //Use onBlur Event so that changes are only submitted to store when field loses focus
        onBlur={e => dispatch(defineItem(desc, amount))}
      />
      &nbsp;
      <input
        value={amount}
        type="number"
        name="amount"
        placeholder="Amount"
        onChange={e => setAmount(e.target.value)}
        //Use onBlur Event so that changes are only submitted to store when field loses focus
        onBlur={e => {
          dispatch(defineItem(desc, amount))
        }}
      />
    </div>
  )
}

export default ItemDef
1

1 Answers

3
votes

SOLUTION - WITH FULL CODE IN REPOSITORY

I worked out a solution by using useSelector (to map pendingItem part of redux state to itemDef) and the setEffect hook to apply useState to either state item (from input) or itemDef (from Redux State - this happens when redux is updated by another component or through the ADD ITEM TO INPUT button)

I have posted the working component below. I have also posted this small application to demonstrate how to use reacdt-redux libraries with both class based components and fuinctional components using hooks

The repository is https://github.com/Intelliflex/hiresystem

//**************************************************************************************************
//***** ITEMDEF COMPONENT - Allow entry of new Items (dispatched from button in HireList Table) ****
//**************************************************************************************************
import React, { useState, useEffect, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { defineItem, clearItem } from '../store/actions'
import _ from 'lodash'

const ItemDef = props => {
  //BRING IN DISPATCH FROM REDUX STORE
  const dispatch = useDispatch()

  //DEFINE SELECTOR - EQUIV TO MAPSTATETOPROPS
  const { itemDef } = useSelector(state => ({
    itemDef: state.pendingItem
  }))

  const [item, setItem] = useState({ desc: '', amount: 0 })

  const onChange = e => {
    setItem({
      ...item,
      [e.target.name]: e.target.value
    })
  }

  const prevItem = useRef(item)
  useEffect(() => {
    //WE NEED TO CONDITIONALLY UPDATE BASED ON EITHER STORE BEING CHANGED DIRECTLY OR INPUT FORM CHANGING
    if (!_.isEqual(item, prevItem.current)) {
      //INPUT HAS CHANGED
      setItem(item)
    } else if (!_.isEqual(item, itemDef)) {
      //REDUX STATE HAS CHANGED
      setItem(itemDef)
    }
    prevItem.current = item
  }, [item, itemDef]) //Note: item and ItemDef are passed in as second argument in order to use setItem

  const clearIt = e => {
    dispatch(clearItem())
  }

  const addIt = e => {
    dispatch(defineItem({ desc: 'MY NEW ITEM', amount: 222 }))
  }

  return (
    <div>
      <p>Define new items to be added below - before clicking Add Item</p>
      <input
        value={item.desc}
        type='text'
        name='desc'
        placeholder='Description of Item'
        onChange={onChange}
        //Use onBlur Event so that changes are only submitted to store when field loses focus
        onBlur={e => dispatch(defineItem(item))}
      />
      &nbsp;
      <input
        value={item.amount}
        type='number'
        name='amount'
        placeholder='Amount'
        onChange={onChange}
        //Use onBlur Event so that changes are only submitted to store when field loses focus
        onBlur={e => dispatch(defineItem(item))}
      />
      &nbsp;
      <button onClick={clearIt}>CLEAR ITEM</button>
      &nbsp;
      <button onClick={addIt}>ADD ITEM TO INPUT</button>
    </div>
  )
}

export default ItemDef