I am trying to solve a problem that happens in react app. In one of the views (components) i have a management tools that operate on big data. Basically when view loads i have componentDidMount that triggers ajax fetch that downloads array populated by around 50.000 records. Each array row is an object that has 8-10 key-value pairs.
import React, { Component } from "react";
import { List } from "react-virtualized";
import Select from "react-select";
class Market extends Component {
state = {
sports: [], // ~ 100 items
settlements: [], // ~ 50k items
selected: {
sport: null,
settlement: null
}
};
componentDidMount() {
this.getSports();
this.getSettlements();
}
getSports = async () => {
let response = await Ajax.get(API.sports);
if (response === undefined) {
return false;
}
this.setState({ sports: response.data });
};
getSettlements = async () => {
let response = await Ajax.get(API.settlements);
if (response === undefined) {
return false;
}
this.setState({ settlements: response.data });
};
save = (key, option) => {
let selected = { ...this.state.selected };
selected[key] = option;
this.setState({ selected });
};
virtualizedMenu = props => {
const rows = props.children;
const rowRenderer = ({ key, index, isScrolling, isVisible, style }) => (
<div key={key} style={style}>
{rows[index]}
</div>
);
return (
<List
style={{ width: "100%" }}
width={300}
height={300}
rowHeight={30}
rowCount={rows.length || 1}
rowRenderer={rowRenderer}
/>
);
};
render() {
const MenuList = this.virtualizedMenu;
return (
<div>
<Select
value={this.state.selected.sport}
options={this.state.sports.map(option => {
return {
value: option.id,
label: option.name
};
})}
onChange={option => this.save("sport", option)}
/>
<Select
components={{ MenuList }}
value={this.state.selected.settlement}
options={this.state.settlements.map(option => {
return {
value: option.id,
label: option.name
};
})}
onChange={option => this.save("settlement", option)}
/>
</div>
);
}
}
The problem i am experiencing is that after that big data is downloaded and saved to view state, even if i want to update value using select that has ~100 records it takes few seconds to do so. For example imagine that smallData is array of 100 items just { id: n, name: 'xyz' } and selectedFromSmallData is just single item from data array, selected with html select.
making a selection before big data loads takes few ms, but after data is loaded and saved to state it suddenly takes 2-4 seconds.
What would possibly help to solve that problem (unfortunately i cannot paginate that data, its not anything i have access to).
.map() creates a new array on every render. To avoid that you have three options:
store state.sports and state.settlements already prepared for Select
every time you change state.sports or state.settlements also change state.sportsOptions or state.settlementsOptions
use componentDidUpdate to update state.*Options:
The third option might be easier to implement. But it will trigger an additional rerender:
componentDidUpdate(prevProps, prevState) {
if (prevState.sports !== this.state.sports) {
this.setState(oldState => ({sportsOptions: oldState.sports.map(...)}));
}
...
}
Your onChange handlers are recreated every render and may trigger unnecessary rerendering of Select. Create two separate methods to avoid that:
saveSports = option => this.save("sport", option)
...
render() {
...
<Select onChange={this.saveSports}/>
...
}
You have similar problem with components={{ MenuList }}. Move this to the state or to the constructor so {MenuList} object is created only once. You should end up with something like this:
<Select
components={this.MenuList}
value={this.state.selected.settlement}
options={this.state.settlementsOptions}
onChange={this.saveSettlements}
/>
If this doesn't help consider using the default select and use a PureComponent to render its options. Or try to use custom PureComponents to render parts of the Select.
Also check React-select is slow when you have more than 1000 items
The size of the array shouldn't be a problem, because only the reference is stored in the state object, and react doesn't do any deep equality on state.
Maybe your render or componentDidUpdate iterates over this big array and that causes the problem.
Try to profile your app if this doesn't help.
Related
How to push element inside useState array AND deleting said object in a dynamic matter using React hooks (useState)?
I'm most likely not googling this issue correctly, but after a lot of research I haven't figured out the issue here, so bare with me on this one.
The situation:
I have a wrapper JSX component which holds my React hook (useState). In this WrapperComponent I have the array state which holds the objects I loop over and generate the child components in the JSX code. I pass down my onChangeUpHandler which gets called every time I want to delete a child component from the array.
Wrapper component:
export const WrapperComponent = ({ component }) => {
// ID for component
const { odmParameter } = component;
const [wrappedComponentsArray, setWrappedComponentsArray] = useState([]);
const deleteChildComponent = (uuid) => {
// Logs to array "before" itsself
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5"},
{"uuid":"0ed3cc3-7cd-c647-25db-36ed78b5cbd8"]
*/
setWrappedComponentsArray(prevState => prevState.filter(item => item !== uuid));
// After
console.log(wrappedComponentsArray);
/*
Output: [{"uuid":"acc0d4c-165c-7d70-f8e-d745dd361b5",{"uuid":"0ed3cc3-
7cd-c647-25db-36ed78b5cbd8"]
*/
};
const onChangeUpHandler = (event) => {
const { value } = event;
const { uuid } = event;
switch (value) {
case 'delete':
// This method gets hit
deleteChildComponent(uuid);
break;
default:
break;
}
};
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
onChangeOut: onChangeUpHandler,
};
setWrappedComponentsArray(wrappedComponentsArray => [...wrappedComponentsArray, objToAdd]);
// Have also tried this solution with no success
// setWrappedComponentsArray(wrappedComponentsArray.concat(objToAdd));
};
return (
<>
<div className='page-content'>
{/*Loop over useState array*/}
{
wrappedComponentsArray.length > 0 &&
<div>
{wrappedComponentsArray.map((props) => {
return <div className={'page-item'}>
<ChildComponent {...props} />
</div>;
})
}
</div>
}
{/*Add component btn*/}
{wrappedComponentsArray.length > 0 &&
<div className='page-button-container'>
<ButtonContainer
variant={'secondary'}
label={'Add new component'}
onClick={() => addOnClick()}
/>
</div>
}
</div>
</>
);
};
Child component:
export const ChildComponent = ({ uuid, onChangeOut }) => {
return (
<>
<div className={'row-box-item-wrapper'}>
<div className='row-box-item-input-container row-box-item-header'>
<Button
props={
type: 'delete',
info: 'Deletes the child component',
value: 'Delete',
uuid: uuid,
callback: onChangeOut
}
/>
</div>
<div>
{/* Displays generated uuid in the UI */}
{uuid}
</div>
</div>
</>
)
}
As you can see in my UI my adding logic works as expected (code not showing that the first element in the UI are not showing the delete button):
Here is my problem though:
Say I hit the add button on my WrapperComponent three times and adds three objects in my wrappedComponentsArray gets rendered in the UI via my mapping in the JSX in the WrapperComponent.
Then I hit the delete button on the third component and hit the deleteChildComponent() funtion in my parent component, where I console.log my wrappedComponentsArray from my useState.
The problem then occurs because I get this log:
(2) [{…}, {…}]
even though I know the array has three elements in it, and does not contain the third (and therefore get an undefined, when I try to filter it out, via the UUID key.
How do I solve this issue? Hope my code and explanation makes sense, and sorry if this question has already been posted, which I suspect it has.
You provided bad filter inside deleteChildComponent, rewrite to this:
setWrappedComponentsArray(prevState => prevState.filter(item => item.uuid !== uuid));
You did item !== uuid, instead of item.uuid !== uuid
Please try this, i hope this works
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item !== uuid));
};
After update
const deleteChildComponent = (uuid) => {
console.log(wrappedComponentsArray);
setWrappedComponentsArray(wrappedComponentsArray.filter(item => item.uuid !== uuid)); // item replaced to item.uuid
};
Huge shoutout to #Jay Vaghasiya for the help.
Thanks to his expertise we managed to find the solution.
First of, I wasn't passing the uuid reference properly. The correct was, when making the objects, and pushing them to the array, we passed the uuid like this:
const addOnClick = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid(),
parentOdmParameter: odmParameter,
onChangeOut: function(el) { onChangeUpHandler(el, this.uuid)}
};
setWrappedComponentsArray([...wrappedComponentsArray, objToAdd]);
};
When calling to delete function the function that worked for us, was the following:
const deleteChildComponent = (uuid) => {
setWrappedComponentsArray(item => item.filter(__item => __item.uuid !== uuid)); // item replaced to item.uuid
};
App takes user options and creates an array objects randomly, and based on user options. (it's a gamer tag generator, writing to learn react.js). As is, App is a functional component and I use useState to store array of objects (gamertags) and the current selected options.
I use formik for my simple form. It takes two clicks to get a new item with updated options. I know why, options in state of App doesn't not update until it rerenders as the function for form submission is async. Therefore, all of my options are updated, after the first click, and are correct with the second because they were updated with the rerendering and after I needed them.
I know the solution is to use a useEffect hook, but despite reading over other posts and tuts, I don't understand how to apply it. It's my first instance of needing that hook and I'm still learning.
I wrote a simplified App to isolate the problem as much as possible and debug. https://codesandbox.io/s/morning-waterfall-impg3?file=/src/App.js
export default function App() {
const [itemInventory, setItemInventory] = useState([
{ options: "apples", timeStamp: 123412 },
{ options: "oranges", timeStamp: 123413 }
]);
const [options, setOptions] = useState("apples");
const addItem = (item) => {
setItemInventory([item, ...itemInventory]);
};
const createItem = () => {
return { options: options, timeStamp: Date.now() };
};
class DisplayItem extends React.Component {
render() { // redacted for brevity}
const onFormUpdate = (values) => {
const newOption = values.options;
setOptions(newOption);
addItem(createItem());
};
const UserForm = (props) => {
return (
<div>
<Formik
initialValues={{
options: props.options
}}
onSubmit={async (values) => {
await new Promise((r) => setTimeout(r, 500));
console.log(values);
props.onUpdate(values);
}}
>
{({ values }) => (
<Form> //redacted for brevity
</Form>
)}
</Formik>
</div>
);
};
return (
<div className="App">
<div className="App-left">
<UserForm options={options} onUpdate={onFormUpdate} />
</div>
<div className="App-right">
{itemInventory.map((item) => (
<DisplayItem item={item} key={item.timeStamp} />
))}
</div>
</div>
);
}
This is probably a "layup" for you all, can you help me dunk this one? Thx!
Solved problem by implementing the useEffect hook.
Solution: The functions that create and add an item to the list, addItem(createItem()), become the first argument for the useEffect hook. The second argument is the option stored in state, [options]. The callback for the form, onFormUpdate only updates the option in state and no longer tries to alter state, i.e. create and add an item to the list. The useEffect 'triggers' the creation and addition of a new item, this time based on the updated option because the updated option is the second argument of the hook.
Relevant new code:
useEffect( () => {
addItem(createItem());
}, [options]);
const onFormUpdate = (values) => {
const newOption = values.options;
setOptions(newOption);
//addItem used to be here
};
I would like to use the awesome react-widgets DropDownList to load records on demand from the server.
My data load all seems to be working. But when the data prop changes, the DropDownList component is not displaying items, I get a message
The filter returned no results
Even though I see the data is populated in my component in the useEffect hook logging the data.length below.
I think this may be due to the "filter" prop doing some kind of client side filtering, but enabling this is how I get an input control to enter the search term and it does fire "onSearch"
Also, if I use my own component for display with props valueComponent or listComponent it bombs I believe when the list is initially empty.
What am I doing wrong? Can I use react-widgets DropDownList to load data on demand in this manner?
//const ItemComponent = ({item}) => <span>{item.id}: {item.name}</span>;
const DropDownUi = ({data, searching, fetchData}) => {
const onSearch = (search) => {
fetchData(search);
}
// I can see the data coming back here!
useEffect(() => {
console.log(data.length);
}, [data]);
<DropDownList
data={data}
filter
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
Got it! This issue is with the filter prop that you are passing to the component. The filter cannot take a true as value otherwise that would lead to abrupt behavior like the one you are experiencing.
This usage shall fix your problem:
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
Working demo
The entire code that I had tried at codesandbox:
Warning: You might have to handle the clearing of the values when the input is empty.
I thought that the logic for this was irrelevant to the problem statement. If you want, I can update that as well.
Also, I added a fakeAPI when searchTerm changes that resolves a mocked data in 2 seconds(fake timeout to see loading state).
import * as React from "react";
import "./styles.css";
import { DropdownList } from "react-widgets";
import "react-widgets/dist/css/react-widgets.css";
// Coutesy: https://usehooks.com/useDebounce
import useDebounce from "./useDebounce";
interface IData {
id: string;
name: string;
}
const fakeAPI = () =>
new Promise<IData[]>((resolve) => {
window.setTimeout(() => {
resolve([
{
name: "NA",
id: "user210757"
},
{
name: "Yash",
id: "id-1"
}
]);
}, 2000);
});
export default function App() {
const [state, ss] = React.useState<{
searching: boolean;
data: IData[];
searchTerm: string;
}>({
data: [],
searching: false,
searchTerm: ""
});
const debounceSearchTerm = useDebounce(state.searchTerm, 1200);
const setState = (obj: Record<string, any>) =>
ss((prevState) => ({ ...prevState, ...obj }));
const getData = () => {
console.log("getting data...");
setState({ searching: true });
fakeAPI().then((response) => {
console.log("response: ", response);
setState({ searching: false, data: response });
});
};
React.useEffect(() => {
if (debounceSearchTerm) {
getData();
}
}, [debounceSearchTerm]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
</div>
);
}
Let me know if you have more queries on this 😇
So it i think that list should be loaded a then you can filtering your loaded data.In your example on the beginning you don't have value so list is empty, you tape in some text and then value of list re render but it look like is not filtered.....
However I look through code base, and it's look like is not ready until you don't set manually open prop drop down list component. In getDerivedStateFromprops, next data list is read only if in next props is open set. to true
From DropDwonList
static getDerivedStateFromProps(nextProps, prevState) {
let {
open,
value,
data,
messages,
searchTerm,
filter,
minLength,
caseSensitive,
} = nextProps
const { focusedItem } = prevState
const accessors = getAccessors(nextProps)
const valueChanged = value !== prevState.lastValue
let initialIdx = valueChanged && accessors.indexOf(data, value)
//-->> --- -- --- -- -- -- -- - - - - - - - - - --- - - --------
//-->>
if (open)
data = Filter.filter(data, {
filter,
searchTerm,
minLength,
caseSensitive,
textField: accessors.text,
})
const list = reduceToListState(data, prevState.list, { nextProps })
const selectedItem = data[initialIdx]
const nextFocusedItem = ~data.indexOf(focusedItem) ? focusedItem : data[0]
return {
data,
list,
accessors,
lastValue: value,
messages: getMessages(messages),
selectedItem: valueChanged
? list.nextEnabled(selectedItem)
: prevState.selectedItem,
focusedItem:
(valueChanged || focusedItem === undefined)
? list.nextEnabled(selectedItem !== undefined ? selectedItem : nextFocusedItem)
: nextFocusedItem,
}
}
I would try:
<DropDownList
data={data}
filter
open
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
if it will be works, then you just have to
manage your open state by yourself.
I have a table of schedules that is rendered by a dropdown. Each schedule can then be marked for export via a slider, this will store the schedule id in scheduleIdsToExport and show that schedule in the export table.
But if I change the Select Query dropdown, which renders more schedules specific to that query, the schedules marked for export from the previous query disappear from the table. I want the schedules marked for export to persist in the table no matter what query is selected from the dropdown.
So I'm thinking I need to have something in my slider function to update state with the all the schedule objects marked for export and have them persist in the exported table. I'm not exactly sure how to go about storing all the schedules to keep them in the exported table and have the scheduleIdsToExport array also keep the id's of each schedule
slider = ({ id, isExported }) => {
if (isExported === true) {
this.setState(
{
scheduleIdsToExport: [id, ...this.state.scheduleIdsToExport]
},
() => {
console.log(this.state.scheduleIdsToExport);
}
);
} else {
const newArray = this.state.scheduleIdsToExport.filter(
storedId => storedId !== id
);
this.setState(
{
scheduleIdsToExport: newArray
},
() => {
console.log(this.state.scheduleIdsToExport);
}
);
}
};
The sandbox here will provide further explanation on what is happening.
This is just chaotic!
The problem : Keep track from multiples list of items(schedules) that will eventually be added to another list schedulesToExport
The Solution :
Create a parent component that reflects the previously described state
class Container extends Component{
state ={
querys :[
['foo','lore ipsum','it\'s never lupus'],
['bar','ipsumlorem', 'take the canolli']
],
selectedQuery : 0,
schedulesToExport : []
}
}
Now we have a list of lists, that can be interpreted as a list of querys containing a list of schedules
Render a select element to reflect each query:
render(){
const { querys, selectedQuery } = this.state
const options = querys.map((_, index) => (<option value={index}> Query: {index + 1}</option>))
return(
<div>
<select onChange={this.selectQuery}>
{options}
</select>
{
querys[selectedQuery].map(schedule => (
<button onClick={() => this.selectSchedule(index)}> Schedule: {schedule} </button>
))
}
</div>
)
}
What's happening? We are just rendering the selected query by it's index and showing all it's respective schedules.
Implement the selectQuery and selectSchedule methods which will add the selected schedule in the list to export:
selectQuery = e => this.setState({selectedQuery : e.target.value})
selectSchedule = index => {
const { selectedQuery } = this.state
const selected = this.state.querys[selectedQuery][index]
this.setState({
schedulesToExport: this.state.schedulesToExport.concat(selected)
})
}
That's it, now you a have a list of querys being displayed conditionally rendered selectedQuery props is just a index, but could be a property's name. You only see schedules from the current selected query, so when you click on schedule we just return it's index, and the state.querys[selectedQuery][index] will be your selected schedule, that is securely store in the state on a separated list.
I have updated your sandbox here.
In essence, it does not work in your example because of the following:
schedules
.filter(schedule =>
scheduleIdsToExport.includes(Number(schedule.id))
)
.map(schedule => {
return (
<Table.Row>
...
</Table.Row>
);
})
The value of schedules is always set to the current query, hence you end up showing schedules to export for the current query only.
A solution that changes very little of your code is to ditch scheduleIdsToExport altogether, and use schedulesToExport instead. Initially, we'll set schedulesToExport to an empty
object; we'll add schedules to it (keyed by schedule id) every time a schedule is selected - we'll delete schedules in the same way every time a schedule is unselected.
class App extends React.Component {
// ...
slider = ({ id, isExported }) => {
const obj = this.state.schedules.find(s => Number(s.id) === Number(id));
if (isExported === true) {
this.setState({
schedulesToExport: {
...this.state.schedulesToExport,
[id]: {
...obj
}
}
});
} else {
const newSchedulesToExport = this.state.schedulesToExport;
delete newSchedulesToExport[id];
this.setState({
schedulesToExport: newSchedulesToExport
});
}
};
// ...
}
You would then render the schedules to export as follows:
Object.keys(schedulesToExport).map(key => {
const schedule = schedulesToExport[key];
return (
<Table.Row>
...
</Table.Row>
);
})
Again, see more details in sandbox here.
Scenario::
I have a parent component () that is using context API this is the Provider that passes the state to children components. Each of these children components has children ( a form to handle input ) when those forms are submitted I then use props to pass the input all the back up the parent component () aka the Provider whose job is to call an external script to calculate data from what the user submits which is received as a promise. Once I get the data I setState with the updated data.
My Problem::
I have two arrays of objects that are added to the state after the user submits the form. One containing all the values of the input the user typed in. Two the results the external script returned based on however many values the user typed in. Now is when I want to render both arrays to the view once we get them. Array one renders fine, but array two renders nothing. When I log array two in the console the array is filled, but if I log the array[index] and give it a specific index I get undefined
1st User submits the form
<KeywordSearch submitForm={this.handleKeywordFormSubmit} name="Bracket" />
Form is passed up to the Parent Component the Provider it looks like this
handleKeywordFormSubmit = () => {
let query = {
options: {
keyword: true
},
data: {
keyword_bracket: this.state.values
}
}
this.props.updateReport(query)
}
Values array is structured like this
values: [
{
value: 'input1',
label: 'input1'
},
{
value: 'input2',
label: 'input2'
}
]
2nd the parent component takes the query and runs the external script, returns the result which is an object which is then pushed in an array. When we're done the new array is added to the Context.Provider state to be accessed by the children components Context.Consumer
handleReportUpdate = (query) => {
if(query.options.keyword){
console.log('Keyword');
let keyword_bracket = []
query.data.keyword_bracket.forEach(bracket => {
tableBuilder.keywords(
this.state.thisReport.client.data.organic_research,
bracket
)
.then(keyword_summary => {
keyword_bracket.push(keyword_summary)
})
})
console.log(keyword_bracket)
let updatedReport = {
client: {
data: {
...this.state.thisReport.client.data,
keywordBrackets: keyword_bracket,
},
info: {
...this.state.thisReport.client.info,
Keyword: query.data.keyword_bracket
}
},
competitors: [...this.state.thisReport.competitors],
name: this.state.thisReport.name,
_id: this.state.thisReport._id
}
this.setState({
thisReport: updatedReport
})
}
}
3rd the Rendering stage in the child component the Context.Consumer
<Grid item xs={12} style={{margin: '20px 0'}}>
{
context.thisReport.client.data.keywordBrackets.length !== 0 ?
context.thisReport.client.data.keywordBrackets.map(bracket =>
{
return(
<div key={bracket.searchTerms.value}>{bracket.searchTerms.value}</div>
)
}
)
:
(<div>Nothing Here</div>)
}
</Grid>
<Grid item xs={12} style={{margin: '20px 0'}}>
<KeywordSearch submitForm={this.handleCompetitorFormSubmit}
name='Competitor' />
</Grid>
<Grid item xs={12} style={{margin: '20px 0'}}>
{
context.thisReport.client.info.Keyword.length !== 0 ?
context.thisReport.client.info.Keyword.map(bracket =>
{
return(
<div key={bracket.value}>{bracket.value}</div>
)
}
)
:
undefined
}
</Grid>
Here's where it's confusing because by following the process above when its time to render the new state from Context the codes second rendering that maps the context.thisReport.client.info.Keyword are rendered perfectly fine on the screen. The first rendering context.thisReport.client.data.keywordBrackets returns nothing. As a test, you can see I have added a <div>Nothing Here</div>
if the condition returns false. At first, before the user goes through the process of submitting the form that is shown on the screen as expected. Once they submit the form it disappears and where the return(
<div key={bracket.searchTerms.value}>{bracket.searchTerms.value}</div>) the output should be shown its blank. I have log's in the console that says the state is there the react dev tools confirms it as well. One weird thing is if I try to access the array by index I get undefined console.log(context.thisReport.client.data.keywordBrackets[0]) //undefined
This is a lot to take in so thanks in advance for reading. If you have any solutions advice lmk!!
Can you try the following?
handleReportUpdate = (query) => {
//create array of jobs
var getKeywordSummaries = [];
if(query.options.keyword){
console.log('Keyword');
let keyword_bracket = []
query.data.keyword_bracket.forEach(bracket => {
let getKeywordSummary = tableBuilder.keywords(
this.state.thisReport.client.data.organic_research,
bracket
)
.then(keyword_summary => {
keyword_bracket.push(keyword_summary)
})
//push each job into the jobs array
getKeywordSummaries.push(getKeywordSummary);
});
//wait until all jobs are done
Promise.all(getKeywordSummaries).then(function(results) {
console.log(keyword_bracket)
let updatedReport = {
client: {
data: {
...this.state.thisReport.client.data,
keywordBrackets: keyword_bracket,
},
info: {
...this.state.thisReport.client.info,
Keyword: query.data.keyword_bracket
}
},
competitors: [...this.state.thisReport.competitors],
name: this.state.thisReport.name,
_id: this.state.thisReport._id
}
this.setState({
thisReport: updatedReport
});
}
}
}