Problem rendering search results in search component React, Semantic-UI-React - javascript

I am trying to use the custom render for a search box. I am using semantic-ui-react.
Following is a code snippet for the render method :
render() {
const { isLoading, value, results } = this.state
const resultRenderer = ({ clientName }) => (
<div>
<p>{clientName}</p>
</div>
);
resultRenderer.propTypes = {
clientName: PropTypes.string
}
return (
<Search
loading={isLoading}
onResultSelect={this.handleResultSelect}
onSearchChange={_.debounce(this.handleSearchChange, 10, {loading: true})}
results={this.state.results}
value={value}
resultRenderer={this.resultRenderer}
{...this.props}
/>
)
}
}
The code snippet below is the handleSearchChange method :
handleSearchChange = (e, { value }) => {
var resultsArray = []
this.setState({ isLoading: true, value })
api.autoCompleteClient(value).then(res => {
var tempArray = res.data.data.hits.hits
var autoResult = []
for(var i = 0; i < tempArray.length; i++)
{
resultsArray.push(tempArray[i]._source.clientName)
}
}).catch(error => {
console.log('Some error got while performing autocomplete for clients ', error)
})
setTimeout(() => {
if (this.state.value.length < 1) return this.setState(initialState)
this.setState({
isLoading: false,
results: resultsArray,
})
}, 300)
}
The results in the render when printed can be seen in the following screenshot :
However, the results in the search bar are empty and I am getting the following errors as can be seen below :
Inside the result renderer if I put a print statement, I get the following results :
Any ideas as to what I may be doing wrong. All help would be greatly appreciated!

I think this line is the culprit:
resultsArray.push(tempArray[i]._source.clientName)
Not sure what your data looks like, but if you have three empty results and there are three results, I'm guessing it's not grabbing the right property.
It looks like you need to set the state in the .then() callback for api.autoCompleteClient. This way you know the data is ready, instead of just waiting for 300ms.
I also suggest using Array.map() instead of the for loop for brevity/showing intent.
And just a heads up, using the React Devtools extension in Chrome lets you examine the state/props of components in real-time.
As for the first first two console warnings, I would just look in SemanticUI docs and see what the props are for that component, it looks like two aren't actually used.
The last warning just meaning you need a key prop when mapping multiple components, to help render them more efficiently. So just key={someUniqueValue}.
Edit: Try this :)
const resultRenderer = (data) => {
debugger;
return (
<div>
<p>{data.clientName}</p>
</div>
);
}

can you also paste your code in search component?

I was able to get it working with help from #adamz4008.
The updated code for handleSearchChange with the corrections looks as follows:
handleSearchChange = (e, { value }) => {
var resultsArray = []
this.setState({ isLoading: true, value })
api.autoCompleteClient(value).then(res => {
var tempArray = res.data.data.hits.hits
var autoResult = []
for(var i = 0; i < tempArray.length; i++)
{
//Converted every result into an object
//Semantic React UI renders objects
var result = {clientName : tempArray[i]._source.clientName}
resultsArray.push(result)
}
}).catch(error => {
console.log('Some error got while performing autocomplete for clients ', error)
})
setTimeout(() => {
if (this.state.value.length < 1) return this.setState(initialState)
this.setState({
isLoading: false,
results: resultsArray,
})
}, 300)
}
Also the result renderer looks as follows after correction :
const resultRenderer = (data) => {
console.log('Inside resultRenderer ', data)
return (
<div>
<p>{data.clientName}</p>
</div>
);
}

Related

How is my reduce function getting an error after successfully iterating over the array?

I'm looking through a CSV file with name/stock symbol objects. When the user inputs a company name and clicks submit I'm searching through the file to locate the company record and extract the symbol.
Within a handleSubmit React function I've got a reduce that keeps failing the try/catch and throwing an error.
Data array looks like this:
[{"Symbol": "A", "Name": "Agilent Technologies Inc. Common Stock"}, {"Symbol": "AA", "Name": "Alcoa Corporation Common Stock"},etc.]
My functional component:
export function ParseAndSearch() {
const [newAnswer, setNewAnswer] = useState({});
// parse csv file here - output is 'data' array like above
const handleSubmit => {
// user input = userCompany (a string)
try {
let output = [];
let tempAnswer =
data.reduce((output, company) => {
if (company.Name.toLowerCase().includes(userCompany.toLowerCase())) {
output.push(company);
};
let dummyAnswer = [...output];
console.log('filtered list is: ' + JSON.stringify(dummyAnswer));
return output;
}, []);
setNewAnswer(tempAnswer);
} catch (err) {
console.log('error block');
}
}
return (
<div>
Company Name: {newAnswer['Symbol']}
</div>
}
The
How is my reduce failing? I'm getting results on "dummyAnswer" with each iteration but then it seems to fail when I get to the end of the reduce or when I try to "setNewAnswer."
I've had success with find (instead of reduce), but that does not help me as I need to return more than one answer for some searches.
If you need to return more than one answer then you'll probably need to change the newAnswer state to an array and use the filter function:
const [newAnswer, setNewAnswer] = useState([]);
// parse csv file here - output is 'data' array like above
const handleSubmit = () => {
// user input = userCompany (a string)
const tempAnswer = data.filter((company) =>
company.Name?.toLowerCase().includes(userCompany.toLowerCase())
);
setNewAnswer(tempAnswer);
};
return (
<div>
{/* Display multiple answers */}
{newAnswer.map((answer, index) => {
return <div key={index}>Company Name: {answer.Symbol}</div>;
})}
</div>
)

API is returning data, but cannot read properties

Hello (I am learning React), I am working with an API that returns pictures of random people, however the problem I am having is when I use axios.get I am getting the response from the API, and I can see the results in the console, but when I try to access them it says "Cannot read properties of picture".
The thing I am making is that when the user press the input field it gets an URL of a random picture from the API and the value from that inputText changes to the URL, but it says "Cannot read properties of picture" when clicking on the input, but the API is returning me the data in the console.
Here is what my API returns me.
Here is my code.
class PersonasInsert extends Component {
urlPersonas = "https://randomuser.me/api/?inc=picture";
constructor(props) {
super(props);
this.state = {
peticionImagen: null,
name: "",
last: "",
image: "",
};
}
peticionImagenPersona = async () => {
await axios.get(this.urlPersonas).then(
(response) => {
this.setState({ peticionImagen: response.data.results });
},
(error) => {
console.log(error);
}
);
};
handleChangeImage = async (event) => {
this.peticionImagenPersona();
const peticionImagen = this.state.peticionImagen.picture.large
this.setState({ peticionImagen });
};
render() {
const { peticionImagen } = this.state;
return (
<Wrapper>
<Title>Insertar Persona</Title>
<Label>image: </Label>
<InputText
type="text"
value={peticionImagen}
readOnly
onClick={this.handleChangeImage}
/>
</Wrapper>
);
}
}
export default PersonasInsert;
Thank you in advance.
Does the API return an array of objects or a single object? It looks like an array from the log you posted, you will need to traverse the array through .map or if you want only the first element then do something like this: this.state.peticionImagen[0].picture.large

React promise code need to change a json value

Need to change data coming back from promise return and add a link wrapper to one of the fields.
This is the React code
this.state = {
medications: [],
}
queryMeds().then((response) => {
this.setState({medications: response});
});
Above returns several records that i see in console as this format (which I need)
(You can see it has json string with fields "value" and "short_description"
0: {value: "VICODIN TAB", short_description: "VICODIN TAB 5-300MG"}
What I need to do is alter this daily to have a change to the "value" So what I want is
0: {value: "<a onClick=jsfunction('VICODIN TAB')>VICODIN TAB</a>", short_description: "VICODIN TAB 5-300MG"}
Things that I was trying to do (push and map) ended up giving me
0: "VICODIN TAB"
Thus both my attempt with a map and a for loop with push is not using value and short_description in a string json
response.map((item)=>{
//adding_list.push( <a onclick={`jsfunction(${item.value}) href=javascript:void(0);`}>{`${item.value}`}</a>);
adding_list.push(item.value, item.short_description)
});
//Nor
for (let i = 0; i < response.length; i++) {
let newMedication = response[i].value; //<a onclick={`jsfunction(${response[i].value}) href=javascript:void(0);`}>{`${response[i].value}`}</a>;
this.setState(prevState => {
return {
medications: [...prevState.medications, newMedication]
}
})
}
let newResponse = response.map((res)=>{
res.value= (<a onClick=jsfunction({res.value})>{res.value}</a>)
return res
})
You are very close!
The method map will return a new (and possibly modified) array, so the first step is to save that in a new variable.
Inside the map method, you can transform the item and return the transformed version. So your code should look like:
// Mutable version
const modifiedResponse = response.map((item) => {
const modifiedValue = <a onClick={() => this.jsfunction(item.value)} href="javascript:void(0);">{item.value}</a>;
item.value = modifiedValue;
return item;
});
// Imutable version
const modifiedResponse = response.map((item) => {
const modifiedValue = <a onClick={() => this.jsfunction(item.value)} href="javascript:void(0);">{item.value}</a>;
return {...item, value: modifiedValue}
});
More info here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

React - Adding a property to an object inside of array in component's state

I am fetching an array of objects in the state, and I am trying to add a property to each of these objects.
The problem is I can see my property being added to each of the objects but when I mapping through all these objects and trying to console.log it I'm getting undefined
Adding a property
addFavourites() {
let data = this.state.data.map((e) => {
e.favourite = false;
return e;
});
return data;
}
state = {
data: []
}
addFavourites's called here:
getItem = () => {
this.service
.mergeData()
.then((body) => {
this.setState({
data: body
},
() => {
this.addFavourites();
},
() => {
this.dataToFilter();
});
});
}
componentDidMount(){
this.getItem();
}
In render function I see all my objects including favourite
console.log(data.map((e) => e));
But this gives an array of undefined
console.log(data.map((e) => e.favourite));
How can I solve this?
First, welcome! You should have probably made it more clear that this question is about React.
Now to the answer, every state modification would be made using setState, so your addFavourites function should be like this:
addFavourites() {
let data = this.state.data.map((e) => {
e.favourite = false;
return e;
});
this.setState({ data: data });
}

Testing React Components setState overload which takes a function

I am trying to test a React component which uses one of the overloads for setState, but am unsure how to assert the call correctly. An example component would be:
class CounterComponent extends React.Component {
updateCounter() {
this.setState((state) => {
return {
counterValue: state.counterValue + 1
};
});
}
}
The assumption here is that this method will be called asyncronously, so cannot rely on the current state, outwith the call to setState (as it may change before setState executes). Can anyone suggest how you would assert this call? The following test fails as it is simply comparing the function names.
it("Should call setState with the expected parameters", () => {
const component = new CounterComponent();
component.setState = jest.fn(() => {});
component.state = { counterValue: 10 };
component.updateCounter();
const anonymous = (state) => {
return {
counterValue: state.counterValue + 1
};
};
//expect(component.setState).toHaveBeenCalledWith({ counterValue: 11 });
expect(component.setState).toHaveBeenCalledWith(anonymous);
});
Edit: Given yohai's response below, i will add some further context as I feel i may have over simplified the problem however i do not want to re-write the entire question for clarity.
In my actual component, the state value being edited is not a simple number, it is an array of objects with the structure:
{ isSaving: false, hasError: false, errorMessage: ''}
and a few other properties. When the user clicks save, an async action is fired for each item in the array, and then the corresponding entry is updated when that action returns or is rejected. As an example, the save method would look like this:
onSave() {
const { myItems } = this.state;
myItems.forEach(item => {
api.DoStuff(item)
.then(response => this.handleSuccess(response, item))
.catch(error => this.handleError(error, item));
});
}
The handle success and error methods just update the object and call replaceItem:
handleSuccess(response, item) {
const updated = Object.assign({}, item, { hasSaved: true });
this.replaceItem(updated);
}
handleError(error, item) {
const updated = Object.assign({}, item, { hasError: true });
this.replaceItem(updated);
}
And replaceItem then replaces the item in the array:
replaceItem(updatedItem) {
this.setState((state) => {
const { myItems } = state;
const working = [...myItems];
const itemToReplace = working.find(x => x.id == updatedItem.id);
if (itemToReplace) {
working.splice(working.indexOf(itemToReplace), 1, updatedItem);
};
return {
myItems: working
};
});
}
replaceItem is the method I am trying to test, and am trying to validate that it calls setState with the correct overload and a function which correctly updated the state.
My answer below details how I have solved this for myself,but comments and answers are welcome =)
#Vallerii: Testing the resulting state does seem a simpler way, however if i do, there is no way for the test to know that the method is not doing this:
replaceItem(updatedItem) {
const { myItems } = state;
const working = [...myItems];
const itemToReplace = working.find(x => x.id == updatedItem.id);
if (itemToReplace) {
working.splice(working.indexOf(itemToReplace), 1, updatedItem);
};
this.setState({ myItems: working });
}
When replaceItem does not use the correct overload for setState, this code fails when called repeatedly as (I assume) react is batching updates and the state this version uses is stale.
I think you should test something a little bit different and it will look somthing like this (I'm using enzyme):
import React from 'react'
import { mount } from 'enzyme'
import CounterComponent from './CounterComponent'
it("Should increase state by one", () => {
const component = mount(<CounterComponent />)
const counter = 10;
component.setState({ counter });
component.instance().updateCounter();
expect(component.state().counter).toEqual(counter + 1);
});
I have come up with a solution to this after some further thought. I am not sure it is the best solution, but given that the updateCounter method in the example above passes a function into the setState call, I can simply get a reference to that function, execute it with a known state and check the return value is correct.
The resulting test looks like this:
it("Should call setState with the expected parameters", () => {
let updateStateFunction = null;
const component = new CounterComponent();
component.setState = jest.fn((func) => { updateStateFunction = func;});
component.updateCounter();
const originalState = { counterValue: 10 };
const expectedState = { counterValue: 11};
expect(component.setState).toHaveBeenCalled();
expect(updateStateFunction(originalState)).toEqual(expectedState);
});

Categories