React SetState Array with function - reactjs

I have an array I wish to keep track of,
const [myArray, setMyArray] = useState([]);
I want this to be an array of MyCustomObject
I am then creating the array in my useEffect():
const newObject = new MyCustomObject(); //initiate object and set values (removed to simplify)
setMyArray(newObject);
I want to override myArray rather than push onto the list. How do I do this?
EDIT:
I think my initial code example was misleading. I've now copy/pasted my actual code for full transparency of the problem..
//this is imported further above
// import CustomerTradeDto from "Models/Customer/CustomerTradeDto";
const [arrayOfCustomerProviderRefTrades, setArrayOfCustomerProviderRefTrades] = useState([]);
useEffect(() => {
const newArray = customer.customerProviderRefList.map((providerRef) => {
return {
customersWithTrade: customerTradeList.customerTradeList.find(trade => trade.customerProviderRef == providerRef.externalRefId)
}
})
setArrayOfCustomerProviderRefTrades([...arrayOfCustomerProviderRefTrades, newArray]);
});
Error I am receiving: Type '{ customersWithTrade: CustomerTradeDto | undefined; }[]' is not assignable to type 'never'

You should use the spread operator.
Try this
setMyArray([...newObject]);
Hope this helps

You want the state to be an array of an object, so set the state likewise.
Solution
setMyArray([...myArray, newObject])
This should work smoothly.

I assume that you have array of object and want to override one object of it with new one
you can do it by using prevState and findIndex
like this:
const [myArray, setMyArray] = useState([]);
const newObject = new MyCustomObject();
setMyArray((prevState)=>{
let newArr = prevState;
if(newArr.length === 0 ){
return [newObject]
}
let arrIndex = newArr.findIndex(item => item.id === newObject.id)
if(arrIndex !== -1){
newArr[arrIndex] = newObject
}
return newArr
});
and if newObject is array of new objects you can have a loop for it
like this:
setMyArray((prevState)=>{
let newArr = prevState;
if(newArr.length === 0 ){
return [newObject]
}
newObject.forEach(obj=>{
let arrIndex = newArr.findIndex(item => item.id === obj.id)
if(arrIndex !== -1){
newArr[arrIndex] = obj
}
})
return newArr
});
and for typescript error you can set typeof arrayOfCustomerProviderRefTrades state in this way :
const [arrayOfCustomerProviderRefTrades, setArrayOfCustomerProviderRefTrades] = useState<Array<{ customersWithTrade: CustomerTradeDto | undefined }[]>>([]);

Related

What type to specify for event handlers tied to multiple fields?

I have a React form with dynamic fields based on this tutorial. It needs to be modified to be TypeScript compatible. I'm getting this error:
"Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ makeModel: string; year: string; }'."
const handleCarChange = (e: any, index: number) => {
const { name, value } = e.target;
const list = [...carList];
list[index][name] = value; // TODO fix this typescript error
setCarList(list);
};
For using types with click handlers, you can try using MouseEvent from React. Not sure if this solves the typescript error though. It looks different.
import { MouseEvent } from 'react';
const handleCarChange = (e: MouseEvent, index: number) => {
const { name, value } = e.target;
const list = [...carList];
list[index][name] = value; // TODO fix this typescript error
setCarList(list);
};
Further reading.
https://fettblog.eu/typescript-react/events/
This tutorial is bad. And this is only my opinion.
Why ?
First of all, this line const list = [...carList]; does not make any sense.
Here, author of tutorial still mutates the array
list[index][name] = value;
Consider next example:
const list = [{ name: 1 }]
const newList = [...list]
newList[0]["name"] = 2
list // [{name: 2}]
You should never mutate the state. This line [...list] only creates shallow copy. It just new reference to whole array, but each element in the array still have the same reference.
Let's go back to your problem.
type Fn = (args: any) => any
type ChangeHandler = ChangeEventHandler<HTMLInputElement>
type WithIndex<T extends Fn> = (e: Parameters<T>[0], index: number) => void
const Component = () => {
const [inputList, setInputList] = useState<State>([{ firstName: "", lastName: "" }]);
const handleCarChange: WithIndex<ChangeHandler> = (e, index) => {
const { name, value } = e.target;
const newList = inputList.map((elem: Entity, i: number) =>
i === index ? {
...elem,
[name]: value
} : elem)
setInputList(newList);
};
return (
<input
onChange={e => handleCarChange(e, 0)}
/>
)
}
Pls keep in mind, for any mouse handler you alway can use built in types like ChangeEventHandler
Playground
If you want to update your state please refer to official redux patterns

react js, why is this undefined

I have this array that is type Technology and type undefined and its making things very difficult. For example I am trying to compare this array to another array and it complains that it might be undefined etc. So I am wondering why is this undefined and how to prevent this.
This component is my main component that gets the data from the server and creates 2 different arrays holding all the data from each collection.
const App = ():JSX.Element => {
//Data from server recieve both collections in one get
const onResponse = (result:JSONData):void => {
// data received from Web API
//console.table(result);
setTechnologies(result.technologies);
setAll_Courses(result.all_courses);
setLoading(false);
};
//For error
const onError = (message:string):void => console.log("*** Error has occured during AJAX data transmission: " + message);
// ----------------------------------- setting state variables
const [technologies, setTechnologies] = React.useState<Technology[]>([]);
const [all_courses, setAll_Courses] = React.useState<AllCourses[]>([]);
// Loading
const [loading, setLoading] = React.useState<boolean>(false);
// Routing and history
const history:any = useHistory();
const route:string = useLocation().pathname;
React.useEffect(():void => {
getJSONData(COURSE_SCRIPT, onResponse, onError);
}, []);
And in my other component I make an array from the data that matches this _id in the collection of technologies. So I can then work in this component with that specific document, because I need to edit the data and display data etc. Everything is difficult because its undefined.
const EditTechnology = ({technologies, all_courses, visible}:ViewProps):JSX.Element => {
let { id } = useParams<{id:string}>();
let edit_Technology:(Technology | undefined) = technologies.find(item => item._id === id) ;
you can give props a default value in child components to prevent undefined errors, like this
const EditTechnology = ({technologies = [], all_courses = [], visible}:ViewProps):JSX.Element => {
let { id } = useParams<{id:string}>();
let edit_Technology:(Technology | undefined) = technologies.find(item => item._id === id);
}
Or put it in onResponse, in this way, you don’t need to add many default value to each child components
const onResponse = (result:JSONData):void => {
// data received from Web API
//console.table(result);
setTechnologies(result.technologies || []);
setAll_Courses(result.all_courses || []);
setLoading(false);
};
I think it maybe depends on method find
const bad = ['you','will','get', 'undefined', 'if', 'you', 'looking']
.find(e => e === 'something else')
const good = ['now', 'you', 'will','find', 'it']
.find(e => e === 'it')
console.log('for `something else` -', bad)
console.log('for `it` -', good)

How to Loop an Array Using Map Function Based on Condition and Then setState?

I want to have same functionality using map function. Following is my code:
async componentDidMount() {
const data = await TodoAPI.getTodo();
for(var i=0;i<data.response.length;i++)
{
if(data.response[i]._id === this.props.match.params.id)
{
this.setState({
todo_description: data.response[i].todo_description,
todo_responsible: data.response[i].todo_responsible,
todo_priority: data.response[i].todo_priority,
todo_completed: data.response[i].todo_completed
})
}
}
}
I guess you’re a bit confused, as map() would not be useful in this case. It creates a new array but what you want (correct me if I’m wrong) is to find the item, so find() seems to be what you need:
async componentDidMount() {
const data = await TodoAPI.getTodo();
const id = this.props.match.params.id;
const item = data.response.find(item => item._id === id);
this.setState(item);
}

How to solve Error Use object destructuring prefer-destructuring - React

I am stuck with an ugly issue which I am unable to resolve. I am beginner in React.
This is my Code
handleCheckChildElement(event) {
let items = this.state.items;
items.forEach(items = () => {
if(items.value === event.target.value) {
items.isChecked = event.target.checked;
}
});
this.setState({ items });
}
This is the image of the error -
Use below code for line #55 :
let {items}= {...this.state};
Read more here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring
Your code can be improved to something like below. Please find relevant comments in the code below for your better understanding
handleCheckChildElement(event) {
const { items } = this.state; //extract state values like this to a const variable
const newItems = items.map(item => { //do map on items because map returns a new array. It’s good practice to use .map than forEach in your case
if(item.value === event.target.value) {
item.isChecked = event.target.checked;
return item; //return updated item object so that it will be pushed to the newItems array
}
return item; // return item because you need this item object as well
});
this.setState({ items: newItems}); //finally set newItems array into items
}
handleCheckChildElement(event) {
const items = this.state.items;
const filtered = items.filter(item => item.value === event.target.value)
.map(item => item.isChecked = event.target.checked) ;
this.setState({items : [...filtered] );
}

component is getting loaded on 2nd iteration in react

I am using react plus d3 for my graph. now I am merging two different arrays to populate y graph. but one of the array is showing undefined to me, I checked in reducer both array gets the data properly. but if I go and come back again to the page it is working fine. so means only whenever my DOM is clear it is giving me problem. below is my reducer
const VolumeQuantity = MixMergeGroup.map(data => {
return data.map
.map(mixdata => mixdata.world_volume * sum)
.reduce((prev, curr) => prev + curr);
});
var VolumeQuantityGraph = [];
var tempObj_world = null;
MixMergeGroup.map((data, index) => {
tempObj_world = {};
tempObj_world['world_name'] = data.key;
tempObj_world['world_volume'] = VolumeQuantity[index];
VolumeQuantityGraph[index] = tempObj_world;
});
var overGraph = [];
currentYearData.map((data, index) => {
let temp = {};
temp.key = data.name;
let values = {};
values.value = data.forcasted_volume;
values.label = data.year + ' Volume';
temp.values = [values];
overGraph[index] = temp;
});
return {...state,overallGraph: overGraph,MixVolumeGraph:VolumeQuantityGraph}
here is my component where I use this value
componentWillReceiveProps(nextProps) {
if (
this.state.graphData.length == 0 &&
nextProps.MixVolumeGraph !== undefined
) {
nextProps.overallMixData.forEach(function(element) {
let filtereddata = nextProps.MixVolumeGraph.filter(
data => data.name === element.name
);
element.world_volume = filtereddata[0].volume;
});
console.log('nextprops', nextProps);
this.setState({ graphData: nextProps.overallMixData });
}
}
can please anyone let me know why on first instance its not giving me any value?
from docs React doesn't call componentWillReceiveProps with initial props during mounting. It only calls this method if some of component's props may update. Calling this.setState generally doesn't trigger componentWillReceiveProps.
so you need to call this method in componentdidmount to start it on first mount
edit: sample:
processData(nextProps) {
if (
this.state.graphData.length == 0 &&
nextProps.MixVolumeGraph !== undefined
) {
nextProps.overallMixData.forEach(function(element) {
let filtereddata = nextProps.MixVolumeGraph.filter(
data => data.name === element.name
);
element.world_volume = filtereddata[0].volume;
});
console.log('nextprops', nextProps);
this.setState({ graphData: nextProps.overallMixData });
}
}
componentWillReceiveProps(nextProps) {
processData(nextProps);
}
componentDidMount() {
processData(this.props);
}
I had used componentDidMount but it was giving me error Cannot read property 'props' of undefined this is because I was using foreach loop. I changed into map it is working now below is my update loop
this.props.overallWorldMixData.map(data => {
let filtereddata = this.props.MixVolumeGraph.filter(
dataName=> dataName.name === data.name
);
data.volume = filtereddata[0].volume;
});
I don't know the reason and I know it is not immutable solution, if anyone can clarify this I would be glad

Resources