I have this function that is supposed to run each validator and then return the object that contains errors.
Everything seems to work fine, but the first validator in the array. It seems like reduce completely ignores it. No matter what validator I put there, it just goes right over to the second one.
Am I missing something obvious here?
export default values => (
[
validateFullName,
validateServicePresence,
validatePhoneField,
validateOrganizationName,
validateInn,
validateEmailField,
validateManagerEmail,
validateComment,
validateAgreement,
].reduce((currentErrors, validator) => {
const validationResult = validator(values);
return {
...currentErrors,
...validationResult,
};
})
);
If you don't provide an initial value to reduce, then it will use the first element of the array as the initial value, and skip calling your reducer with that element. So the very first time your reducer is called, currentErrors is validateFullName, and validator is validateServicePresence.
To fix this, just add an initial value:
export default values => (
[
validateFullName,
validateServicePresence,
validatePhoneField,
validateOrganizationName,
validateInn,
validateEmailField,
validateManagerEmail,
validateComment,
validateAgreement,
].reduce((currentErrors, validator) => {
const validationResult = validator(values);
return {
...currentErrors,
...validationResult,
};
}, {}) // <===================
);
See the initialValue section here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#Parameters
By default Array.prototype.reduce uses the first element as the accumulator value if no starting value is provided. Passing the statring value for the accumulator explicitly will make sure your first element is also processed.
See the initialValue parameter on the MDN docs
export default values => (
[
validateFullName,
validateServicePresence,
validatePhoneField,
validateOrganizationName,
validateInn,
validateEmailField,
validateManagerEmail,
validateComment,
validateAgreement,
].reduce((currentErrors, validator) => {
const validationResult = validator(values);
return {
...currentErrors,
...validationResult,
};
}, {})
);
Related
I have an array of default values in a useState. Upon an onChange event via a select, the properties in the array are changed to undefined. I've been reading up on controlled components and I think I've missed something silly.
Here is a codesandbox.
const [config, setConfig] = useState([{
carType: "Sedan",
carColor: "Red"
}]);
// onChange
const setCarType = (e) => {
setConfig({ ...config[0], carType: e.target.value });
};
const { carType, carColor } = config[0];
I thought that I could use the spread operator here in order to copy the two properties of config[0] into two separate constants. I originally had the config in an object but was thrown an error "Objects are not valid as a react child".
const setCarType = (e) => {
setConfig([{ ...config[0], carType: e.target.value }]);
};
const setCarColor = (e) => {
setConfig([{ ...config[0], carColor: e.target.value }]);
};
When you were setting config, you were setting a new object as config but it was initialized as an array of objects. So config[0] was undefined cause config was no more an array. Try the above code, it will solve the issue.
I want to update value of one object only but updating value of one Object, Updates the value for all objects.
let default = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(default)
}
updateName (event) {
let index = event.target.id,
values = this.state.values;
values[index].name = event.target.value;
this.setState ({
values: values
});
}
There are four significant problems in that code.
You're using the same object for all entries in your array. If you want to have different objects, you have to create multiple copies of the default.
You're calling setState incorrectly. Any time you're setting state based on existing state (and you're setting values based, indirectly, on this.state.values), you must use the function callback version of setState. More: State Updates May Be Asynchronous
You can't directly modify the object held in this.state.values; instead, you must make a copy of the object and modify that. More: Do Not Modify State Directly
default is a keyword, you can't use it as an identifier. Let's use defaultValue instead.
Here's one way you can address all four (see comments):
// #4 - `default` is a keyword
let defaultValue = {
name: '',
age: ''
};
this.state = {
// #1 - copy default, don't use it directly
values: [
Object.assign({}, defaultValue),
Object.assign({}, defaultValue),
] // <=== Side note - no ; here!
};
// ....
updateName(event) {
// Grab the name for later use
const name = event.target.value;
// Grab the index -- I __don't__ recommend using indexed updates like this;
// instead, use an object property you can search for in the array in case
// the order changes (but I haven't done that in this code).
const index = event.target.id;
// #2 - state updates working from current state MUST use
// the function callback version of setState
this.setState(prevState => {
// #3 - don't modify state directly - copy the array...
const values = prevState.values.slice();
// ...and the object, doing the update; again, I wouldn't use an index from
// the `id` property here, I'd find it in the `values` array wherever it
// is _now_ instead (it may have moved).
values[index] = {...values[index], name};
return {values};
});
}
Note that this line in the above:
values[index] = {...values[index], name};
...uses property spread syntax added in ES2018 (and shorthand property syntax, just name instead of name: name).
I would use the Array.prototype.map function with combination of the object spread syntax (stage 4):
Note that i changed the name of the default object to obj.
default is a reserved key word in javascript
let obj = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(obj)
}
updateName(event){
const {id, value} = event.target;
this.setState(prev => {
const {values} = prev;
const nextState = values.map((o,idx) => {
if(idx !== id)
return o; // not our object, return as is
return{
...o,
name: value;
}
});
return{
values: nextState
}
});
}
There is an easy and safe way to achieve that through the following:
this.setState({
values: [ newObject, ...this.state.values],
});
this will create an instance of the state and change the value of an existing object with new object.
Basically what I'm trying to do is call this 'myMemoFunction' function in a part of my code. The problem is that from what I've read in the documentation, useMemo() executes this function right on rendering, therefore the myArray parameter is still empty. Then it returns the error below.
const myMemoFunction = useMemo((myArray) => {
const users = myArray.map((a) => a.user)
return users;
})
Error:
myArray is undefined
You should use useCallback in that case
as useMemo memoizes a variable. Also I doubt it can take arguments.
Edit:
const myMemoFunction = useCallback((myArray) => {
// this won't be called on renders
const users = myArray.map((a) => a.user)
return users;
}, [] /* dont forget the dependency you want to evaluate only once */)
// later
myMemoFunction(arr);
Edit 2 with useMemo:
const myMemoVariable = useMemo(() => {
// re-evaluates each time myArray changes
const users = myArray.map((a) => a.user)
return users;
}, [myArray])
// note that we dont use myMemoVariable() to get our variable
console.log(myMemoVariable)
update your code to this
const myMemoFunction = useMemo((myArray) => {
// myArray could be undefined, that was why ? was added
const users = myArray?.map((a) => a.user)
return users;
}, [myArray]) // myArray is in a dependency of the function
your memo function will get updated with the new myArray variable
it is always good to initialize your myArray variable as an array e.g []
I have an array of objects that is saved into a userList useState which is composed of:
[{
firstName: "blah"
lastName: "blah2"
}
{
firstName: "test"
lastName: "test2"
}]
I have a useEffect that calls a function and returns a value. I want to store a new key and value to each user in userList.
useEffect(() => {
userList.forEach((user, index) =>
returnNewValueForNewKeyFunction(user, index).then(newValue => {
userList[index]['newKey'] = newValue
//this console.log shows new field and value
console.log(userList)
//this console.log ALSO shows new field and value
console.log(JSON.stringify(contactList[index]))
})
)
}
}, [])
This is fine if I'm operating out of console.log, but unfortunately I need to render the data onto the page.. in my render I have:
return (
<TableBody>
{userList
.map((user, index) => (
<TableRow>
<TableCell>
{user.newKey}
</TableCell>
)
user.newKey is showing as blank and it seems like the user wasn't updated at all. How can I make it so the value is actually updated and can be read from when rendering?
You shouldnt mutate your list, you should use useState to store your list, so something like this :
const [ state, setState] = useState(userList);
Then when you want to update, do something like this :
const listCopy = [...state];
//Logic to update your list here
listCopy[index][otherindex] = Value;
setState(listCopy)
Hope this helps
You are modifying your userList but not calling your set function on which means React won't know to re-render with the updated state.
Instead of mutating the current state, you should create a new array and then call the set function returned by useState with the updated array after making your changes.
It also looks like your returnNewValueForNewKeyFunction is a promise / async which means each of your item changes are happening async. You'll need to make these synchronous / wait for them all before updating your state to make your state change a single update for the UI.
E.g., putting these both together - if you are doing:
const [userList, setUserList] = useState();
You could do:
useEffect(() => {
// Since can't use an async func directly with useEffect -
// define an async func to handle your updates and call it within the useEffect func
const updateUsers = async () => {
// Create a new array for your updated state
const updatedUserList = [];
// Loop over your values inline so your can await results to make them sync
for (let index = 0; index < userList.length; index ++) {
const user = userList[index];
const newVal = await returnNewValueForNewKeyFunction(user, index);
// Create a shallow copy of the original value and add the newValue
updatedUserList[index] = { ...user, newKey: newValue };
// ... Any other logic you need
}
// Call set with the updated value so React knows to re-render
setUserList(updatedUserList);
};
// Trigger your async update
updateUsers();
}, [])
I get the warning in the title when compiling. I understand that it is about not handling some cases of if, but how can I filter before mapping in the correct way?
componentDidMount() {
this.props.UserReducer.user.employeeInfoList.map(role => {
if (role.employeeType) this.rolesOfUser.push(role.employeeType);
if (role.xdockId) this.xdockIdsOfUser.push(role.xdockId);
});
}
It is because you are misusing map which is used for mapping/transforming one array to another. Having a call to map without a return value indicates a problem, as you shouldn't be using it to just iterate over an array performing some action.
It looks like what you really wanted was a forEach call.
To filter an array use Array#filter. Also you can use Array#forEach for your case
componentDidMount() {
this.props.UserReducer.user.employeeInfoList.forEach(role => {
if (role.employeeType) this.rolesOfUser.push(role.employeeType);
if (role.xdockId) this.xdockIdsOfUser.push(role.xdockId);
});
}
Or
componentDidMount() {
const rolesOfUser = this.props.UserReducer.user.employeeInfoList.filter(role => {
return role.employeeType;
})
const xdockIdsOfUser = this.props.UserReducer.user.employeeInfoList.filter(role => {
return role.xdockId;
})
// Do smth with both arrays
}