score:1

Accepted answer

I put your code in a code sandbox and it seems to work just fine. Note that i removed className properties and commented stuff that is not necessary to test your issue like redux imports.

Since I removed the className properties this could be an CSS issue, where your error span is actually rendered but not visible (check out the dev tools of your browser to see if the span is really not there).

Also I would recommend you to use a library if you use a lot of forms, since state handling + validation could get quite complicated and there are plenty solutions out there.
I wrote my own library - react-fluent-form - feel free to check that out.

EDIT

The issue here is that when you update the error object using setError you always pass the same object reference:

// this is not doing a copy
// validationError will have the same reference as error
let validationError = error;

// ...

// following line will not trigger a rerender
setError(validationError);

Since error and validationError have the same reference, react will asume no change has happened, thus it will cause bail out of a state update. If you work with complex types (like objects or arrays) in state you always need to create a new reference instead of adapting the previous one:

// this is an actual copy using the spread operator
// validationError will have different reference than error
let validationError = {...error};

// ...

// triggers rerender as expected
setError(validationError);

EDIT 2

I added a return value for validate to use the updated validationError object when calling validateForm.

const validate = () => {
    //..
    let validationError = { ...error };

    // ...

    return validationError;
};

const onSubmit = e => {
    // ...
    const validationError = validate();

    if (validateForm(validationError)) {
    //...
    }
};

See the updated code sandbox.


Related Query

More Query from same tag