2

I have an array of objects in my React state. I want to be able to map through them, find the one I need to update and update its value field. The body of my request being sent to the server should look like: { name: "nameOfInput", value:"theUserSetValue" type: "typeOfInput" }

What I thought would be simple is causing me some heartache. My reducer function calls, and I hit the "I AM RUNNING" log where it then jumps over my map and simply returns my state (which is empty). Please note that I NEVER see the "I SHOULD RETURN SOMETHING BUT I DONT" log.

NOTE: I have learned that I could be simply handingling this with useState

function Form(props) {
    const title = props.title;
    const paragraph = props.paragraph;
    const formBlocks = props.blocks.formBlocks
    const submitEndpoint = props.blocks.submitEndpoint || "";
    const action = props.blocks.action || "POST";

    const formReducer = (state, e) => {
        console.log("I AM RUNNING")
        state.map((obj) => {
            console.log("I SHOULD RETURN SOMETHING BUT I DONT")
            if (obj.name === e.target.name) {
                console.log("OBJ EXISTS", obj)
                return {...obj, [e.target.name]:obj.value}
            } else {
                console.log("NO MATCH", obj)
                return obj
            }
        });
        return state
    }

    const [formData, setFormData] = useReducer(formReducer, []);
    const [isSubmitting, setIsSubmitting] = useState(false);

===================================================================== Where I am calling my reducer from:

<div className="form-block-wrapper">
                {formBlocks.map((block, i) => {
                    return <FormBlock 
                    key={block.title + i}
                    title={block.title}
                    paragraph={block.paragraph}
                    inputs={block.inputs}
                    buttons={block.buttonRow}
                    changeHandler={setFormData}
                    />
                })}
            </div>
2
  • Are you sure you should be using the useReducer hook instead of the useState hook? You should be passing an action object to the setFormData function which is the dispatch function, and the reducer function should handle specific action types. What are you really trying to accomplish here? What does the FormBlock component pass to its changeHandler handler? Can you also add the FormBlock component code? See minimal reproducible example. Commented Oct 1, 2022 at 7:37
  • maybe I shouldn't be using useReducer. I have tried with useState and it is the same issue. I will update my post with that code as well. As for your blocks question it just builds the form automatically based on a JSON file that is produced from somewhere else and as I mentioned my event comes through fine because it is triggering my formReducer handler. Commented Oct 1, 2022 at 7:47

2 Answers 2

1

Issues

When using the useReducer hook you should dispatch actions to effect changes to the state. The reducer function should handle the different cases. From what I see of the code snippet it's not clear if you even need to use the useReducer hook.

When mapping an array not only do you need to return a value for each iterated element, but you also need to return the new array.

Solution

Using useReducer

const formReducer = (state, action) => {
  switch(action.type) {
    case "UPDATE":
      const { name, value } = action.payload;
      return state.map((obj) => obj.name === name
        ? { ...obj, [name]: value }
        : obj
      );
    
    default:
      return state;
  }
};

...

const [formData, dispatch] = useReducer(formReducer, []);

...

{formBlocks.map((block, i) => {
  return (
    <FormBlock 
      key={block.title + i}
      title={block.title}
      paragraph={block.paragraph}
      inputs={block.inputs}
      buttons={block.buttonRow}
      changeHandler={e => dispatch({
        type: "UPDATE",
        payload: {...e.target}
      })}
    />
  );
})}

Using useState

const [formData, setFormData] = useState([]);

...

const changeHandler = e => {
  const { name, value } = e.target;
  setFormData(data => data.map(obj => obj.name === name
    ? { ...obj, [name]: value }
    : obj
  ));
};

...

{formBlocks.map((block, i) => {
  return (
    <FormBlock 
      key={block.title + i}
      title={block.title}
      paragraph={block.paragraph}
      inputs={block.inputs}
      buttons={block.buttonRow}
      changeHandler={changeHandler}
    />
  );
})}
Sign up to request clarification or add additional context in comments.

5 Comments

I tried moving to useState with your code and it still returns nothing from the map. I have a useEffect() console logging and it never sets my state.
@ColtonVanBastelaere Do you have anything that is populating the formData array? If it's an empty array then mapping it will result in an empty array.
It starts off empty and this function is supposed to add a new one if it's empty or update an existing one.
@ColtonVanBastelaere I see what you mean now. I'll update my answer.
@ColtonVanBastelaere Actually, I think we need to see a bit more of these components, specifically what the FormBlock is passing to the changeHandler. The formData needs a complete object passed to the changeHandler so it can be added if it's not already there.
0

I have come to understand my problem much better now and I'll update my question to reflect this.

  1. As the user interacted with an input I needed to figure out if they had interacted with it before
  2. If they did interact with it before, I needed to find that interaction in the state[] and update the value as required
  3. If they didn't I needed to add an entirely new object to my forms state[]

I wrote two new functions, an AddObjectToArray function and an UpdateObjectInArray function to serve these purposes.

const handleFormInputChange = (e) => {
    const { name, value, type } = e.target;

    const addObjectToArray = (obj) => {
        console.log("OBJECT TO BE ADDED TO ARRAY:", obj)
        setFormData(currentArray => ([...currentArray, obj]))
    }
    
    const updateObjectInArray = () => {
        const updatedObject = formData.map(obj => {
            if (obj.name === name) {
                //If the name matches, Update the value of the input
                return ({...obj, value:value})
            }
            else {
                //if no match just return the object as is
                return obj
            }
        })
        setFormData(updatedObject)
    } 
    //Check if the user has already interacted with this input
    if (formData.find(input => input.name === name)) {
        updateObjectInArray()
    }
    else {
        addObjectToArray({name, value, type})
    } 
}

I could get more complicated with this now and begin to write custom hooks that take a setState function as a callback and the data to be handled.

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.